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