001    /*
002     * Copyright 2006 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 implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package net.dpml.lang;
017    
018    import dpml.lang.Info;
019    import dpml.lang.Classpath;
020    import dpml.lang.Part;
021    import dpml.lang.DOM3DocumentBuilder;
022    
023    import dpml.util.DefaultLogger;
024    import dpml.util.SimpleResolver;
025    import dpml.util.ElementHelper;
026    
027    import java.io.IOException;
028    import java.net.URI;
029    import java.net.URL;
030    import java.net.URLConnection;
031    import java.lang.management.ManagementFactory;
032    import java.util.Map;
033    import java.util.Hashtable;
034    import java.util.ServiceLoader;
035    import java.util.ArrayList;
036    
037    import javax.management.MBeanServer;
038    import javax.management.ObjectName;
039    import javax.management.InstanceAlreadyExistsException;
040    
041    import net.dpml.annotation.Component;
042    
043    import net.dpml.appliance.Appliance;
044    import net.dpml.appliance.ApplianceFactory;
045    
046    import net.dpml.runtime.ComponentStrategyHandler;
047    
048    import net.dpml.transit.Artifact;
049    import net.dpml.transit.ContentHandler;
050    
051    import net.dpml.util.Logger;
052    import net.dpml.util.Resolver;
053    
054    import org.w3c.dom.Element;
055    import org.w3c.dom.Document;
056    import org.w3c.dom.TypeInfo;
057    
058    /**
059     * Content handler for the 'part' artifact type.
060     * @author <a href="http://www.dpml.net">Digital Product Management Library</a>
061     * @version 2.1.1
062     */
063    public final class PartContentHandler extends ContentHandler implements PartContentManager, ApplianceFactory
064    {
065       /**
066        * Part XSD uri.
067        */
068        public static final String NAMESPACE = "dpml:part";
069    
070        private static final String PART_CONTENT_TYPE = "part";
071        
072        private static final Logger LOGGER = new DefaultLogger( "dpml.part" );
073        
074        // DOES NOT WORK
075        //private static final Map<String, WeakReference<Part>> CACHE = 
076        //  new Hashtable<String, WeakReference<Part>>();
077    
078        // POTENTIAL SOLUTION: map of anchor classloader (keys) to a map of part classloader and parts
079        //private static final Map<ClassLoader,Map<ClassLoader,Part>> CACHE = 
080        //  new Hashtable<ClassLoader,Map<ClassLoader,Part>>();
081        
082        private static final Map<String,Part> CACHE = new Hashtable<String,Part>();
083    
084        private static final DOM3DocumentBuilder DOCUMENT_BUILDER = 
085          new DOM3DocumentBuilder();
086        
087        private static final ComponentStrategyHandler STANDARD_STRATEGY_HANDLER = 
088          new ComponentStrategyHandler();
089    
090       /**
091        * Return a strategy handler based on the supplied component annotation.
092        * @param annotation the component annotation
093        * @return the strategy handler
094        * @exception Exception if an error ocurrs during handler establishment
095        */
096        public static StrategyHandler getStrategyHandler( Component annotation ) throws Exception
097        {
098            String classname = annotation.handlerClassname();
099            try
100            {
101                return getStrategyHandler( classname );
102            }
103            catch( UnknownServiceException e )
104            {
105                String urn = annotation.handlerURI();
106                URI uri = new URI( urn );
107                return getStrategyHandler( uri );
108            }
109        }
110        
111       /**
112        * Return the strategy handler supporting the supplied class.  If the class 
113        * contains the component annotation the handler is resolved relative to the 
114        * annotation properties, otherwise a default strategy handler is returned.
115        *
116        * @param subject the subject class
117        * @return the strategy handler
118        * @exception Exception if a general loading error occurs
119        */
120        public static StrategyHandler getStrategyHandler( Class<?> subject ) throws Exception
121        {   
122            if( subject.isAnnotationPresent( Component.class ) )
123            {
124                Component annotation = 
125                  subject.getAnnotation( Component.class );
126                return PartContentHandler.getStrategyHandler( annotation );
127            }
128            else
129            {
130                return STANDARD_STRATEGY_HANDLER;
131            }
132        }
133        
134       /**
135        * Load a potentially foreign strategy handler.
136        * Strategy resolution is based on the following rules relative to the namespace
137        * of the supplied element:
138        * <ul>
139        *  <li>if a system property named <tt>handler:[namespace]</tt> is defined, 
140        *     it is assumed to be a handler classname and the implementation will 
141        *     load the class and instantiate the handler instance.</li>
142        *  <li>if the namespace is recognized as a standard strategy the appropriate 
143        *     strategy handler is returned</li>
144        *  <li>the implementation will evaluate the <tt<handler</tt> attribute on the
145        *     supplied element - if the attribute value is not null, then 
146        *     the values will interprited as either a classname or a uri.  If the 
147        *     value contains the ':' character the value will be interprited as a 
148        *     uri to a part definition from which a strategy handler can be established, 
149        *     otherwise, the implementation assumes the the value is a strategy handler
150        *     classname resolvable form the current context classloader</li>
151        * </ul>
152        *
153        * @param element the strategy element
154        * @return the strategy handler
155        * @exception Exception if loading error occurs
156        */
157        public static StrategyHandler getStrategyHandler( Element element ) throws Exception
158        {
159            TypeInfo info = element.getSchemaTypeInfo();
160            String namespace = info.getTypeNamespace();
161            String override = System.getProperty( "handler:" + namespace );
162            if( null != override )
163            {
164                return getStrategyHandler( override );
165            }
166            if( AntlibStrategyHandler.NAMESPACE.equals( namespace ) )
167            {
168                return new AntlibStrategyHandler();
169            }
170            else if( ComponentStrategyHandler.NAMESPACE.equals( namespace ) )
171            {
172                return new ComponentStrategyHandler();
173            }
174            else
175            {
176                String urn = ElementHelper.getAttribute( element, "handler" );
177                if( null != urn )
178                {
179                    if( urn.indexOf( ":" ) > -1 )
180                    {
181                        URI uri = new URI( urn );
182                        return getStrategyHandler( uri );
183                    }
184                    else
185                    {
186                        return getStrategyHandler( urn );
187                    }
188                }
189                else
190                {
191                    try
192                    {
193                        URI uri = getURIFromNamespaceURI( namespace );
194                        return getStrategyHandler( uri );
195                    }
196                    catch( Exception e )
197                    {
198                        final String error = 
199                          "Cannot resolve strategy handler for element.";
200                        throw new DecodingException( error, element );
201                    }
202                }
203            }
204        }
205    
206       /**
207        * Load a strategy handler.  The implementation will attempt to resolve a 
208        * part defintion from the supplied uri, caching a reference to
209        * the handler, and returning the strategy handler instance.
210        *
211        * @param uri the part handler part uri
212        * @exception Exception if part loading error occurs
213        */
214        static StrategyHandler getStrategyHandler( URI uri ) throws Exception
215        {
216            ClassLoader context = Thread.currentThread().getContextClassLoader();
217            Thread.currentThread().setContextClassLoader( ClassLoader.getSystemClassLoader() ); // TODO
218            try
219            {
220                Strategy strategy = Strategy.load( null, null, uri, null );
221                StrategyHandler handler = strategy.getContentForClass( StrategyHandler.class );
222                if( null == handler )
223                {
224                    final String error = 
225                      "URI does not resolve to a strategy handler ["
226                      + uri
227                      + "]";
228                    throw new ServiceError( error );
229                }
230                else
231                {
232                    return handler;
233                }
234            }
235            finally
236            {
237                Thread.currentThread().setContextClassLoader( context );
238            }
239        }
240        
241       /**
242        * Load a strategy handler.
243        *
244        * @param classname the strategy handler service implementation class
245        * @exception Exception if part loading error occurs
246        */
247        static StrategyHandler getStrategyHandler( String classname ) throws Exception
248        {
249            ServiceLoader<StrategyHandler> handlers = ServiceLoader.load( StrategyHandler.class );
250            for( StrategyHandler handler : handlers )
251            {
252                if( handler.getClass().getName().equals( classname ) )
253                {
254                    return handler;
255                }
256            }
257            throw new UnknownServiceException( classname );
258        }
259        
260        //--------------------------------------------------------------------------------
261        // constructor
262        //--------------------------------------------------------------------------------
263        
264       /**
265        * Creation of a new part content handler.
266        * @exception Exception if an error occurs
267        */
268        public PartContentHandler() throws Exception
269        {
270            String flag = System.getProperty( "dpml.jmx.enabled", "false" );
271            if( "true".equals( flag ) )
272            {
273                try
274                {
275                    MBeanServer server = ManagementFactory.getPlatformMBeanServer();
276                    Hashtable<String,String> table = new Hashtable<String,String>();
277                    table.put( "type", "Parts" );
278                    ObjectName name =
279                      ObjectName.getInstance( "net.dpml.transit", table );
280                    server.registerMBean( this, name );
281                }
282                catch( InstanceAlreadyExistsException e )
283                {
284                    //e.printStackTrace();
285                }
286            }
287        }
288        
289        //--------------------------------------------------------------------------------
290        // ContentHandler
291        //--------------------------------------------------------------------------------
292    
293       /**
294        * Returns the type tha the content handler supports.
295        * @return the content type name
296        */
297        public String getType()
298        {
299            return PART_CONTENT_TYPE;
300        }
301        
302       /**
303        * Returns the content in the form of a {@link net.dpml.lang.Strategy} datatype.
304        * @param connection the url connection
305        * @return the part datatype
306        * @exception IOException if an IO error occurs
307        */
308        public Object getContent( URLConnection connection ) throws IOException
309        {
310            Part part = getPartContent( connection );
311            return part.getStrategy();
312        }
313        
314       /**
315        * Returns the content assignable to the first recognized class in the list
316        * os supppied classes.  If the class array is empty the part datatype is returned.
317        * If none of the classes are recognized, null is returned.
318        * @param connection the url connection
319        * @param classes the selection class array
320        * @return the resolved instance
321        * @exception IOException if an IO error occurs
322        */
323        public Object getContent( URLConnection connection, Class[] classes ) throws IOException
324        {
325            Part part = getPartContent( connection );
326            return getContentForClasses( part, classes );
327        }
328        
329        //--------------------------------------------------------------------------------
330        // ApplianceFactory
331        //--------------------------------------------------------------------------------
332        
333       /**
334        * Create a new appliance using the supplied connection object.
335        * @param connection the URL connection
336        * @param partition an optional partition name
337        * @return the appliance
338        * @exception IOException if an IO error occurs
339        */
340        public Appliance newAppliance( URLConnection connection, String partition ) throws IOException
341        {
342            Part part = getPartContent( connection, partition );
343            return getContentForClass( part, Appliance.class );
344        }
345        
346        //--------------------------------------------------------------------------------
347        // ContentManager
348        //--------------------------------------------------------------------------------
349        
350       /**
351        * Return the part managers handled by the content handler.
352        * @return the part manager array
353        */
354        public PartManager[] getPartManagers()
355        {
356            ArrayList<PartManager> list = new ArrayList<PartManager>();
357            Part[] parts = CACHE.values().toArray( new Part[0] );
358            for( Part part : parts )
359            {
360                if( part instanceof PartManager )
361                {
362                    PartManager manager = (PartManager) part;
363                    list.add( manager );
364                }
365            }
366            return list.toArray( new PartManager[0] );
367        }
368        
369        //--------------------------------------------------------------------------------
370        // internals
371        //--------------------------------------------------------------------------------
372        
373        private Logger getLogger()
374        {
375            return LOGGER;
376        }
377        
378       /**
379        * Return a resolved value given a supplied part instance and class.
380        * @param part the part to resolve against
381        * @param c the return type
382        * @return the resolved value
383        * @exception IOException if an error occurs
384        */
385        private <T>T getContentForClass( Part part, Class<T> c ) throws IOException
386        {
387            if( Part.class == c )
388            {
389                return c.cast( part );
390            }
391            Strategy strategy = part.getStrategy();
392            return strategy.getContentForClass( c );
393        }
394        
395        private Object getContentForClasses( Part part, Class[] classes ) throws IOException
396        {
397            Strategy strategy = part.getStrategy();
398            for( Class<?> c : classes )
399            {
400                Object content = strategy.getContentForClass( c );
401                if( null != content )
402                {
403                    return content;
404                }
405            }
406            return null;
407        }
408        
409        static Part getPartContent( URLConnection connection ) throws IOException
410        {
411            return getPartContent( connection, null, true );
412        }
413        
414        static Part getPartContent( URLConnection connection, String name ) throws IOException
415        {
416            return getPartContent( connection, name, true );
417        }
418        
419        static Part getPartContent( URLConnection connection, String name, boolean cache ) throws IOException
420        {
421            return getPartContent( null, connection, name, cache );
422        }
423        
424        static Part getPartContent( ClassLoader anchor, URLConnection connection, String name, boolean cache ) throws IOException
425        {
426            ClassLoader classloader = getAnchorClassLoader( anchor );
427            URL url = connection.getURL();
428            try
429            {
430                String key = buildKey( classloader, url );
431                if( cache )
432                {
433                    //WeakReference ref = CACHE.get( key );
434                    //if( null != ref )
435                    //{
436                    //    Part part = (Part) ref.get();
437                        //if( null != part )
438                        //{
439                        //    if( getLogger().isTraceEnabled() )
440                        //    {
441                        //        getLogger().trace( "located cached part: " + url );
442                        //    }
443                        //    return part;
444                        //}
445                        //else
446                        //{
447                        //    if( getLogger().isTraceEnabled() )
448                        //    {
449                        //        getLogger().trace( "located disgarded ref: " + key );
450                        //    }
451                        //}
452                    //}
453                    
454                    Part part = CACHE.get( key );
455                    if( null != part )
456                    {
457                        return part;
458                    }
459                    else
460                    {
461                        // otherwise we need to build it
462                        
463                        part = buildPart( classloader, connection, name, true );
464                        //if( part instanceof PartManager )
465                        //{
466                        //    // register it with the mbean server
467                        //}
468                        
469                        //WeakReference<Part> reference = new WeakReference<Part>( part );
470                        //CACHE.put( key, reference );
471                        CACHE.put( key, part );
472                        if( LOGGER.isTraceEnabled() )
473                        {
474                            LOGGER.trace( "caching part" 
475                              + "\n  url: " + url
476                              + "\n  key: " + key ); 
477                        }
478                        return part;
479                    }
480                }
481                else
482                {
483                    if( LOGGER.isTraceEnabled() )
484                    {
485                        LOGGER.trace( "building new part: " + url );
486                    }
487                    return buildPart( classloader, connection, name, true );
488                }
489            }
490            catch( IOException e )
491            {
492                throw e;
493            }
494            catch( NoClassDefFoundError e )
495            {
496                LOGGER.error( 
497                  e.toString() 
498                  + "\n" 
499                  + classloader.toString() );
500                throw e;
501            }
502            catch( Exception e )
503            {
504                final String error = "Unexpected error in part handling: " + url;
505                IOException ioe = new IOException();
506                ioe.initCause( e );
507                throw ioe;
508            }
509        }
510        
511        private static Part buildPart( 
512          ClassLoader anchor, URLConnection connection, String name, boolean validate ) throws Exception
513        {
514            URL url = connection.getURL();
515            if( LOGGER.isTraceEnabled() )
516            {
517                LOGGER.trace( 
518                  "building part ["
519                  + url
520                  + "] with anchor ["
521                  + System.identityHashCode( anchor )
522                  + "]" );
523            }
524            
525            Document document = DOCUMENT_BUILDER.parse( connection );
526            final Element element = document.getDocumentElement();
527            TypeInfo type = element.getSchemaTypeInfo();
528            String namespace = type.getTypeNamespace();
529            if( isNamespaceRecognized( namespace ) )
530            {
531                URI uri = url.toURI();
532                Resolver resolver = new SimpleResolver();
533                Info info = getInfo( uri, element );
534                Classpath classpath = getClasspath( element );
535                ClassLoader classloader = ClassLoaderHelper.newClassLoader( anchor, uri, classpath );
536                ClassLoader context = Thread.currentThread().getContextClassLoader();
537                Thread.currentThread().setContextClassLoader( classloader );
538                try
539                {
540                    Element elem = getStrategyElement( element );
541                    StrategyHandler handler = getStrategyHandler( elem );
542                    String query = url.getQuery();
543                    Strategy strategy = handler.build( classloader, elem, resolver, name, query, validate );
544                    if( LOGGER.isTraceEnabled() )
545                    {
546                        LOGGER.trace( 
547                          "establised new part using [" 
548                          + strategy.getClass().getName() 
549                          + "]" );
550                    }
551                    return new Part( info, classpath, strategy );
552                }
553                finally
554                {
555                    Thread.currentThread().setContextClassLoader( context );
556                }
557            }
558            else
559            {
560                final String error = 
561                  "Part namespace not recognized."
562                  + "\nFound: " + namespace
563                  + "\nExpecting: " + PartContentHandler.NAMESPACE;
564                throw new DecodingException( error, element );
565            }
566        }
567        
568        private static boolean isNamespaceRecognized( String namespace )
569        {
570            return NAMESPACE.equals( namespace );
571        }
572    
573        private static Info getInfo( URI uri, Element root )
574        {
575            Element element = ElementHelper.getChild( root, "info" );
576            if( null == element )
577            {
578                return new Info( uri, null, null );
579            }
580            String title = ElementHelper.getAttribute( element, "title" );
581            Element descriptionElement = ElementHelper.getChild( element, "description" );
582            String description = ElementHelper.getValue( descriptionElement );
583            return new Info( uri, title, description );
584        }
585    
586       /**
587        * Construct the classpath defintion.
588        * @param root the element containing a 'classpath' element.
589        * @return the classpath definition
590        * @exception DecodingException if an error occurs during element evaluation
591        */
592        private static Classpath getClasspath( Element root ) throws DecodingException
593        {
594            // TODO: update to support different classpath defintions (e.g. 277 module scenario)
595            
596            Element classpath = ElementHelper.getChild( root, "classpath" );
597            if( null == classpath )
598            {
599                return new Classpath();
600            }
601            
602            try
603            {
604                Element[] children = ElementHelper.getChildren( classpath );
605                URI[] sys = buildURIs( classpath, "system" );
606                URI[] pub = buildURIs( classpath, "public" );
607                URI[] prot = buildURIs( classpath, "protected" );
608                URI[] priv = buildURIs( classpath, "private" );
609                return new Classpath( sys, pub, prot, priv );
610            }
611            catch( Throwable e )
612            {
613                final String error = 
614                  "Unable to decode classpath due to an unexpected error.";
615                throw new DecodingException( error, e, classpath );
616            }
617        }
618        
619        private static URI[] buildURIs( Element classpath, String key ) throws Exception
620        {
621            Element category = ElementHelper.getChild( classpath, key );
622            Element[] children = ElementHelper.getChildren( category, "uri" );
623            URI[] uris = new URI[ children.length ];
624            for( int i=0; i<children.length; i++ )
625            {
626                Element child = children[i];
627                String value = ElementHelper.getValue( child );
628                uris[i] = new URI( value );
629            }
630            return uris;
631        }
632        
633        // cache utils
634        
635        private static String buildKey( ClassLoader classloader, URL url ) throws IOException
636        {
637            try
638            {
639                int n = System.identityHashCode( classloader );
640                return "" + n + "#" + url.toURI().toASCIIString();
641            }
642            catch( Exception e )
643            {
644                final String error = "Internal error while resolving part key from url: " + url;
645                IOException ioe = new IOException();
646                ioe.initCause( e );
647                throw ioe;
648            }
649        }
650    
651        static ClassLoader getAnchorClassLoader( ClassLoader parent )
652        {
653            if( null != parent )
654            {
655                return parent;
656            }
657            else
658            {
659                return Strategy.class.getClassLoader();
660                //ClassLoader classloader = Thread.currentThread().getContextClassLoader();
661                //if( null == classloader )
662                //{
663                //    return getClass().getClassLoader();
664                //}
665                //else
666                //{
667                //    return classloader;
668                //}
669            }
670        }
671    
672        private static Element getStrategyElement( Element root ) throws DecodingException
673        {
674            // TODO: update this to select the strategy element based on type overwise
675            // we risk issues when dealing with a non-standard classpath element
676                    
677            Element[] children = ElementHelper.getChildren( root );
678            for( Element element : children )
679            {
680                String name = element.getLocalName();
681                if( !name.equals( "info" ) && !name.equals( "classpath" ) )
682                {
683                    return element;
684                }
685            }
686            final String error = 
687              "Missing part strategy element.";
688            throw new DecodingException( error, root );
689        }
690    
691       /**
692        * Resolve the part handler given an element namespace.
693        * @param urn the namespace value
694        * @return the decoder uri
695        * @exception Exception if an error occurs
696        */
697        private static URI getURIFromNamespaceURI( String urn ) throws Exception
698        {
699            URI raw = new URI( urn );
700            Artifact artifact = Artifact.createArtifact( raw );
701            String group = artifact.getGroup();
702            String name = artifact.getName();
703            String path = "link:part:" + group + "/" + name;
704            Artifact link = Artifact.createArtifact( path );
705            return link.toURI();
706        }
707    }