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