package org.codehaus.mojo.jasperreports; /* * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the * "License") you may not use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ import java.io.File; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import net.sf.jasperreports.engine.JRException; import net.sf.jasperreports.engine.JasperCompileManager; import net.sf.jasperreports.engine.util.JRProperties; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.compiler.util.scan.InclusionScanException; import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner; import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner; import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping; import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping; /** * Compiles JasperReports xml definition files. * <p> * Much of this was inspired by the JRAntCompileTask, while trying to make it slightly cleaner and * easier to use with Maven's mojo api. * </p> * * @author gjoseph * @author Tom Schwenk * @goal compile-reports * @phase generate-sources * @requiresDependencyResolution compile */ public class JasperReportsMojo extends AbstractMojo { /** * @parameter expression="${project} */ private MavenProject project; /** * This is where the generated java sources are stored. * * @parameter expression="${project.build.directory}/jasperreports/java" */ private File javaDirectory; /** * This is where the .jasper files are written. * * @parameter expression="${project.build.outputDirectory}" */ private File outputDirectory; /** * This is where the xml report design files should be. * * @parameter default-value="src/main/jasperreports" */ private File sourceDirectory; /** * The extension of the source files to look for. Finds files with a .jrxml extension by * default. * * @parameter default-value=".jrxml" */ private String sourceFileExt; /** * The extension of the compiled report files. Creates files with a .jasper extension by * default. * * @parameter default-value=".jasper" */ private String outputFileExt; /** * Since the JasperReports compiler deletes the compiled classes, one might want to set this to * true, if they want to handle the generated java source in their application. Mind that this * will not work if the mojo is bound to the compile or any other later phase. (As one might * need to do if they use classes from their project in their report design) * * @parameter default-value="false" * @deprecated There seems to be an issue with the compiler plugin so don't expect this to work * yet - the dependencies will have disappeared. */ private boolean keepJava; /** * Not used for now - just a TODO - the idea being that one might want to set this to false if * they want to handle the generated java source in their application. * * @parameter default-value="true" * @deprecated Not implemented */ private boolean keepSerializedObject; /** * Wether the xml design files must be validated. * * @parameter default-value="true" */ private boolean xmlValidation; /** * Uses the Javac compiler by default. This is different from the original JasperReports ant * task, which uses the JDT compiler by default. * * @parameter default-value="net.sf.jasperreports.engine.design.JRJavacCompiler" */ private String compiler; /** * @parameter expression="${project.compileClasspathElements}" */ private List classpathElements; /** * Additional JRProperties * @parameter * @since 1.0-beta-2 */ private Map additionalProperties = new HashMap(); /** * Any additional classpath entry you might want to add to the JasperReports compiler. Not * recommended for general use, plugin dependencies should be used instead. * * @parameter */ private String additionalClasspath; public void execute() throws MojoExecutionException, MojoFailureException { getLog().debug( "javaDir = " + javaDirectory ); getLog().debug( "sourceDirectory = " + sourceDirectory ); getLog().debug( "sourceFileExt = " + sourceFileExt ); getLog().debug( "targetDirectory = " + outputDirectory ); getLog().debug( "targetFileExt = " + outputFileExt ); getLog().debug( "keepJava = " + keepJava ); //getLog().debug("keepSerializedObject = " + keepSerializedObject); getLog().debug( "xmlValidation = " + xmlValidation ); getLog().debug( "compiler = " + compiler ); getLog().debug( "classpathElements = " + classpathElements ); getLog().debug( "additionalClasspath = " + additionalClasspath ); checkDir( javaDirectory, "Directory for generated java sources", true ); checkDir( sourceDirectory, "Source directory", false ); checkDir( outputDirectory, "Target directory", true ); SourceMapping mapping = new SuffixMapping( sourceFileExt, outputFileExt ); Set staleSources = scanSrcDir( mapping ); if ( staleSources.isEmpty() ) { getLog().info( "Nothing to compile - all Jasper reports are up to date" ); } else { // actual compilation compile( staleSources, mapping ); if ( keepJava ) { project.addCompileSourceRoot( javaDirectory.getAbsolutePath() ); } } } protected void compile( Set files, SourceMapping mapping ) throws MojoFailureException, MojoExecutionException { String classpath = buildClasspathString( classpathElements, additionalClasspath ); getLog().debug( "buildClasspathString() = " + classpath ); getLog().info( "Compiling " + files.size() + " report design files." ); getLog().debug( "Set classloader" ); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader( getClassLoader( classLoader ) ); JRProperties.backupProperties(); try { JRProperties.setProperty( JRProperties.COMPILER_CLASSPATH, classpath ); JRProperties.setProperty( JRProperties.COMPILER_TEMP_DIR, javaDirectory.getAbsolutePath() ); JRProperties.setProperty( JRProperties.COMPILER_KEEP_JAVA_FILE, keepJava ); JRProperties.setProperty( JRProperties.COMPILER_CLASS, compiler ); JRProperties.setProperty( JRProperties.COMPILER_XML_VALIDATION, xmlValidation ); for ( Iterator i = additionalProperties.keySet().iterator(); i.hasNext(); ) { String key = (String) i.next(); String value = (String) additionalProperties.get( key ); JRProperties.setProperty( key, value ); getLog().debug( "Added property: " + key + ":" + value ); } Iterator it = files.iterator(); while ( it.hasNext() ) { File src = (File) it.next(); String srcName = getPathRelativeToRoot( src ); try { // get the single destination file File dest = (File) mapping.getTargetFiles( outputDirectory, srcName ).iterator().next(); File destFileParent = dest.getParentFile(); if ( !destFileParent.exists() ) { if ( destFileParent.mkdirs() ) { getLog().debug( "Created directory " + destFileParent ); } else { throw new MojoExecutionException( "Could not create directory " + destFileParent ); } } getLog().info( "Compiling report file: " + srcName ); JasperCompileManager.compileReportToFile( src.getAbsolutePath(), dest.getAbsolutePath() ); } catch ( JRException e ) { throw new MojoFailureException( this, "Error compiling report design : " + src, "Error compiling report design : " + src + " : " + e.getMessage() ); } catch ( InclusionScanException e ) { throw new MojoFailureException( this, "Error compiling report design : " + src, "Error compiling report design : " + src + " : " + e.getMessage() ); } } } finally { JRProperties.restoreProperties(); if ( classLoader != null ) { Thread.currentThread().setContextClassLoader( classLoader ); } } getLog().info( "Compiled " + files.size() + " report design files." ); } /** * Determines source files to be compiled, based on the SourceMapping. No longer needs to be * recursive, since the SourceInclusionScanner handles that. * * @param mapping * @return * @throws MojoExecutionException */ protected Set scanSrcDir( SourceMapping mapping ) throws MojoExecutionException { final int staleMillis = 0; SourceInclusionScanner scanner = new StaleSourceScanner( staleMillis ); scanner.addSourceMapping( mapping ); try { return scanner.getIncludedSources( sourceDirectory, outputDirectory ); } catch ( InclusionScanException e ) { throw new MojoExecutionException( "Error scanning source root: \'" + sourceDirectory + "\' " + "for stale files to recompile.", e ); } } private String getPathRelativeToRoot( File file ) throws MojoExecutionException { try { String root = this.sourceDirectory.getCanonicalPath(); String filePath = file.getCanonicalPath(); if ( !filePath.startsWith( root ) ) { throw new MojoExecutionException( "File is not in source root ??? " + file ); } return filePath.substring( root.length() + 1 ); } catch ( IOException e ) { throw new MojoExecutionException( "Could not getCanonicalPath from file " + file, e ); } } protected String buildClasspathString( List classpathElements, String additionalClasspath ) { StringBuffer classpath = new StringBuffer(); Iterator it = classpathElements.iterator(); while ( it.hasNext() ) { String cpElement = (String) it.next(); classpath.append( cpElement ); if ( it.hasNext() ) { classpath.append( File.pathSeparator ); } } if ( additionalClasspath != null ) { if ( classpath.length() > 0 ) { classpath.append( File.pathSeparator ); } classpath.append( additionalClasspath ); } return classpath.toString(); } private void checkDir( File dir, String desc, boolean isTarget ) throws MojoExecutionException { if ( dir.exists() && !dir.isDirectory() ) { throw new MojoExecutionException( desc + " is not a directory : " + dir ); } else if ( !dir.exists() && isTarget && !dir.mkdirs() ) { throw new MojoExecutionException( desc + " could not be created : " + dir ); } if ( isTarget && !dir.canWrite() ) { throw new MojoExecutionException( desc + " is not writable : " + dir ); } } private ClassLoader getClassLoader( ClassLoader classLoader ) throws MojoExecutionException { List classpathURLs = new ArrayList(); for ( int i = 0; i < classpathElements.size(); i++ ) { String element = (String) classpathElements.get( i ); try { File f = new File( element ); URL newURL = f.toURI().toURL(); classpathURLs.add( newURL ); getLog().debug( "Added to classpath " + element ); } catch ( Exception e ) { throw new MojoExecutionException( "Error parsing classparh " + element + " " + e.getMessage() ); } } if ( additionalClasspath != null && additionalClasspath.length() > 0 ) { String[] elements = additionalClasspath.split( File.pathSeparator ); for ( int i = 0; i < elements.length; i++ ) { String element = elements[i]; try { File f = new File( element ); URL newURL = f.toURI().toURL(); classpathURLs.add( newURL ); getLog().debug( "Added to classpath " + element ); } catch ( Exception e ) { throw new MojoExecutionException( "Error parsing classpath " + additionalClasspath + " " + e.getMessage() ); } } } URL[] urls = (URL[]) classpathURLs.toArray( new URL[classpathURLs.size()] ); return new URLClassLoader( urls, classLoader ); } }