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
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.lang;
020
021 import java.io.File;
022 import java.io.IOException;
023 import java.net.URI;
024 import java.net.URL;
025 import java.util.Map;
026 import java.util.Hashtable;
027 import java.lang.ref.WeakReference;
028
029 import net.dpml.transit.link.ArtifactLinkManager;
030
031 import net.dpml.util.Logger;
032 import net.dpml.util.DefaultLogger;
033 import net.dpml.util.ElementHelper;
034 import net.dpml.util.DOM3DocumentBuilder;
035 import net.dpml.util.Decoder;
036 import net.dpml.util.DecoderFactory;
037 import net.dpml.util.DecodingException;
038 import net.dpml.util.Resolver;
039 import net.dpml.util.SimpleResolver;
040
041 import org.w3c.dom.Document;
042 import org.w3c.dom.Element;
043 import org.w3c.dom.TypeInfo;
044
045 /**
046 * Construct a part.
047 */
048 public final class PartDecoder implements Decoder
049 {
050 /**
051 * Part XSD uri.
052 */
053 public static final String PART_XSD_URI = "link:xsd:dpml/lang/dpml-part#1.0";
054
055 private static final DOM3DocumentBuilder DOCUMENT_BUILDER =
056 new DOM3DocumentBuilder();
057
058 private static final ValueDecoder VALUE_DECODER = new ValueDecoder();
059
060 private static final PartDecoder DECODER = new PartDecoder();
061
062 private static final String BASEPATH = setupBasePathSpec();
063
064 /**
065 * Get the singleton instance of the part decoder.
066 * @return the decoder instance.
067 */
068 public static PartDecoder getInstance()
069 {
070 return DECODER;
071 }
072
073 private Map m_map = new Hashtable();
074 private Map m_builders = new Hashtable();
075
076 private Logger m_logger;
077
078 private PartDecoder()
079 {
080 m_logger = new DefaultLogger( "dpml.lang" );
081 }
082
083 /**
084 * Load a part from a uri.
085 * @param uri the part uri
086 * @param cache if true parts are cached relative to the requested uri
087 * @return the part definition
088 * @exception IOException if an IO error occurs
089 */
090 public Part loadPart( URI uri, boolean cache ) throws IOException
091 {
092 if( null == uri )
093 {
094 throw new NullPointerException( "uri" );
095 }
096 if( getLogger().isDebugEnabled() )
097 {
098 String path = getPartSpec( uri );
099 if( getLogger().isTraceEnabled() )
100 {
101 if( cache )
102 {
103 getLogger().trace( "loading part (cache enabled): " + path );
104 }
105 else
106 {
107 getLogger().trace( "loading part (cache disabled): " + path );
108 }
109 }
110 else
111 {
112 getLogger().debug( "loading part: " + path );
113 }
114 }
115 String key = buildKey( uri );
116 if( cache )
117 {
118 WeakReference ref = (WeakReference) m_map.get( key );
119 if( null != ref )
120 {
121 Part part = (Part) ref.get();
122 if( null != part )
123 {
124 if( getLogger().isDebugEnabled() )
125 {
126 getLogger().debug( "located part in cache" );
127 }
128 return part;
129 }
130 }
131 }
132
133 // cache based retrieval was disabled or no cache value present
134
135 try
136 {
137 final Document document = DOCUMENT_BUILDER.parse( uri );
138 final Element root = document.getDocumentElement();
139 Resolver resolver = new SimpleResolver();
140 Part value = decodePart( uri, root, resolver );
141 if( cache )
142 {
143 WeakReference reference = new WeakReference( value );
144 m_map.put( key, reference );
145 if( getLogger().isTraceEnabled() )
146 {
147 getLogger().trace( "caching part"
148 + "\n uri: " + uri
149 + "\n key: " + key );
150 }
151 }
152 return value;
153 }
154 catch( Throwable e )
155 {
156 final String error =
157 "An error while attempting to load a part."
158 + "\n uri: " + uri;
159 IOException exception = new IOException( error );
160 exception.initCause( e );
161 throw exception;
162 }
163 }
164
165 private String buildKey( URI uri ) throws IOException
166 {
167 ClassLoader classloader = getAnchorClassLoader();
168 int n = System.identityHashCode( classloader );
169 return "" + n + "#" + getRealURI( uri ).toASCIIString();
170 }
171
172 private String getID()
173 {
174 ClassLoader classloader = getAnchorClassLoader();
175 int n = System.identityHashCode( classloader );
176 return "" + n;
177 }
178
179 private URI getRealURI( URI uri ) throws IOException
180 {
181 if( "link".equals( uri.getScheme() ) )
182 {
183 ArtifactLinkManager manager = new ArtifactLinkManager();
184 return manager.getTargetURI( uri );
185 }
186 else
187 {
188 return uri;
189 }
190 }
191
192 /**
193 * Resolve a object from a DOM element.
194 * @param element the dom element
195 * @param resolver build-time value resolver
196 * @return the resolved object
197 * @exception IOException if an error occurs during element evaluation
198 */
199 public Object decode( Element element, Resolver resolver ) throws IOException
200 {
201 return decodePart( null, element, resolver );
202 }
203
204 /**
205 * Resolve a part from a DOM element.
206 * @param uri the part uri
207 * @param element element part definition
208 * @param resolver build-time value resolver
209 * @return the resolved part datastructure
210 * @exception IOException if an error occurs during element evaluation
211 */
212 public Part decodePart( URI uri, Element element, Resolver resolver ) throws IOException
213 {
214 TypeInfo info = element.getSchemaTypeInfo();
215 String namespace = info.getTypeNamespace();
216 if( PART_XSD_URI.equals( namespace ) )
217 {
218 boolean alias = ElementHelper.getBooleanAttribute( element, "alias", false );
219 Info information = getInfo( uri, element );
220 Classpath classpath = getClasspath( element );
221 Element strategy = getStrategyElement( element );
222 return build( m_logger, information, classpath, strategy, resolver );
223 }
224 else
225 {
226 final String error =
227 "Part namespace not recognized."
228 + "\nExpecting: " + PART_XSD_URI
229 + "\nFound: " + namespace;
230 throw new DecodingException( element, error );
231 }
232 }
233
234 /**
235 * Resolve a part plugin or resource strategy.
236 * @param logger the logging channel
237 * @param information the part info definition
238 * @param classpath the part classpath definition
239 * @param strategy part deployment strategy definition
240 * @param resolver build-time value resolver
241 * @return the resolved part
242 * @exception IOException if an error occurs during element evaluation
243 */
244 public Part build(
245 Logger logger, Info information, Classpath classpath, Element strategy, Resolver resolver )
246 throws IOException
247 {
248 ClassLoader anchor = getAnchorClassLoader();
249 TypeInfo info = strategy.getSchemaTypeInfo();
250 String namespace = info.getTypeNamespace();
251 if( PART_XSD_URI.equals( namespace ) )
252 {
253 // this is either a plugin or a resource
254
255 String name = info.getTypeName();
256 if( "plugin".equals( name ) )
257 {
258 if( logger.isTraceEnabled() )
259 {
260 logger.trace( "reading plugin definition" );
261 }
262 String classname = ElementHelper.getAttribute( strategy, "class" );
263 Element[] elements = ElementHelper.getChildren( strategy );
264 Value[] values = VALUE_DECODER.decodeValues( elements );
265 Part part = new Plugin( logger, information, classpath, classname, values );
266 if( logger.isTraceEnabled() )
267 {
268 logger.trace( "loaded plugin definition" );
269 }
270 return part;
271 }
272 else if( "resource".equals( name ) )
273 {
274 if( logger.isTraceEnabled() )
275 {
276 logger.trace( "reading resource definition" );
277 }
278 String urn = ElementHelper.getAttribute( strategy, "urn" );
279 String path = ElementHelper.getAttribute( strategy, "path" );
280 Part part = new Resource( logger, information, classpath, urn, path );
281 if( logger.isTraceEnabled() )
282 {
283 logger.trace( "loaded resource definition" );
284 }
285 return part;
286 }
287 else
288 {
289 final String error =
290 "Element type name ["
291 + name
292 + "] is not a recognized element type within the "
293 + PART_XSD_URI
294 + " namespace.";
295 throw new DecodingException( strategy, error );
296 }
297 }
298 else
299 {
300 // this is a foreign part
301
302 try
303 {
304 URI uri = getDecoderURI( strategy );
305 Builder builder = loadForeignBuilder( uri );
306 if( logger.isTraceEnabled() )
307 {
308 logger.trace(
309 "using builder ["
310 + builder.getClass().getName()
311 + "]" );
312 }
313 Part part = builder.build( logger, information, classpath, strategy, resolver );
314 if( logger.isTraceEnabled() )
315 {
316 logger.trace(
317 "loaded part ["
318 + part.getClass().getName()
319 + "]" );
320 }
321 return part;
322 }
323 catch( Exception ioe )
324 {
325 final String error =
326 "Internal error while attempting to load foreign part.";
327 throw new DecodingException( strategy, error, ioe );
328 }
329 finally
330 {
331 Thread.currentThread().setContextClassLoader( anchor );
332 }
333 }
334 }
335
336 /**
337 * Resolve the element decoder uri.
338 *
339 * @param element the DOM element
340 * @return the decoder uri
341 * @exception DecodingException if an error occurs
342 */
343 public URI getDecoderURI( Element element ) throws DecodingException
344 {
345 String uri = ElementHelper.getAttribute( element, "handler" );
346 if( null != uri )
347 {
348 try
349 {
350 return new URI( uri );
351 }
352 catch( Exception e )
353 {
354 final String error =
355 "Internal error while resolving handler attribute (expecting uri value)";
356 throw new DecodingException( element, error, e );
357 }
358 }
359 TypeInfo info = element.getSchemaTypeInfo();
360 String namespace = info.getTypeNamespace();
361 try
362 {
363 return DecoderFactory.getDecoderURIFromNamespaceURI( namespace );
364 }
365 catch( Exception e )
366 {
367 final String error =
368 "Internal error while attempting to resolve default decoder uri.";
369 throw new DecodingException( element, error, e );
370 }
371 }
372
373 /**
374 * Get the assigned logging channel.
375 * @return the logging channel
376 */
377 protected Logger getLogger()
378 {
379 return m_logger;
380 }
381
382 /**
383 * Load a forign part builder. The implementation will attempt to resolve a
384 * plugin defintion from the supplied uri, caching a reference to
385 * the builder, and returning the plugin instance as a builder instance.
386 *
387 * @param uri the part builder uri
388 * @exception DecodingException if a part decoding error occurs
389 * @exception Exception if part loading error occurs
390 */
391 private Builder loadForeignBuilder( URI uri ) throws DecodingException, Exception
392 {
393 WeakReference ref = (WeakReference) m_builders.get( uri );
394 if( null != ref )
395 {
396 Builder builder = (Builder) ref.get();
397 if( null != builder )
398 {
399 if( getLogger().isTraceEnabled() )
400 {
401 getLogger().trace( "located builder [" + uri + "]" );
402 }
403 return builder;
404 }
405 else
406 {
407 if( getLogger().isTraceEnabled() )
408 {
409 getLogger().trace( "reloading builder [" + uri + "]" );
410 }
411 }
412 }
413 else
414 {
415 if( getLogger().isTraceEnabled() )
416 {
417 getLogger().trace( "loading builder [" + uri + "]" );
418 }
419 }
420
421 Part part = loadPart( uri, true );
422 Logger logger = getLogger();
423 Object[] args = new Object[]{logger};
424 Object object = part.instantiate( args );
425 if( Builder.class.isAssignableFrom( object.getClass() ) )
426 {
427 Builder builder = (Builder) object;
428 WeakReference reference = new WeakReference( builder );
429 m_builders.put( uri, reference );
430 return builder;
431 }
432 else
433 {
434 String stack =
435 StandardClassLoader.toString(
436 getClass().getClassLoader(),
437 object.getClass().getClassLoader() );
438 String classname = object.getClass().getName();
439 final String error =
440 "Plugin instance is not assignable to the "
441 + Builder.class.getName()
442 + " interface."
443 + "\n URI: " + uri
444 + "\n Class: " + classname
445 + "\n" + stack;
446 throw new PartException( error );
447 }
448 }
449
450 private Element getStrategyElement( Element root ) throws DecodingException
451 {
452 Element[] children = ElementHelper.getChildren( root );
453 if( children.length != 3 )
454 {
455 final String error =
456 "Illegal number of child elements in <part>. Expecting 3, found "
457 + children.length
458 + ".";
459 throw new DecodingException( root, error );
460 }
461 return children[1];
462 }
463
464
465 private Info getInfo( URI uri, Element root )
466 {
467 Element element = ElementHelper.getChild( root, "info" );
468 String title = ElementHelper.getAttribute( element, "title" );
469 Element descriptionElement = ElementHelper.getChild( element, "description" );
470 String description = ElementHelper.getValue( descriptionElement );
471 return new Info( uri, title, description );
472 }
473
474 /**
475 * Construct the classpath defintion.
476 * @param root the element containing a 'classpath' element.
477 * @return the classpath definition
478 * @exception DecodingException if an error occurs during element evaluation
479 */
480 protected Classpath getClasspath( Element root ) throws DecodingException
481 {
482 Element classpath = ElementHelper.getChild( root, "classpath" );
483 if( null == classpath )
484 {
485 final String error =
486 "Required classpath element is not present in plugin descriptor.";
487 throw new DecodingException( root, error );
488 }
489
490 try
491 {
492 Element[] children = ElementHelper.getChildren( classpath );
493 URI[] sys = buildURIs( classpath, "system" );
494 URI[] pub = buildURIs( classpath, "public" );
495 URI[] prot = buildURIs( classpath, "protected" );
496 URI[] priv = buildURIs( classpath, "private" );
497 Classpath cp = new Classpath( sys, pub, prot, priv );
498 return cp;
499 }
500 catch( Throwable e )
501 {
502 final String error =
503 "Unable to decode classpath due to an unexpected error.";
504 throw new DecodingException( classpath, error, e );
505 }
506 }
507
508 private URI[] buildURIs( Element classpath, String key ) throws Exception
509 {
510 Element category = ElementHelper.getChild( classpath, key );
511 if( null == category )
512 {
513 return new URI[0];
514 }
515 else
516 {
517 Element[] children = ElementHelper.getChildren( category, "uri" );
518 URI[] uris = new URI[ children.length ];
519 for( int i=0; i<children.length; i++ )
520 {
521 Element child = children[i];
522 String value = ElementHelper.getValue( child );
523 uris[i] = new URI( value );
524 }
525 return uris;
526 }
527 }
528
529 private ClassLoader getAnchorClassLoader()
530 {
531 ClassLoader classloader = Thread.currentThread().getContextClassLoader();
532 if( null == classloader )
533 {
534 return Part.class.getClassLoader();
535 }
536 else
537 {
538 return classloader;
539 }
540 }
541
542 private static String setupBasePathSpec()
543 {
544 try
545 {
546 String path = System.getProperty( "user.dir" );
547 File file = new File( path );
548 URI uri = file.toURI();
549 URL url = uri.toURL();
550 return url.toString();
551 }
552 catch( Exception e )
553 {
554 return e.toString();
555 }
556 }
557
558 static String getPartSpec( URI uri )
559 {
560 String path = uri.toASCIIString();
561 if( path.startsWith( BASEPATH ) )
562 {
563 return "./" + path.substring( BASEPATH.length() );
564 }
565 else
566 {
567 return path;
568 }
569 }
570 }