001    /*
002     * Copyright 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.tools.impl;
020    
021    import java.io.File;
022    import java.net.URI;
023    import java.net.URL;
024    import java.util.Vector;
025    
026    import net.dpml.library.Builder;
027    import net.dpml.library.Library;
028    import net.dpml.library.Resource;
029    
030    import net.dpml.tools.Context;
031    import net.dpml.tools.BuildError;
032    import net.dpml.tools.info.BuilderDirective;
033    import net.dpml.tools.info.BuilderDirectiveHelper;
034    
035    import net.dpml.transit.Artifact;
036    import net.dpml.transit.model.TransitModel;
037    import net.dpml.transit.tools.MainTask;
038    
039    import net.dpml.util.Logger;
040    
041    import org.apache.tools.ant.Project;
042    import org.apache.tools.ant.ProjectHelper;
043    import org.apache.tools.ant.BuildException;
044    import org.apache.tools.ant.BuildLogger;
045    import org.apache.tools.ant.DefaultLogger;
046    import org.apache.tools.ant.input.DefaultInputHandler;
047    import org.apache.tools.ant.DemuxInputStream;
048    
049    /**
050     * The StandardBuilder is a plugin established by the Tools build controller
051     * used for the building of a project based on the Ant build system in conjunction
052     * with Transit plugin management services.
053     *
054     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
055     * @version 1.1.3
056     */
057    public class StandardBuilder implements Builder 
058    {
059        // ------------------------------------------------------------------------
060        // static
061        // ------------------------------------------------------------------------
062    
063       /**
064        * The default template uri path.
065        */
066        public static final String DEFAULT_TEMPLATE_URN = "local:template:dpml/tools/standard";
067        
068       /**
069        * The builder configuration.
070        */
071        public static final BuilderDirective CONFIGURATION = loadConfiguration();
072        
073        private static BuilderDirective loadConfiguration()
074        {
075            try
076            {
077                return BuilderDirectiveHelper.build();
078            }
079            catch( Throwable e )
080            {
081                final String error = 
082                  "Internal error while attempting to establish the builder configuration.";
083                BuilderError be = new BuilderError( error, e );
084                be.printStackTrace();
085                return null;
086            }
087        }
088        
089        // ------------------------------------------------------------------------
090        // state
091        // ------------------------------------------------------------------------
092        
093        private Logger m_logger;
094        private TransitModel m_model;
095        private Library m_library;
096        private boolean m_verbose;
097        private Throwable m_result;
098    
099        // ------------------------------------------------------------------------
100        // constructors
101        // ------------------------------------------------------------------------
102    
103       /**
104        * Creation of a new standard builder.
105        *
106        * @param logger assigned logging channel
107        * @param library the library
108        */
109        public StandardBuilder( Logger logger, Library library )
110        {
111            this( logger, library, false );
112        }
113        
114       /**
115        * Creation of a new standard builder.
116        *
117        * @param logger assigned logging channel
118        * @param library the library
119        * @param verbose verbose execution flag
120        */
121        public StandardBuilder( Logger logger, Library library, boolean verbose )
122        {
123            m_logger = logger;
124            m_verbose = verbose;
125            m_library = library;
126            
127            Thread.currentThread().setContextClassLoader( Builder.class.getClassLoader() );
128        }
129    
130        // ------------------------------------------------------------------------
131        // Builder
132        // ------------------------------------------------------------------------
133    
134       /**
135        * Build the project defined by the supplied resource.
136        * @param resource the project definition
137        * @param targets an array of build target names
138        * @return the build success status
139        */
140        public boolean build( Resource resource, String[] targets )
141        {
142            Project project = null;
143            File template = null;
144            try
145            {
146                project = createProject( resource );
147            }
148            catch( Throwable e )
149            {
150                m_result = e;
151                if( m_logger.isErrorEnabled() )
152                {
153                    final String error = 
154                      "Failed to construct embedded project."
155                      + "\nResource: " + resource.getResourcePath()
156                      + "\nBasedir: " + resource.getBaseDir();
157                    m_logger.error( error, e );
158                }
159                return false;
160            }
161            try
162            {
163                template = getTemplateFile( resource );
164            }
165            catch( Throwable e )
166            {
167                m_result = e;
168                if( m_logger.isErrorEnabled() )
169                {
170                    final String error = 
171                      "Failed to load template file."
172                      + "\nResource: " + resource.getResourcePath()
173                      + "\nBasedir: " + resource.getBaseDir();
174                    m_logger.error( error, e );
175                }
176                return false;
177            }
178            return build( resource, project, template, targets );
179        }
180        
181       /**
182        * Return the template for the resource.
183        * @param resource the project definition
184        * @return the template
185        */
186        public File getTemplateFile( Resource resource )
187        {
188            try
189            {
190                String systemOverride = System.getProperty( "project.template" );
191                String override = resource.getProperty( "project.template", systemOverride );
192                if( null != override )
193                {
194                    File template = getTemplateFile( override );
195                    return template;
196                }
197                
198                File basedir = resource.getBaseDir();
199                String buildfile = resource.getProperty( "project.buildfile" );
200                String defaultBuildfile = resource.getProperty( "project.standard.buildfile", "build.xml" );
201                if( null != buildfile )
202                {
203                    // there is an explicit 'project.buildfile' declaration in which case
204                    // we check for existance and fail if it does not exist
205                    
206                    File file = new File( basedir, buildfile );
207                    if( file.exists() )
208                    {
209                        return file;
210                    }
211                    else
212                    {
213                        final String error = 
214                          "Resource buildfile ["
215                          + file
216                          + "] does not exist.";
217                        throw new BuildException( error );
218                    }
219                }
220                else if( null != defaultBuildfile )
221                {
222                    // check if a buildfile of the default name exists in the project's 
223                    // basedir - and if so - use it to build the project
224                    
225                    File file = new File( basedir, defaultBuildfile );
226                    if( file.exists() )
227                    {
228                        return file;
229                    }
230                }
231                
232                // otherwise we build using either an explicit or default template
233                // resolved via a uri (typically a template stored in prefs)
234                
235                String defaultTemplateSpec = 
236                  resource.getProperty( "project.standard.template", DEFAULT_TEMPLATE_URN );
237                String templateSpec = resource.getProperty( "project.template", defaultTemplateSpec );
238                    
239                if( null != templateSpec )
240                {
241                    File template = getTemplateFile( templateSpec );
242                    return template;
243                }
244                else
245                {
246                    final String error = 
247                      "Resource template property 'project.template' is undefined.";
248                    throw new BuildException( error );
249                }
250            }
251            catch( BuildException e )
252            {
253                throw e;
254            }
255            catch( Throwable e )
256            {
257                m_result = e;
258                final String error = 
259                  "Unexpected error while attempting to resolve project template."
260                  + "\nResource path: " 
261                  + resource.getResourcePath();
262                throw new BuildError( error, e );
263            }
264        }
265        
266        // ------------------------------------------------------------------------
267        // implementation
268        // ------------------------------------------------------------------------
269        
270        boolean build( Resource resource, Project project, File template, String[] targets )
271        {
272            try
273            {
274                ProjectHelper helper = (ProjectHelper) project.getReference( "ant.projectHelper" );
275                
276                if( null != template )
277                {
278                    helper.parse( project, template );
279                }
280                
281                Vector vector = new Vector();
282                
283                if( targets.length == 0 )
284                {
285                    if( null != project.getDefaultTarget() )
286                    {
287                        vector.addElement( project.getDefaultTarget() );
288                    }
289                    else
290                    {
291                        vector.addElement( CONFIGURATION.getDefaultPhase() );
292                    }
293                }
294                else
295                {
296                    for( int i=0; i<targets.length; i++ )
297                    {
298                        String target = targets[i];
299                        vector.addElement( target );
300                    }
301                }
302                
303                if( vector.size() == 0 )
304                {
305                    final String errorMessage =
306                      "No targets requested and no default target declared.";
307                    throw new BuildException( errorMessage );
308                }
309                
310                project.executeTargets( vector );
311                return true;
312            }
313            catch( BuildException e )
314            {
315                m_result = e;
316                if( m_logger.isDebugEnabled() )
317                {
318                    final String error = 
319                      "Build failure."
320                      + "\nProject: " + resource.getResourcePath()
321                      + "\nBasedir: " + resource.getBaseDir()
322                      + "\nTemplate: " + template 
323                      + "\nLocation: " + e.getLocation();
324                    Throwable cause = e.getCause();
325                    m_logger.error( error, cause );
326                }
327                return false;
328            }
329            catch( Throwable e )
330            {
331                System.out.println( 
332                  "# UNEXPECTED: " 
333                  + e.toString() 
334                  + " (" 
335                  + m_logger.isErrorEnabled() 
336                  + ")" );
337                  
338                m_result = e;
339                if( m_logger.isErrorEnabled() )
340                {
341                    final String error = 
342                      "Build error."
343                      + "\nProject: " + resource.getResourcePath()
344                      + "\nBasedir: " + resource.getBaseDir()
345                      + "\nTemplate: " + template;
346                    m_logger.error( error, e );
347                }
348                return false;
349            }
350            finally
351            {
352                project.fireBuildFinished( m_result );
353            }
354        }
355        
356        private File getTemplateFile( String spec )
357        {
358            try
359            {
360                URI uri = new URI( spec );
361                if( Artifact.isRecognized( uri ) )
362                {
363                    URL url = uri.toURL();
364                    return (File) url.getContent( new Class[]{File.class} );
365                }
366            }
367            catch( Throwable e )
368            {
369            }
370            return new File( spec );
371        }
372        
373        Project createProject( Resource resource )
374        {
375            try
376            {
377                Project project = newProject();
378                Context context = new DefaultContext( resource, project );
379                return project;
380            }
381            catch( Exception e )
382            {
383                final String error = 
384                  "Unable to establish build context." 
385                  + "\nProject: " + resource;
386                throw new BuildError( error, e );
387            }
388        }
389        
390        private Project newProject() 
391        {
392            Project project = new Project();
393            project.setSystemProperties();
394            project.setDefaultInputStream( System.in );
395            setupTransitComponentHelper( project );
396            project.setCoreLoader( getClass().getClassLoader() );
397            project.addBuildListener( createLogger() );
398            System.setIn( new DemuxInputStream( project ) );
399            project.setProjectReference( new DefaultInputHandler() );
400            ProjectHelper helper = ProjectHelper.getProjectHelper();
401            project.addReference( "ant.projectHelper", helper );
402            return project;
403        }
404        
405        private void setupTransitComponentHelper( Project project ) 
406        {
407            try
408            {
409                MainTask task = new MainTask();
410                task.setProject( project );
411                task.init();
412                task.execute();
413            }
414            catch( BuildException e )
415            {
416                throw e;
417            }
418            catch( Exception e )
419            {
420                final String error = 
421                  "Setup failure.";
422                throw new BuildException( error, e );
423            }
424        }
425        
426        private BuildLogger createLogger()
427        {
428            BuildLogger logger = new DefaultLogger();
429            if( m_verbose )
430            {
431                logger.setMessageOutputLevel( Project.MSG_VERBOSE );
432            }
433            else
434            {
435                logger.setMessageOutputLevel( Project.MSG_INFO );
436            }
437            logger.setOutputPrintStream( System.out );
438            logger.setErrorPrintStream( System.err );
439            return logger;
440        }
441        
442        private Logger getLogger()
443        {
444            return m_logger;
445        }
446    }
447