001    /*
002     * Copyright 2004-2005 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.transit.tools;
020    
021    import java.io.InputStream;
022    import java.util.Vector;
023    import java.util.Hashtable;
024    import java.net.URI;
025    
026    import net.dpml.lang.Part;
027    import net.dpml.lang.Plugin;
028    import net.dpml.lang.Resource;
029    
030    import net.dpml.util.ElementHelper;
031    
032    import org.apache.tools.ant.Project;
033    import org.apache.tools.ant.BuildException;
034    import org.apache.tools.ant.ComponentHelper;
035    import org.apache.tools.ant.SubBuildListener;
036    import org.apache.tools.ant.BuildEvent;
037    
038    import org.w3c.dom.Element;
039    
040    /**
041     * A component helper that handles automatic loading of plugins into the
042     * ant plugin based on namespace declarations in the project file.  This is similar
043     * to the 'antlib:' convention except we use the 'plugin:' convention.
044     *
045     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
046     * @version 1.1.0
047     */
048    public class TransitComponentHelper extends ComponentHelper
049        implements SubBuildListener
050    {
051       //------------------------------------------------------------------------
052       // static
053       //------------------------------------------------------------------------
054    
055       /**
056        * The constant Transit ANTLIB namespace.
057        */
058        public static final String TRANSIT_ANTLIB_URN = "antlib:net.dpml.transit";
059    
060       /**
061        * The constant Transit ANTLIB init task namespace.
062        */
063        public static final String TRANSIT_INIT_URN = TRANSIT_ANTLIB_URN + ":init";
064    
065       /**
066        * The constant Transit ANTLIB plugin task namespace.
067        */
068        public static final String TRANSIT_PLUGIN_URN = TRANSIT_ANTLIB_URN + ":plugin";
069    
070       /**
071        * The constant Transit ANTLIB import task namespace.
072        */
073        public static final String TRANSIT_IMPORT_URN = TRANSIT_ANTLIB_URN + ":import";
074    
075       /**
076        * The constant Transit ANTLIB get task namespace.
077        */
078        public static final String TRANSIT_GET_URN = TRANSIT_ANTLIB_URN + ":get";
079    
080       /**
081        * The constant artifact plugin header.
082        */
083        public static final String PLUGIN_ARTIFACT_HEADER = "artifact:part:";
084    
085       /**
086        * Creation of a component helper for the supplied project.
087        *
088        * @param project the project
089        */
090        public static void initialize( Project project )
091        {
092            initialize( project, false );
093        }
094    
095       /**
096        * Creation of a component helper for the supplied project.
097        *
098        * @param project the project
099        * @param flag subproject flag
100        */
101        public static void initialize( Project project, boolean flag )
102        {
103            ComponentHelper current =
104              (ComponentHelper) project.getReference( "ant.ComponentHelper" );
105            if( ( null != current ) && ( current instanceof TransitComponentHelper ) )
106            {
107                return;
108            }
109            TransitComponentHelper helper = new TransitComponentHelper( project, current );
110            helper.initDefaultDefinitions();
111            if( flag )
112            {
113                project.log(
114                  "\nAssigning Transit component helper to sub-project: "
115                  + project.getBaseDir() );
116            }
117            else
118            {
119                project.log(
120                  "\nAssigning Transit component helper to project: "
121                  + project.getBaseDir() );
122            }
123            project.addReference( "ant.ComponentHelper", helper );
124            project.addBuildListener( helper );
125        }
126    
127       /**
128        * Vector of plugin uris already loaded.
129        */
130        private static Vector m_URIS = new Vector();
131    
132       /**
133        * Table of urn to uri mappings.
134        */
135        private static Hashtable m_MAPPINGS = new Hashtable();
136    
137       /**
138        * Register the mapping between a urn and a plugin uri.
139        * @param maps a sequence of urn to uri bindings
140        */
141        public static void register( MapDataType[] maps )
142        {
143            if( null == maps )
144            {
145                throw new NullPointerException( "maps" );
146            }
147    
148            for( int i=0; i < maps.length; i++ )
149            {
150                MapDataType map = maps[i];
151                String urn = map.getURN();
152                if( !m_MAPPINGS.contains( urn ) )
153                {
154                    URI uri = map.getURI();
155                    m_MAPPINGS.put( urn, uri );
156                }
157            }
158        }
159    
160       //------------------------------------------------------------------------
161       // state
162       //------------------------------------------------------------------------
163    
164       /**
165        * The current project.
166        */
167        private Project m_project;
168    
169       /**
170        * The parent component helper.
171        */
172        private ComponentHelper m_parent;
173    
174       //------------------------------------------------------------------------
175       // constructor
176       //------------------------------------------------------------------------
177    
178       /**
179        * Creation of a new transit component helper.
180        * @param project the current project
181        */  
182        public TransitComponentHelper( Project project )
183        {
184            this( project, ComponentHelper.getComponentHelper( project ) );
185        }
186    
187       /**
188        * Creation of a new transit component helper.
189        * @param project the current project
190        * @param parent the parent component helper
191        */  
192        public TransitComponentHelper( Project project, ComponentHelper parent )
193        {
194            setProject( project );
195            m_parent = parent;
196            if( null != parent )
197            {
198                parent.setNext( this );
199            }
200    
201            Hashtable map = getTaskDefinitions();
202            if( null == map.get( TRANSIT_INIT_URN ) )
203            {
204                addTaskDefinition( TRANSIT_INIT_URN, MainTask.class );
205                addTaskDefinition( TRANSIT_PLUGIN_URN, PluginTask.class );
206                addTaskDefinition( TRANSIT_IMPORT_URN, ImportArtifactTask.class );
207                addTaskDefinition( TRANSIT_GET_URN, GetTask.class );
208            }
209        }
210    
211        //------------------------------------------------------------------------
212        // implementation
213        //------------------------------------------------------------------------
214    
215       /**
216        * Set the current project.
217        * @param project the current ant project
218        */
219        public void setProject( Project project )
220        {
221            m_project = project;
222            super.setProject( project );
223        }
224    
225       /**
226        * Create an object for a component using a supplied name. The name
227        * is the fully qualified component name which allows us to intercept
228        * specific namespace qualifiers - in this case 'plugin:'.  In the event of
229        * a plugin namespace we check to see if the plugin for that name is already
230        * loaded and it not we proceed with classic transit-based loading of the
231        * plugin and registration of plugin classes with the component helper.
232        *
233        * @param name the name of the component, if the component is in a namespace, the
234        *   name is prefixed with the namespace uri and ":"
235        * @return the class if found or null if not.
236        */
237        public Object createComponent( String name )
238        {
239            Object object = super.createComponent( name );
240            if( null != object )
241            {
242                return object;
243            }
244    
245            if( null != m_parent )
246            {
247                object = m_parent.createComponent( name );
248                if( null != object )
249                {
250                    return object;
251                }
252            }
253    
254            //
255            // from here we need to validate - code has been worked over more than a
256            // few time ans I would not recommend it for flight control scenarios
257            // just yet
258            //
259    
260            int k = name.lastIndexOf( ":" );
261            if( k > 0 )
262            {
263                String urn = name.substring( 0, k );
264                String task = name.substring( k + 1 );
265                URI uri = convertUrnToURI( urn );
266                if( null != uri )
267                {
268                    installPlugin( uri, urn, task );
269                    object = super.createComponent( name );
270                    if( null != object )
271                    {
272                        return object;
273                    }
274                    else
275                    {
276                        final String error =
277                          "Mapped urn returned a null object.";
278                        throw new BuildException( error );
279                    }
280                }
281                else
282                {
283                    return super.createComponent( name );
284                }
285            }
286            else
287            {
288                return super.createComponent( name );
289            }
290        }
291    
292       /**
293        * Convert a urn to a uri taking into account possible urn alias names.
294        *
295        * @param urn the urn to convert to a uri
296        * @return the converted uri
297        */
298        private URI convertUrnToURI( String urn )
299        {
300            URI uri = (URI) m_MAPPINGS.get( urn );
301            if( null != uri )
302            {
303                return uri;
304            }
305            if( urn.startsWith( PLUGIN_ARTIFACT_HEADER ) )
306            {
307                return convertToURI( urn );
308            }
309            else
310            {
311                return null;
312            }
313        }
314    
315       /**
316        * The implementation will retrieve the plugin descriptor.  If the descriptor
317        * declares a classname then the class will be loaded and assigned under
318        * name.  If the classname is undefined and resource is defined, the
319        * implementation will attempt to locate an antlib definition at the resource
320        * location and will attempt to load all taskdef and typedef entries declared
321        * in the antlib.
322        *
323        * @param uri the plugin uri
324        * @param name the fully qualified component name
325        */
326        private void installPlugin( URI uri, String urn, String name )
327        {
328            final String label = uri + ":" + name;
329            if( null != getTaskDefinitions().get( label ) )
330            {
331                return;
332            }
333    
334            try
335            {
336                m_project.log( "installing: " + uri + " as " + urn );
337    
338                Part part = Part.load( uri );
339                
340                ClassLoader current = Thread.currentThread().getContextClassLoader();
341                
342                if( part instanceof Plugin )
343                {
344                    Plugin plugin = (Plugin) part;
345                    Class clazz = plugin.getPluginClass();
346                    final String key = uri + ":" + name;
347                    getProject().log( "installing single task plugin [" + key + "]", Project.MSG_VERBOSE );
348                    super.addTaskDefinition( key, clazz );
349                }
350                else if( part instanceof Resource )
351                {
352                    Resource res = (Resource) part;
353                    String resource = res.getPath();
354                    getProject().log( "installing antlib plugin [" + resource + "]", Project.MSG_VERBOSE );
355                    ClassLoader classloader = part.getClassLoader();
356                    InputStream input = classloader.getResourceAsStream( resource );
357                    if( null == input )
358                    {
359                        final String error = 
360                          "Cannot load resource [" 
361                          + resource 
362                          + "] because it does not exist within the cloassloader defined by the uri [" 
363                          + uri 
364                          + "]"
365                          + "\n" + classloader.toString();
366                        throw new BuildException( error );
367                    }
368    
369                    Element root = ElementHelper.getRootElement( input );
370                    Element[] tasks = ElementHelper.getChildren( root, "taskdef" );
371                    for( int i=0; i < tasks.length; i++ )
372                    {
373                        Element task = tasks[i];
374                        String key = urn + ":" + ElementHelper.getAttribute( task, "name" );
375                        getProject().log( "installing task [" + key + "]", Project.MSG_VERBOSE );
376                        String classname = ElementHelper.getAttribute( task, "classname" );
377                        Class clazz = classloader.loadClass( classname );
378                        super.addTaskDefinition( key, clazz );
379                    }
380                    Element[] types = ElementHelper.getChildren( root, "typedef" );
381                    for( int i=0; i < types.length; i++ )
382                    {
383                        Element type = types[i];
384                        String key = urn + ":" + ElementHelper.getAttribute( type, "name" );
385                        getProject().log( "installing type [" + key + "]", Project.MSG_VERBOSE );
386                        String classname = ElementHelper.getAttribute( type, "classname" );
387                        Class clazz = classloader.loadClass( classname );
388                        super.addDataTypeDefinition( key, clazz );
389                    }
390                }
391                m_URIS.add( uri );
392            }
393            catch( BuildException e )
394            {
395                throw e;
396            }
397            catch( Throwable e )
398            {
399                final String error =
400                  "Could not load plugin: " + uri;
401                throw new BuildException( error, e );
402            }
403        }
404    
405       /**
406        * Returns the current project.
407        * @return the project
408        */
409        private Project getProject()
410        {
411            return m_project;
412        }
413    
414       /**
415        * Convert a urn to a url wrapping any errors in a build exception.
416        * @param urn the urn
417        * @return the uri
418        * @exception BuildException if a convertion error occurs
419        */
420        private URI convertToURI( String urn ) throws BuildException
421        {
422            try
423            {
424                return new URI( urn );
425            }
426            catch( Exception e )
427            {
428                final String error =
429                  "Unable to convert the urn ["
430                  + urn
431                  + "] to a uri.";
432                throw new BuildException( error, e );
433            }
434        }
435    
436       /**
437        * Notification that the build has started.
438        * @param event the build event
439        * @exception BuildException if a build error occurs
440        */
441        public void buildStarted( BuildEvent event )
442            throws BuildException
443        {
444            initialize( event.getProject(), false );
445        }
446    
447       /**
448        * Notification that a sub build has started.
449        * @param event the build event
450        */
451        public void subBuildStarted( BuildEvent event )
452        {
453            initialize( event.getProject(), true );
454        }
455    
456       /**
457        * Notification that a sub build has finished.
458        * @param event the build event
459        */
460        public void subBuildFinished( BuildEvent event )
461        {
462        }
463    
464       /**
465        * Notification that the build has finished.
466        * @param event the build event
467        */
468        public void buildFinished( BuildEvent event )
469        {
470        }
471    
472       /**
473        * Notification that the build target has started.
474        * @param event the build event
475        */
476        public void targetStarted( BuildEvent event )
477        {
478        }
479    
480       /**
481        * Notification that the build target has finished.
482        * @param event the build event
483        */
484        public void targetFinished( BuildEvent event )
485        {
486        }
487    
488       /**
489        * Notification that the build task has started.
490        * @param event the build event
491        */
492        public void taskStarted( BuildEvent event )
493        {
494        }
495    
496       /**
497        * Notification that the build task has finaished.
498        * @param event the build event
499        */
500        public void taskFinished( BuildEvent event )
501        {
502        }
503    
504       /**
505        * Notification of a message logged.
506        * @param event the build event
507        */
508        public void messageLogged( BuildEvent event )
509        {
510        }
511    }
512