001 /* 002 * Copyright 2004-2007 Stephen J. McConnell. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.runtime; 020 021 import dpml.appliance.StandardAppliance; 022 import dpml.lang.Value; 023 import dpml.lang.Disposable; 024 import dpml.state.StateDecoder; 025 import dpml.util.DefaultLogger; 026 027 import java.io.IOException; 028 import java.io.Writer; 029 import java.io.File; 030 import java.lang.ref.Reference; 031 import java.lang.ref.SoftReference; 032 import java.lang.ref.WeakReference; 033 import java.lang.reflect.Constructor; 034 import java.lang.reflect.InvocationHandler; 035 import java.lang.reflect.Method; 036 import java.lang.reflect.Proxy; 037 import java.lang.reflect.InvocationTargetException; 038 import java.net.URI; 039 import java.net.URL; 040 import java.util.ServiceLoader; 041 import java.util.WeakHashMap; 042 import java.util.Hashtable; 043 import java.util.Map; 044 import java.util.Set; 045 import java.util.concurrent.Executors; 046 import java.util.concurrent.ExecutorService; 047 import java.util.concurrent.CopyOnWriteArraySet; 048 import java.util.concurrent.TimeUnit; 049 050 import net.dpml.annotation.Activation; 051 import net.dpml.annotation.CollectionPolicy; 052 import net.dpml.annotation.LifestylePolicy; 053 import net.dpml.annotation.ActivationPolicy; 054 055 import net.dpml.appliance.Appliance; 056 057 import net.dpml.lang.Buffer; 058 import net.dpml.lang.ServiceRegistry; 059 import net.dpml.lang.StandardServiceRegistry; 060 import net.dpml.lang.Strategy; 061 import net.dpml.lang.TypeCastException; 062 063 import net.dpml.state.State; 064 import net.dpml.state.StateMachine; 065 066 import net.dpml.transit.Artifact; 067 068 import net.dpml.util.Logger; 069 070 import static dpml.state.DefaultState.NULL_STATE; 071 072 /** 073 * Component strategy. 074 * 075 * @author <a href="http://www.dpml.net">Digital Product Management Laboratory</a> 076 * @version 2.0.0 077 */ 078 public class ComponentStrategy extends Strategy implements Component, ServiceRegistry 079 { 080 private static final Logger LOGGER = new DefaultLogger( "dpml.lang.component" ); 081 private static final ComponentStrategyHandler HANDLER = new ComponentStrategyHandler(); 082 083 private final String m_name; 084 private final int m_priority; 085 private final Class<?> m_class; 086 private final String m_path; 087 private final LifestylePolicy m_lifestyle; 088 private final CollectionPolicy m_collection; 089 private final State m_graph; 090 private final LifestyleHandler m_handler; 091 private final ContextModel m_context; 092 private final ActivationPolicy m_activation; 093 private final PartsDirective m_parts; 094 private final Logger m_logger; 095 private final Map<String,Object> m_map = new Hashtable<String,Object>(); 096 097 private final Set<ComponentListener> m_listeners = new CopyOnWriteArraySet<ComponentListener>(); 098 private final ExecutorService m_queue = Executors.newSingleThreadExecutor(); 099 100 private ServiceRegistry m_registry; 101 102 /** 103 * Creation of a new component strategy. 104 * @param partition the enclosing partition 105 * @param name the component name relative to the enclosing partition 106 * @param priority the component priority 107 * @param type the component class 108 * @param activation the activation policy 109 * @param lifestyle the lifestyle policy 110 * @param collection the collection policy 111 * @param context the context model 112 * @param parts the internal part structure 113 * @exception IOException if an IO error occurs 114 */ 115 ComponentStrategy( 116 final String partition, final String name, int priority, final Class type, 117 ActivationPolicy activation, LifestylePolicy lifestyle, CollectionPolicy collection, 118 ContextModel context, PartsDirective parts ) 119 throws IOException 120 { 121 super( type.getClassLoader() ); 122 123 m_class = type; 124 m_priority = priority; 125 m_activation = activation; 126 m_lifestyle = lifestyle; 127 m_collection = collection; 128 m_context = context; 129 130 m_name = getComponentName( name, m_class ); 131 m_path = getComponentPath( partition, m_name, m_class ); 132 m_logger = getComponentLogger( m_path ); 133 m_graph = getLifecycleGraph( m_class ); 134 m_parts = getPartsDirective( parts ); 135 136 m_parts.initialize( this ); 137 138 m_map.put( "name", m_name ); 139 m_map.put( "path", m_path ); 140 m_map.put( "work", new File( System.getProperty( "user.dir" ) ).getCanonicalFile() ); 141 m_map.put( "temp", new File( System.getProperty( "java.io.tmpdir" ) ).getCanonicalFile() ); 142 m_map.put( "uri", URI.create( "component:" + m_path ) ); 143 144 if( m_logger.isTraceEnabled() ) 145 { 146 final String message = 147 "new " 148 + m_collection.toString().toLowerCase() 149 + " " 150 + m_lifestyle.toString().toLowerCase() 151 + " [" 152 + m_class.getName() 153 + "]"; 154 m_logger.trace( message ); 155 } 156 157 m_handler = getLifestyleHandler( m_lifestyle ); 158 } 159 160 public String getName() 161 { 162 return m_name; 163 } 164 165 public int getPriority() 166 { 167 return m_priority; 168 } 169 170 /** 171 * Add a listener to the component. 172 * @param listener the component listener 173 */ 174 public void addComponentListener( ComponentListener listener ) 175 { 176 m_listeners.add( listener ); 177 } 178 179 /** 180 * Remove a listener from the component. 181 * @param listener the component listener 182 */ 183 public void removeComponentListener( ComponentListener listener ) 184 { 185 m_listeners.remove( listener ); 186 } 187 188 void processEvent( ComponentEvent event ) 189 { 190 Logger logger= getLogger(); 191 for( ComponentListener listener : m_listeners ) 192 { 193 m_queue.execute( new ComponentEventDistatcher( logger, listener, event ) ); 194 } 195 } 196 197 Map<String,Object> getContextMap() 198 { 199 return m_map; 200 } 201 202 public Provider getProvider() 203 { 204 synchronized( m_handler ) 205 { 206 return m_handler.getProvider(); 207 } 208 } 209 210 public void release( Provider provider ) 211 { 212 synchronized( m_handler ) 213 { 214 m_handler.release( provider ); 215 } 216 } 217 218 public void initialize( ServiceRegistry registry ) // TODO: parts initialization should occur here? 219 { 220 if( m_logger.isTraceEnabled() ) 221 { 222 m_logger.trace( "initialization" ); 223 } 224 m_registry = registry; 225 } 226 227 public boolean isaCandidate( Class<?> type ) 228 { 229 return type.isAssignableFrom( m_class ); 230 } 231 232 public <T>T lookup( Class<T> service ) 233 { 234 if( m_logger.isTraceEnabled() ) 235 { 236 m_logger.trace( "lookup: " + service.getName() ); 237 } 238 239 for( String key : m_parts.getKeys() ) 240 { 241 Strategy strategy = m_parts.getStrategy( key ); 242 if( strategy.isaCandidate( service ) ) 243 { 244 try 245 { 246 return strategy.getInstance( service ); 247 } 248 catch( Exception e ) 249 { 250 if( strategy instanceof ComponentStrategy ) 251 { 252 ComponentStrategy s = (ComponentStrategy) strategy; 253 String path = s.getComponentPath(); 254 255 final String error = 256 "Lookup aquisition in [" 257 + getComponentPath() 258 + "] failed while aquiring the service [" 259 + service.getName() 260 + "] from the provider [" 261 + path 262 + "]."; 263 throw new ComponentError( error, e ); 264 } 265 else 266 { 267 final String error = 268 "Lookup aquisition in [" 269 + getComponentPath() 270 + "] failed while aquiring the service [" 271 + service.getName() 272 + "]."; 273 throw new ComponentError( error, e ); 274 } 275 } 276 } 277 } 278 279 if( null != m_registry ) 280 { 281 return m_registry.lookup( service ); 282 } 283 else 284 { 285 ServiceRegistry registry = new StandardServiceRegistry(); 286 return registry.lookup( service ); 287 } 288 } 289 290 public void terminate() 291 { 292 terminate( 10, TimeUnit.SECONDS ); 293 } 294 295 void terminate( long timeout, TimeUnit units ) 296 { 297 synchronized( this ) 298 { 299 if( getLogger().isTraceEnabled() ) 300 { 301 getLogger().trace( "termination" ); 302 } 303 m_handler.terminate(); 304 m_queue.shutdown(); 305 try 306 { 307 boolean ok = m_queue.awaitTermination( timeout, units ); 308 if( !ok ) 309 { 310 final String message = 311 "Component termination timeout in [" 312 + getName() 313 + "] (some events may not have been processed)."; 314 getLogger().warn( message ); 315 } 316 } 317 catch( Exception e ) 318 { 319 e.printStackTrace(); 320 } 321 } 322 } 323 324 /** 325 * Internal support for the resolution of a context service lookup request. 326 * The service classname comes from a context entry in this component and 327 * is resolved by the parent component. The parent evaluates off of its 328 * internal parts for a component implementing the service and if found, 329 * the instance is returned. 330 */ 331 <T>T getService( Class<?> clazz, Class<T> type ) throws Exception // TODO: ensure we don't evaluate the requestor 332 { 333 if( getLogger().isTraceEnabled() ) 334 { 335 getLogger().trace( "invoking lookup in " + getComponentPath() + " for " + clazz.getName() ); 336 } 337 if( null != m_registry ) 338 { 339 try 340 { 341 Object value = m_registry.lookup( clazz ); 342 return type.cast( value ); 343 } 344 catch( Exception e ) 345 { 346 final String error = 347 "Service lookup in component [" 348 + getComponentPath() 349 + "] failed."; 350 throw new ComponentException( error, e ); 351 } 352 } 353 else 354 { 355 return null; 356 } 357 } 358 359 Class getComponentClass() 360 { 361 return m_class; 362 } 363 364 String getComponentName() 365 { 366 return m_name; 367 } 368 369 String getComponentPath() 370 { 371 return m_path; 372 } 373 374 ContextModel getContextModel() 375 { 376 return m_context; 377 } 378 379 PartsDirective getPartsDirective() 380 { 381 return m_parts; 382 } 383 384 State getStateGraph() 385 { 386 return m_graph; 387 } 388 389 Logger getComponentLogger() 390 { 391 return m_logger; 392 } 393 394 Logger getLogger() 395 { 396 return m_logger; 397 } 398 399 /** 400 * Return the assigned collection policy. 401 * @return the collection policy 402 */ 403 public CollectionPolicy getCollectionPolicy() 404 { 405 return m_collection; 406 } 407 408 /** 409 * Return the assigned lifestyle policy. 410 * @return the lifestyle policy 411 */ 412 public LifestylePolicy getLifestylePolicy() 413 { 414 return m_lifestyle; 415 } 416 417 /** 418 * Return the assigned activation policy. 419 * @return the activation policy 420 */ 421 public ActivationPolicy getActivationPolicy() 422 { 423 return m_activation; 424 } 425 426 private Logger getComponentLogger( String path ) 427 { 428 return new DefaultLogger( path ); 429 } 430 431 public <T>T getInstance( Class<T> clazz ) 432 { 433 if( clazz.equals( Component.class ) ) 434 { 435 return clazz.cast( this ); 436 } 437 synchronized( m_handler ) 438 { 439 Provider provider = getProvider(); 440 if( clazz.equals( Provider.class ) ) 441 { 442 return clazz.cast( provider ); 443 } 444 Object instance = provider.getInstance( clazz ); 445 return clazz.cast( instance ); 446 } 447 } 448 449 public <T>T getContentForClass( Class<T> c ) throws IOException 450 { 451 if( c.isAssignableFrom( m_class ) ) 452 { 453 return getInstance( c ); 454 } 455 else if( c == Provider.class ) 456 { 457 synchronized( m_handler ) 458 { 459 Provider provider = m_handler.getProvider(); 460 return c.cast( provider ); 461 } 462 } 463 else if( c.isAssignableFrom( getClass() ) ) 464 { 465 return c.cast( this ); 466 } 467 else if( c == Appliance.class ) 468 { 469 Logger logger = getComponentLogger(); 470 Appliance appliance = new StandardAppliance( logger, this ); 471 return c.cast( appliance ); 472 } 473 else 474 { 475 return null; 476 } 477 } 478 479 public void encode( Buffer buffer, String key ) throws IOException 480 { 481 String name = getComponentName(); 482 String classname = getComponentClass().getName(); 483 ContextDirective context = getContextModel().getDirective(); 484 PartsDirective parts = getPartsDirective(); 485 486 boolean flag = buffer.isNamespace( NAMESPACE ); 487 if( flag ) 488 { 489 buffer.nl( "<component" ); 490 } 491 else 492 { 493 buffer.nl( "<component xmlns=\"" + NAMESPACE + "\"" ); 494 buffer.nl( " " ); 495 } 496 if( null != key ) 497 { 498 buffer.write( " key=\"" + key + "\"" ); 499 } 500 if( null != name ) 501 { 502 buffer.write( " name=\"" + name + "\"" ); 503 } 504 if( m_priority != 0 ) 505 { 506 buffer.write( " priority=\"" + m_priority + "\"" ); 507 } 508 buffer.write( " class=\"" + classname + "\"" ); 509 if( ( context.size() == 0 ) && ( parts.size() == 0 ) ) 510 { 511 buffer.write( "/>" ); 512 } 513 else 514 { 515 buffer.write( ">" ); 516 Buffer b = buffer.namespace( NAMESPACE ); 517 context.encode( b.indent(), null ); 518 parts.encode( b.indent() ); 519 buffer.nl( "</component>" ); 520 } 521 } 522 523 private PartsDirective getPartsDirective( PartsDirective directive ) 524 { 525 if( null == directive ) 526 { 527 return new PartsDirective(); 528 } 529 else 530 { 531 return directive; 532 } 533 } 534 535 private String getComponentPath( String partition, String name, Class c ) 536 { 537 if( null == partition ) 538 { 539 return "/" + name; 540 } 541 else 542 { 543 return "/" + partition.replace( ".", "/" ) + "/" + name; 544 } 545 } 546 547 private String getComponentName( String name, Class<?> c ) 548 { 549 if( null != name ) 550 { 551 return name; 552 } 553 else 554 { 555 return getComponentName( c ); 556 } 557 } 558 559 //----------------------------------------------------------------------- 560 // Lifestyle handlers 561 //----------------------------------------------------------------------- 562 563 private LifestyleHandler getLifestyleHandler( LifestylePolicy policy ) 564 { 565 if( policy.equals( LifestylePolicy.SINGLETON ) ) 566 { 567 return new SingletonLifestyleHandler( this ); 568 } 569 else if( policy.equals( LifestylePolicy.THREAD ) ) 570 { 571 return new ThreadLifestyleHandler( this ); 572 } 573 else 574 { 575 return new TransientLifestyleHandler( this ); 576 } 577 } 578 579 /** 580 * Singleton holder class. The singleton holder mains a single 581 * <tt>LifestyleHandler</tt> of a component relative to the component model 582 * identity within the scope of the controller. References to the 583 * singleton instance will be shared across mutliple threads. 584 */ 585 private class SingletonLifestyleHandler extends LifestyleHandler 586 { 587 private Reference<Provider> m_reference; 588 589 SingletonLifestyleHandler( ComponentStrategy strategy ) 590 { 591 super( strategy ); 592 Provider provider = new StandardProvider( strategy ); 593 m_reference = createReference( null ); 594 } 595 596 Provider getProvider() 597 { 598 Provider provider = m_reference.get(); 599 if( null == provider ) 600 { 601 ComponentStrategy strategy = getComponentStrategy(); 602 provider = new StandardProvider( strategy ); 603 m_reference = createReference( provider ); 604 } 605 return provider; 606 } 607 608 void release( Provider provider ) 609 { 610 } 611 612 void terminate() 613 { 614 synchronized( this ) 615 { 616 Provider provider = m_reference.get(); 617 if( null != provider ) 618 { 619 if( provider instanceof Disposable ) 620 { 621 Disposable disposable = (Disposable) provider; 622 disposable.dispose(); 623 } 624 m_reference = createReference( null ); 625 } 626 } 627 } 628 } 629 630 /** 631 * Transient holder class. The transient holder provides support for 632 * the transient lifestyle ensuing the creation of a new <tt>LifestyleHandler</tt> 633 * per request. 634 */ 635 private class TransientLifestyleHandler extends LifestyleHandler 636 { 637 private final WeakHashMap<Provider,Void> m_providers = new WeakHashMap<Provider,Void>(); // transients 638 639 TransientLifestyleHandler( ComponentStrategy strategy ) 640 { 641 super( strategy ); 642 } 643 644 Provider getProvider() 645 { 646 ComponentStrategy strategy = getComponentStrategy(); 647 Provider provider = new StandardProvider( strategy ); 648 m_providers.put( provider, null ); 649 return provider; 650 } 651 652 void release( Provider provider ) 653 { 654 if( null == provider ) 655 { 656 return; 657 } 658 if( m_providers.containsKey( provider ) ) 659 { 660 m_providers.remove( provider ); 661 if( provider instanceof Disposable ) 662 { 663 Disposable disposable = (Disposable) provider; 664 disposable.dispose(); 665 } 666 } 667 } 668 669 void terminate() 670 { 671 Provider[] providers = m_providers.keySet().toArray( new Provider[0] ); 672 for( Provider provider : providers ) 673 { 674 release( provider ); 675 } 676 } 677 } 678 679 /** 680 * The ThreadHolder class provides support for the per-thread lifestyle 681 * policy within which new <tt>LifestyleHandler</tt> creation is based on a single 682 * <tt>LifestyleHandler</tt> per thread. 683 */ 684 private class ThreadLifestyleHandler extends LifestyleHandler 685 { 686 private final ThreadLocalHolder m_threadLocalHolder = new ThreadLocalHolder(); 687 688 ThreadLifestyleHandler( ComponentStrategy strategy ) 689 { 690 super( strategy ); 691 } 692 693 Provider getProvider() 694 { 695 return (Provider) m_threadLocalHolder.get(); 696 } 697 698 void release( Provider provider ) 699 { 700 m_threadLocalHolder.release( provider ); 701 } 702 703 void terminate() 704 { 705 m_threadLocalHolder.terminate(); 706 } 707 708 /** 709 * Internal thread local holder for the per-thread lifestyle holder. 710 */ 711 private class ThreadLocalHolder extends ThreadLocal 712 { 713 private final WeakHashMap<Provider,Void> m_providers = 714 new WeakHashMap<Provider,Void>(); // per thread instances 715 716 synchronized protected Provider initialValue() 717 { 718 ComponentStrategy strategy = getComponentStrategy(); 719 Provider provider = new StandardProvider( strategy ); 720 m_providers.put( provider, null ); 721 return provider; 722 } 723 724 synchronized void release( Provider provider ) 725 { 726 if( m_providers.containsKey( provider ) ) 727 { 728 m_providers.remove( provider ); 729 if( provider instanceof Disposable ) 730 { 731 Disposable disposable = (Disposable) provider; 732 disposable.dispose(); 733 } 734 } 735 } 736 737 synchronized void terminate() 738 { 739 Provider[] providers = m_providers.keySet().toArray( new Provider[0] ); 740 for( Provider provider : providers ) 741 { 742 release( provider ); 743 } 744 } 745 } 746 } 747 748 //----------------------------------------------------------------------- 749 // utilities 750 //----------------------------------------------------------------------- 751 752 private static String getComponentName( Class<?> c ) 753 { 754 if( c.isAnnotationPresent( net.dpml.annotation.Component.class ) ) 755 { 756 net.dpml.annotation.Component annotation = 757 c.getAnnotation( net.dpml.annotation.Component.class ); 758 String name = annotation.name(); 759 if( !"".equals( name ) ) 760 { 761 return name; 762 } 763 } 764 return c.getName(); 765 } 766 767 private static String getComponentNameFromClass( Class<?> c ) 768 { 769 String classname = c.getName(); 770 int n = classname.lastIndexOf( "." ); 771 if( n > -1 ) 772 { 773 return classname.substring( n+1 ); 774 } 775 else 776 { 777 return classname; 778 } 779 } 780 781 private static CollectionPolicy getCollectionPolicy( Class<?> c ) 782 { 783 if( c.isAnnotationPresent( net.dpml.annotation.Component.class ) ) 784 { 785 net.dpml.annotation.Component annotation = 786 c.getAnnotation( net.dpml.annotation.Component.class ); 787 return annotation.collection(); 788 } 789 return CollectionPolicy.HARD; 790 } 791 792 private static State getLifecycleGraph( Class<?> c ) throws IOException 793 { 794 String name = getComponentNameFromClass( c ); 795 String spec = name + ".xgraph"; 796 URL url = getLifecycleURL( c, spec ); 797 if( null != url ) 798 { 799 StateDecoder decoder = new StateDecoder(); 800 return decoder.loadState( url ); 801 } 802 if( c.isAnnotationPresent( net.dpml.annotation.Component.class ) ) 803 { 804 net.dpml.annotation.Component annotation = 805 c.getAnnotation( net.dpml.annotation.Component.class ); 806 String path = annotation.lifecycle(); 807 return getLifecycleGraph( c, path ); 808 } 809 else 810 { 811 return NULL_STATE; 812 } 813 } 814 815 private static State getLifecycleGraph( Class<?> c, String path ) throws IOException 816 { 817 if( ( null == path ) || "".equals( path ) ) 818 { 819 return NULL_STATE; 820 } 821 else 822 { 823 URL url = getLifecycleURL( c, path ); 824 StateDecoder decoder = new StateDecoder(); 825 return decoder.loadState( url ); 826 } 827 } 828 829 private static URL getLifecycleURL( Class<?> c, String path ) throws IOException 830 { 831 int n = path.indexOf( ":" ); 832 if( n > -1 ) 833 { 834 try 835 { 836 return Artifact.toURL( new URI( path ) ); 837 } 838 catch( Exception e ) 839 { 840 final String error = 841 "Bad url: " + path; 842 IOException ioe = new IOException( error ); 843 ioe.initCause( e ); 844 throw ioe; 845 } 846 } 847 else 848 { 849 return c.getResource( path ); 850 } 851 } 852 853 private static final String NAMESPACE = ComponentStrategyHandler.NAMESPACE; 854 855 /** 856 * Returns the string representing of the component. 857 * @return the component as a string 858 */ 859 public String toString() 860 { 861 return getComponentPath(); 862 } 863 864 private static class ComponentEventDistatcher implements Runnable 865 { 866 private Logger m_logger; 867 private ComponentListener m_listener; 868 private ComponentEvent m_event; 869 870 ComponentEventDistatcher( Logger logger, ComponentListener listener, ComponentEvent event ) 871 { 872 m_logger = logger; 873 m_listener = listener; 874 m_event = event; 875 } 876 877 public void run() 878 { 879 try 880 { 881 m_listener.componentChanged( m_event ); 882 } 883 catch( Throwable e ) 884 { 885 final String error = 886 "Event distatch error."; 887 m_logger.error( error, e ); 888 } 889 } 890 } 891 }