View Javadoc

1   package org.codehaus.mojo.jasperreports;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
5    * agreements. See the NOTICE file distributed with this work for additional information regarding
6    * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
7    * "License") you may not use this file except in compliance with the License. You may obtain a copy
8    * of the License at
9    * 
10   * http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing, software distributed under the License
13   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
14   * or implied. See the License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.net.URL;
21  import java.net.URLClassLoader;
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import net.sf.jasperreports.engine.JRException;
30  import net.sf.jasperreports.engine.JasperCompileManager;
31  import net.sf.jasperreports.engine.util.JRProperties;
32  
33  import org.apache.maven.plugin.AbstractMojo;
34  import org.apache.maven.plugin.MojoExecutionException;
35  import org.apache.maven.plugin.MojoFailureException;
36  import org.apache.maven.project.MavenProject;
37  import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
38  import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
39  import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner;
40  import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping;
41  import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
42  
43  /**
44   * Compiles JasperReports xml definition files.
45   * <p>
46   * Much of this was inspired by the JRAntCompileTask, while trying to make it slightly cleaner and
47   * easier to use with Maven's mojo api.
48   * </p>
49   * 
50   * @author gjoseph
51   * @author Tom Schwenk
52   * @goal compile-reports
53   * @phase generate-sources
54   * @requiresDependencyResolution compile
55   */
56  public class JasperReportsMojo
57      extends AbstractMojo
58  {
59      /**
60       * @parameter expression="${project}
61       */
62      private MavenProject project;
63  
64      /**
65       * This is where the generated java sources are stored.
66       * 
67       * @parameter expression="${project.build.directory}/jasperreports/java"
68       */
69      private File javaDirectory;
70  
71      /**
72       * This is where the .jasper files are written.
73       * 
74       * @parameter expression="${project.build.outputDirectory}"
75       */
76      private File outputDirectory;
77  
78      /**
79       * This is where the xml report design files should be.
80       * 
81       * @parameter default-value="src/main/jasperreports"
82       */
83      private File sourceDirectory;
84  
85      /**
86       * The extension of the source files to look for. Finds files with a .jrxml extension by
87       * default.
88       * 
89       * @parameter default-value=".jrxml"
90       */
91      private String sourceFileExt;
92  
93      /**
94       * The extension of the compiled report files. Creates files with a .jasper extension by
95       * default.
96       * 
97       * @parameter default-value=".jasper"
98       */
99      private String outputFileExt;
100 
101     /**
102      * Since the JasperReports compiler deletes the compiled classes, one might want to set this to
103      * true, if they want to handle the generated java source in their application. Mind that this
104      * will not work if the mojo is bound to the compile or any other later phase. (As one might
105      * need to do if they use classes from their project in their report design)
106      * 
107      * @parameter default-value="false"
108      * @deprecated There seems to be an issue with the compiler plugin so don't expect this to work
109      *             yet - the dependencies will have disappeared.
110      */
111     private boolean keepJava;
112 
113     /**
114      * Not used for now - just a TODO - the idea being that one might want to set this to false if
115      * they want to handle the generated java source in their application.
116      * 
117      * @parameter default-value="true"
118      * @deprecated Not implemented
119      */
120     private boolean keepSerializedObject;
121 
122     /**
123      * Wether the xml design files must be validated.
124      * 
125      * @parameter default-value="true"
126      */
127     private boolean xmlValidation;
128 
129     /**
130      * Uses the Javac compiler by default. This is different from the original JasperReports ant
131      * task, which uses the JDT compiler by default.
132      * 
133      * @parameter default-value="net.sf.jasperreports.engine.design.JRJavacCompiler"
134      */
135     private String compiler;
136 
137     /**
138      * @parameter expression="${project.compileClasspathElements}"
139      */
140     private List classpathElements;
141     
142     
143     /**
144      * Additional JRProperties
145      * @parameter 
146      * @since 1.0-beta-2
147      */
148     private Map additionalProperties = new HashMap();
149 
150     /**
151      * Any additional classpath entry you might want to add to the JasperReports compiler. Not
152      * recommended for general use, plugin dependencies should be used instead.
153      * 
154      * @parameter
155      */
156     private String additionalClasspath;
157 
158     public void execute()
159         throws MojoExecutionException, MojoFailureException
160     {
161         getLog().debug( "javaDir = " + javaDirectory );
162         getLog().debug( "sourceDirectory = " + sourceDirectory );
163         getLog().debug( "sourceFileExt = " + sourceFileExt );
164         getLog().debug( "targetDirectory = " + outputDirectory );
165         getLog().debug( "targetFileExt = " + outputFileExt );
166         getLog().debug( "keepJava = " + keepJava );
167         //getLog().debug("keepSerializedObject = " + keepSerializedObject);
168         getLog().debug( "xmlValidation = " + xmlValidation );
169         getLog().debug( "compiler = " + compiler );
170         getLog().debug( "classpathElements = " + classpathElements );
171         getLog().debug( "additionalClasspath = " + additionalClasspath );
172 
173         checkDir( javaDirectory, "Directory for generated java sources", true );
174         checkDir( sourceDirectory, "Source directory", false );
175         checkDir( outputDirectory, "Target directory", true );
176 
177         SourceMapping mapping = new SuffixMapping( sourceFileExt, outputFileExt );
178 
179         Set staleSources = scanSrcDir( mapping );
180         if ( staleSources.isEmpty() )
181         {
182             getLog().info( "Nothing to compile - all Jasper reports are up to date" );
183         }
184         else
185         {
186             // actual compilation
187             compile( staleSources, mapping );
188 
189             if ( keepJava )
190             {
191                 project.addCompileSourceRoot( javaDirectory.getAbsolutePath() );
192             }
193         }
194     }
195 
196     protected void compile( Set files, SourceMapping mapping )
197         throws MojoFailureException, MojoExecutionException
198     {
199         String classpath = buildClasspathString( classpathElements, additionalClasspath );
200         getLog().debug( "buildClasspathString() = " + classpath );
201 
202         getLog().info( "Compiling " + files.size() + " report design files." );
203 
204         getLog().debug( "Set classloader" );
205         ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
206         Thread.currentThread().setContextClassLoader( getClassLoader( classLoader ) );
207 
208         JRProperties.backupProperties();
209 
210         try
211         {
212             JRProperties.setProperty( JRProperties.COMPILER_CLASSPATH, classpath );
213             JRProperties.setProperty( JRProperties.COMPILER_TEMP_DIR, javaDirectory.getAbsolutePath() );
214             JRProperties.setProperty( JRProperties.COMPILER_KEEP_JAVA_FILE, keepJava );
215             JRProperties.setProperty( JRProperties.COMPILER_CLASS, compiler );
216             JRProperties.setProperty( JRProperties.COMPILER_XML_VALIDATION, xmlValidation );
217 
218             for ( Iterator i = additionalProperties.keySet().iterator(); i.hasNext(); )
219             {
220                 String key = (String) i.next();
221                 String value = (String) additionalProperties.get( key );
222                 JRProperties.setProperty( key, value );
223                 getLog().debug( "Added property: " + key + ":" + value );
224             }
225             
226             Iterator it = files.iterator();
227             while ( it.hasNext() )
228             {
229                 File src = (File) it.next();
230                 String srcName = getPathRelativeToRoot( src );
231                 try
232                 {
233                     // get the single destination file
234                     File dest = (File) mapping.getTargetFiles( outputDirectory, srcName ).iterator().next();
235 
236                     File destFileParent = dest.getParentFile();
237                     if ( !destFileParent.exists() )
238                     {
239                         if ( destFileParent.mkdirs() )
240                         {
241                             getLog().debug( "Created directory " + destFileParent );
242                         }
243                         else
244                         {
245                             throw new MojoExecutionException( "Could not create directory " + destFileParent );
246                         }
247                     }
248                     getLog().info( "Compiling report file: " + srcName );
249                     JasperCompileManager.compileReportToFile( src.getAbsolutePath(), dest.getAbsolutePath() );
250                 }
251                 catch ( JRException e )
252                 {
253                     throw new MojoFailureException( this, "Error compiling report design : " + src,
254                                                     "Error compiling report design : " + src + " : " + e.getMessage() );
255                 }
256                 catch ( InclusionScanException e )
257                 {
258                     throw new MojoFailureException( this, "Error compiling report design : " + src,
259                                                     "Error compiling report design : " + src + " : " + e.getMessage() );
260                 }
261             }
262         }
263         finally
264         {
265             JRProperties.restoreProperties();
266 
267             if ( classLoader != null )
268             {
269                 Thread.currentThread().setContextClassLoader( classLoader );
270             }
271         }
272         getLog().info( "Compiled " + files.size() + " report design files." );
273     }
274 
275     /**
276      * Determines source files to be compiled, based on the SourceMapping. No longer needs to be
277      * recursive, since the SourceInclusionScanner handles that.
278      * 
279      * @param mapping
280      * @return
281      * @throws MojoExecutionException
282      */
283     protected Set scanSrcDir( SourceMapping mapping )
284         throws MojoExecutionException
285     {
286         final int staleMillis = 0;
287 
288         SourceInclusionScanner scanner = new StaleSourceScanner( staleMillis );
289         scanner.addSourceMapping( mapping );
290 
291         try
292         {
293             return scanner.getIncludedSources( sourceDirectory, outputDirectory );
294         }
295         catch ( InclusionScanException e )
296         {
297             throw new MojoExecutionException( "Error scanning source root: \'" + sourceDirectory + "\' "
298                 + "for stale files to recompile.", e );
299         }
300     }
301 
302     private String getPathRelativeToRoot( File file )
303         throws MojoExecutionException
304     {
305         try
306         {
307             String root = this.sourceDirectory.getCanonicalPath();
308             String filePath = file.getCanonicalPath();
309             if ( !filePath.startsWith( root ) )
310             {
311                 throw new MojoExecutionException( "File is not in source root ??? " + file );
312             }
313             return filePath.substring( root.length() + 1 );
314         }
315         catch ( IOException e )
316         {
317             throw new MojoExecutionException( "Could not getCanonicalPath from file " + file, e );
318         }
319     }
320 
321     protected String buildClasspathString( List classpathElements, String additionalClasspath )
322     {
323         StringBuffer classpath = new StringBuffer();
324         Iterator it = classpathElements.iterator();
325         while ( it.hasNext() )
326         {
327             String cpElement = (String) it.next();
328             classpath.append( cpElement );
329             if ( it.hasNext() )
330             {
331                 classpath.append( File.pathSeparator );
332             }
333         }
334         if ( additionalClasspath != null )
335         {
336             if ( classpath.length() > 0 )
337             {
338                 classpath.append( File.pathSeparator );
339             }
340             classpath.append( additionalClasspath );
341 
342         }
343         return classpath.toString();
344     }
345 
346     private void checkDir( File dir, String desc, boolean isTarget )
347         throws MojoExecutionException
348     {
349         if ( dir.exists() && !dir.isDirectory() )
350         {
351             throw new MojoExecutionException( desc + " is not a directory : " + dir );
352         }
353         else if ( !dir.exists() && isTarget && !dir.mkdirs() )
354         {
355             throw new MojoExecutionException( desc + " could not be created : " + dir );
356         }
357 
358         if ( isTarget && !dir.canWrite() )
359         {
360             throw new MojoExecutionException( desc + " is not writable : " + dir );
361         }
362     }
363 
364     private ClassLoader getClassLoader( ClassLoader classLoader )
365         throws MojoExecutionException
366     {
367         List classpathURLs = new ArrayList();
368 
369         for ( int i = 0; i < classpathElements.size(); i++ )
370         {
371             String element = (String) classpathElements.get( i );
372             try
373             {
374                 File f = new File( element );
375                 URL newURL = f.toURI().toURL();
376                 classpathURLs.add( newURL );
377                 getLog().debug( "Added to classpath " + element );
378             }
379             catch ( Exception e )
380             {
381                 throw new MojoExecutionException( "Error parsing classparh " + element + " " + e.getMessage() );
382             }
383         }
384 
385         if ( additionalClasspath != null && additionalClasspath.length() > 0 )
386         {
387             String[] elements = additionalClasspath.split( File.pathSeparator );
388             for ( int i = 0; i < elements.length; i++ )
389             {
390                 String element = elements[i];
391                 try
392                 {
393                     File f = new File( element );
394                     URL newURL = f.toURI().toURL();
395                     classpathURLs.add( newURL );
396                     getLog().debug( "Added to classpath " + element );
397                 }
398                 catch ( Exception e )
399                 {
400                     throw new MojoExecutionException( "Error parsing classpath " + additionalClasspath + " "
401                         + e.getMessage() );
402                 }
403             }
404         }
405 
406         URL[] urls = (URL[]) classpathURLs.toArray( new URL[classpathURLs.size()] );
407         return new URLClassLoader( urls, classLoader );
408     }
409 
410 }