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.2.0 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