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 }