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.IOException;
022    import java.io.OutputStream;
023    import java.io.OutputStreamWriter;
024    import java.io.Writer;
025    import java.net.URI;
026    
027    import javax.xml.XMLConstants;
028    
029    import net.dpml.util.Logger;
030    
031    /**
032     * Part datastructure.
033     *
034     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
035     * @version 1.0.3
036     */
037    public abstract class Part
038    {
039       /**
040        * A value encoder.
041        */
042        protected static final ValueEncoder VALUE_ENCODER = new ValueEncoder();
043        
044       /**
045        * Default XML header.
046        */
047        protected static final String XML_HEADER = "<?xml version=\"1.0\"?>";
048    
049       /**
050        * Part schema URN.
051        */
052        protected static final String PART_SCHEMA_URN = "link:xsd:dpml/lang/dpml-part#1.0";
053    
054       /**
055        * Part header.
056        */
057        protected static final String PART_HEADER = 
058          "<part xmlns=\"" 
059          + PART_SCHEMA_URN 
060          + "\""
061          + "\n    xmlns:xsi=\"" 
062          + XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI
063          + "\">";
064          
065       /**
066        * Part footer.
067        */
068        protected static final String PART_FOOTER = "</part>";
069    
070        private final Info m_info;
071        private final Classpath m_classpath;
072        private final Logger m_logger;
073        
074        private transient ClassLoader m_classloader;
075        private transient String m_label;
076        
077       /**
078        * Load a part from an external XML source with part caching.
079        * @param uri the external part source
080        * @return the resolved part
081        * @exception IOException of an I/O error occurs
082        */
083        public static Part load( URI uri ) throws IOException
084        {
085            return load( uri, true );
086        }
087        
088       /**
089        * Load a part from an external XML source.
090        * @param uri the external part source
091        * @param cache the cache policy
092        * @return the resolved part
093        * @exception IOException of an I/O error occurs
094        */
095        public static Part load( URI uri, boolean cache ) throws IOException
096        {
097            return PartDecoder.getInstance().loadPart( uri, cache );
098        }
099        
100       /**
101        * Creation of a new part datastructure.
102        * @param logger the logging channel
103        * @param info the info descriptor
104        * @param classpath the part classpath definition
105        * @exception IOException if an I/O error occurs
106        */
107        public Part( Logger logger, Info info, Classpath classpath ) throws IOException
108        {
109            this( logger, info, classpath, null );
110        }
111        
112       /**
113        * Creation of a new part datastructure.
114        * @param logger the logging channel
115        * @param info the info descriptor
116        * @param classpath the part classpath definition
117        * @param label debug label
118        * @exception IOException if an I/O error occurs
119        */
120        public Part( Logger logger, Info info, Classpath classpath, String label ) throws IOException
121        {
122            super();
123            if( null == info )
124            {
125                throw new NullPointerException( "info" );
126            }
127            if( null == classpath )
128            {
129                throw new NullPointerException( "classpath" );
130            }
131            m_logger = logger;
132            m_info = info;
133            m_classpath = classpath;
134            m_label = label;
135        }
136        
137       /**
138        * Return the default part content. 
139        * @return the result of part instantiation
140        * @exception IOException if an IO error occurs
141        */
142        public Object getContent() throws IOException
143        {
144            try
145            {
146                return instantiate( new Object[0] );
147            }
148            catch( IOException e )
149            {
150                throw e;
151            }
152            catch( Exception e )
153            {
154                final String error = 
155                  "Part instantiation error.";
156                throw new PartException( error, e );
157            }
158        }
159        
160       /**
161        * Return the part content or null if the result type is unresolvable 
162        * relative to the supplied classes argument. 
163        * @param classes the content type selection classes
164        * @return the content
165        * @exception IOException if an IO error occurs
166        */
167        public Object getContent( Class[] classes ) throws IOException
168        {
169            if( classes.length == 0 )
170            {
171                return getContent();
172            }
173            else
174            {
175                for( int i=0; i<classes.length; i++ )
176                {
177                    Class c = classes[i];
178                    Object content = getContent( c );
179                    if( null != content )
180                    {
181                        return content;
182                    }
183                }
184                return null;
185            }
186        }
187        
188       /**
189        * Return the part content or null if the result type is unresolvable 
190        * relative to the supplied classes argument. Recognized class arguments
191        * include Info, Classpath, Part, ClassLoader, and Object.
192        *
193        * @param c the content type class
194        * @return the content
195        * @exception IOException if an IO error occurs
196        */
197        protected Object getContent( Class c ) throws IOException
198        {
199            if( Info.class.isAssignableFrom( c ) )
200            {
201                return getInfo();
202            }
203            else if( Classpath.class.isAssignableFrom( c ) )
204            {
205                return getClasspath();
206            }
207            else if( Part.class.isAssignableFrom( c ) )
208            {
209                return this;
210            }
211            else if( ClassLoader.class.isAssignableFrom( c ) )
212            {
213                return getClassLoader();
214            }
215            else if( Object.class == c )
216            {
217                try
218                {
219                    return instantiate( new Object[0] );
220                }
221                catch( IOException e )
222                {
223                    throw e;
224                }
225                catch( Exception e )
226                {
227                    final String error = 
228                      "Part instantiation error.";
229                    throw new PartException( error, e );
230                }
231            }
232            else
233            {
234                return null;
235            }
236        }
237        
238       /**
239        * Get the part info descriptor.
240        *
241        * @return the part info datastructure
242        */
243        public Info getInfo()
244        {
245            return m_info;
246        }
247        
248       /**
249        * Get the part classpath definition.
250        *
251        * @return the classpath definition
252        */
253        public Classpath getClasspath()
254        {
255            return m_classpath;
256        }
257    
258       /**
259        * Instantiate a value.
260        * @param args supplimentary arguments
261        * @return the resolved instance
262        * @exception Exception if a deployment error occurs
263        */
264        public abstract Object instantiate( Object[] args ) throws Exception;
265       
266       /**
267        * Externalize the part to XML.
268        * @param output the output stream
269        * @exception IOException if an I/O error occurs
270        */
271        public void encode( OutputStream output ) throws IOException
272        {
273            final Writer writer = new OutputStreamWriter( output );
274            writer.write( XML_HEADER );
275            writer.write( "\n" );
276            writer.write( "\n" + PART_HEADER );
277            writer.write( "\n" );
278            encodeInfo( writer, getInfo() );
279            writer.write( "\n" );
280            encodeStrategy( writer, "  " );
281            writer.write( "\n" );
282            encodeClasspath( writer, getClasspath() );
283            writer.write( "\n" );
284            writer.write( "\n" + PART_FOOTER );
285            writer.write( "\n" );
286            writer.flush();
287            output.close();
288        }
289        
290       /**
291        * Test is this part is equiovalent to the supplied part.
292        *
293        * @param other the other object
294        * @return true if the parts are equivalent
295        */
296        public boolean equals( Object other )
297        {
298            if( other instanceof Part )
299            {
300                Part part = (Part) other;
301                if( !m_info.equals( part.getInfo() ) )
302                {
303                    return false;
304                }
305                else
306                {
307                    return m_classpath.equals( part.getClasspath() );
308                }
309            }
310            else
311            {
312                return false;
313            }
314        }
315        
316       /**
317        * Get the part hashcode.
318        *
319        * @return the hash value
320        */
321        public int hashCode()
322        {
323            int hash = m_info.hashCode();
324            hash ^= m_classpath.hashCode();
325            return hash;
326        }
327        
328       /**
329        * Encode this part strategy to XML.
330        *
331        * @param writer the output stream writer 
332        * @param pad the character offset 
333        * @exception IOException if an I/O error occurs during part externalization
334        */
335        protected abstract void encodeStrategy( Writer writer, String pad ) throws IOException;
336    
337       /**
338        * Get the implementation classloader.
339        * @return the resolved classloader
340        */
341        public ClassLoader getClassLoader()
342        {
343            if( null == m_classloader )
344            {
345                m_classloader = setupClassLoader();
346            }
347            return m_classloader;
348        }
349    
350       /**
351        * Get the assigned logging channel.
352        * @return the logging channel
353        */
354        protected Logger getLogger()
355        {
356            return m_logger;
357        }
358        
359        private ClassLoader setupClassLoader()
360        {
361            try
362            {
363                return buildClassLoader( m_label );
364            }
365            catch( Exception e )
366            {
367                final String error = 
368                  "Classloader build error.";
369                throw new PartError( error, e );
370            }
371        }
372        
373        private ClassLoader buildClassLoader( String label ) throws IOException
374        {
375            ClassLoader base = getAnchorClassLoader();
376            Classpath classpath = getClasspath();
377            String tag = getLabel( label );
378            return newClassLoader( base, classpath, tag );
379        }
380        
381        private String getLabel( String label )
382        {
383            if( null != label )
384            {
385                return label;
386            }
387            if( null != getInfo().getTitle() )
388            {
389                return getInfo().getTitle();
390            }
391            else
392            {
393                return PartDecoder.getPartSpec( getInfo().getURI() );
394            }
395        }
396        
397        private ClassLoader newClassLoader( ClassLoader base, Classpath classpath, String label ) throws IOException
398        {
399            return newClassLoader( base, classpath, label, true );
400        }
401        
402        private ClassLoader newClassLoader( ClassLoader base, Classpath classpath, String label, boolean expand ) throws IOException
403        {
404            Logger logger = getLogger();
405            
406            if( expand )
407            {
408                Classpath cp = classpath.getBaseClasspath();
409                if( null != cp )
410                {
411                    ClassLoader cl = newClassLoader( base, cp, label + " (super)" );
412                    return newClassLoader( cl, classpath, label, false );
413                }
414            }
415            
416            URI[] uris = classpath.getDependencies( Category.SYSTEM );
417            if( uris.length > 0 )
418            {
419                updateSystemClassLoader( uris );
420            }
421            
422            URI[] apis = classpath.getDependencies( Category.PUBLIC );
423            ClassLoader api = StandardClassLoader.buildClassLoader( logger, label, Category.PUBLIC, base, apis );
424            URI[] spis = classpath.getDependencies( Category.PROTECTED );
425            ClassLoader spi = StandardClassLoader.buildClassLoader( logger, label, Category.PROTECTED, api, spis );
426            URI[] imps = classpath.getDependencies( Category.PRIVATE );
427            return StandardClassLoader.buildClassLoader( logger, label, Category.PRIVATE, spi, imps );
428        }
429        
430        private ClassLoader getAnchorClassLoader()
431        {
432            ClassLoader context = Thread.currentThread().getContextClassLoader();
433            if( null != context )
434            {
435                return context;
436            }
437            else
438            {
439                return Part.class.getClassLoader();
440            }
441        }
442    
443        private void updateSystemClassLoader( URI[] uris ) throws IOException
444        {
445            ClassLoader parent = ClassLoader.getSystemClassLoader();
446            synchronized( parent )
447            {
448                if( parent instanceof SystemClassLoader )
449                {
450                    SystemClassLoader loader = (SystemClassLoader) parent;
451                    loader.addDelegates( uris );
452                    systemExpanded( uris );
453                }
454                else
455                {
456                    final String message =
457                      "Cannot load [" 
458                      + uris.length 
459                      + "] system artifacts into a foreign system classloader.";
460                    getLogger().debug( message );
461                }
462            }
463        }
464        
465       /**
466        * Handle notification of system classloader expansion.
467        * @param uris the array of uris added to the system classloader
468        */
469        private void systemExpanded( URI[] uris )
470        {
471            if( getLogger().isDebugEnabled() )
472            {
473                StringBuffer buffer = new StringBuffer();
474                buffer.append( "system classloader expansion" );
475                for( int i=0; i<uris.length; i++ )
476                {
477                    int n = i+1;
478                    buffer.append( "\n  [" + n + "] \t" + uris[i] );
479                }
480                getLogger().debug( buffer.toString() );
481            }
482        }
483    
484        private void encodeInfo( Writer writer, Info info ) throws IOException
485        {
486            String title = info.getTitle();
487            String description = info.getDescription();
488            if( null == description )
489            {
490                if( null == title )
491                {
492                    writer.write( "\n  <info/>" );
493                }
494                else
495                {
496                    writer.write( "\n  <info title=\"" + title + "\"/>" );
497                }
498            }
499            else
500            {
501                if( null == title )
502                {
503                    writer.write( "\n  <info>" );
504                }
505                else
506                {
507                    writer.write( "\n  <info title=\"" + title + "\">" );
508                }
509                writer.write( "\n    <description>" + description + "</description>" );
510                writer.write( "\n  </info>" );
511            }
512        }
513    
514        private void encodeClasspath( Writer writer, Classpath classpath ) throws IOException
515        {
516            writer.write( "\n  <classpath>" );
517            encodeClasspathCategory( writer, classpath, Category.SYSTEM );
518            encodeClasspathCategory( writer, classpath, Category.PUBLIC );
519            encodeClasspathCategory( writer, classpath, Category.PROTECTED );
520            encodeClasspathCategory( writer, classpath, Category.PRIVATE );
521            writer.write( "\n  </classpath>" );
522        }
523    
524        private void encodeClasspathCategory( 
525          Writer writer, Classpath classpath, Category category ) throws IOException
526        {
527            URI[] uris = classpath.getDependencies( category );
528            if( uris.length > 0 )
529            {
530                String name = category.getName();
531                writer.write( "\n    <" + name + ">" );
532                for( int i=0; i<uris.length; i++ )
533                {
534                    URI uri = uris[i];
535                    writer.write( "\n      <uri>" + uri.toASCIIString() + "</uri>" );
536                }
537                writer.write( "\n    </" + name + ">" );
538            }
539        }
540    }