//========================================================================
//$Id$
//Copyright 2006 Mort Bay Consulting Pty. Ltd.
//------------------------------------------------------------------------
//Licensed 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.
//========================================================================
package com.reucon.maven.plugin.openfire.jspc;
import org.apache.jasper.JspC;
import org.apache.maven.artifact.Artifact;
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.mortbay.jetty.webapp.WebAppClassLoader;
import org.mortbay.jetty.webapp.WebAppContext;
import org.mortbay.util.IO;
import java.io.*;
import java.net.URL;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
/**
* This goal will compile jsps for an Openfire Plugin so that they can be included in the jar.
*
* @author janb
* @goal jspc
* @phase process-classes
* @requiresDependencyResolution compile
* @description Runs jspc compiler to produce .java and .class files
*/
public class JspcMojo extends AbstractMojo
{
public static final String END_OF_WEBAPP = "</web-app>";
/**
* The maven project.
*
* @parameter expression="${project}"
* @required
* @readonly
*/
private MavenProject project;
/**
* File into which to generate the <servlet> and <servlet-mapping>
* tags for the compiled jsps
*
* @parameter expression="${basedir}/target/webfrag.xml"
*/
private String webXmlFragment;
/**
* Optional. A marker string in the src web.xml file which indicates where
* to merge in the generated web.xml fragment. Note that the marker string
* will NOT be preserved during the insertion. Can be left blank, in which
* case the generated fragment is inserted just before the </web-app> line
*
* @parameter
*/
private String insertionMarker;
/**
* Merge the generated fragment file with the web.xml from
* webAppSourceDirectory. The merged file will go into the same
* directory as the webXmlFragment.
*
* @parameter expression="true"
*/
private boolean mergeFragment;
/**
* The destination directory into which to put the
* compiled jsps.
*
* @parameter expression="${basedir}/target/classes"
*/
private String generatedClasses;
/**
* Controls whether or not .java files generated during compilation will be preserved.
*
* @parameter expression="false"
*/
private boolean keepSources;
/**
* Default root package for all generated JSP classes.
*
* @parameter expression="${project.groupId}.jsp"
*/
private String jspPackageRoot;
/**
* Root directory for all html/jsp etc files.
*
* @parameter expression="${basedir}/src/main/webapp"
* @required
*/
private String webAppSourceDirectory;
/**
* The location of the compiled classes for the webapp.
*
* @parameter expression="${project.build.outputDirectory}"
*/
private File classesDirectory;
/**
* Whether or not to output more verbose messages during compilation.
*
* @parameter expression="false";
*/
private boolean verbose;
/**
* If true, validates tlds when parsing.
*
* @parameter expression="false";
*/
private boolean validateXml;
/**
* The encoding scheme to use.
*
* @parameter expression="UTF-8"
*/
private String javaEncoding;
/**
* Whether or not to generate JSR45 compliant debug info
*
* @parameter expression="true";
*/
private boolean suppressSmap;
/**
* Whether or not to ignore precompilation errors caused by
* jsp fragments.
*
* @parameter expression="false"
*/
private boolean ignoreJspFragmentErrors;
/**
* Allows a prefix to be appended to the standard schema locations
* so that they can be loaded from elsewhere.
*
* @parameter
*/
private String schemaResourcePrefix;
public void execute() throws MojoExecutionException, MojoFailureException
{
if (getLog().isDebugEnabled())
{
getLog().info("verbose=" + verbose);
getLog().info("webAppSourceDirectory=" + webAppSourceDirectory);
getLog().info("generatedClasses=" + generatedClasses);
getLog().info("webXmlFragment=" + webXmlFragment);
getLog().info("validateXml=" + validateXml);
getLog().info("jspPackageRoot=" + jspPackageRoot);
getLog().info("javaEncoding=" + javaEncoding);
getLog().info("insertionMarker=" + (insertionMarker == null || insertionMarker.equals("") ? END_OF_WEBAPP : insertionMarker));
getLog().info("keepSources=" + keepSources);
getLog().info("mergeFragment=" + mergeFragment);
getLog().info("suppressSmap=" + suppressSmap);
getLog().info("ignoreJspFragmentErrors=" + ignoreJspFragmentErrors);
getLog().info("schemaResourcePrefix=" + schemaResourcePrefix);
}
try
{
prepare();
compile();
cleanupSrcs();
mergeWebXml();
}
catch (Exception e)
{
throw new MojoFailureException(e, "Failure processing jsps", "Failure processing jsps");
}
}
public void compile()
throws Exception
{
ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
WebAppContext webAppContext = new WebAppContext();
webAppContext.setContextPath("/");
webAppContext.setWar(webAppSourceDirectory);
WebAppClassLoader webAppClassLoader = new WebAppClassLoader(currentClassLoader, webAppContext);
setUpClassPath(webAppClassLoader);
StringBuffer classpathStr = new StringBuffer();
URL[] urls = webAppClassLoader.getURLs();
for (int i = 0; i < urls.length; i++)
{
if (getLog().isDebugEnabled())
{
getLog().debug("webappclassloader contains: " + urls[i]);
}
classpathStr.append(urls[i].getFile());
if (getLog().isDebugEnabled())
{
getLog().debug("added to classpath: " + urls[i].getFile());
}
classpathStr.append(System.getProperty("path.separator"));
}
Thread.currentThread().setContextClassLoader(webAppClassLoader);
JspC jspc = new JspC();
jspc.setWebXmlFragment(webXmlFragment);
jspc.setUriroot(webAppSourceDirectory);
jspc.setPackage(jspPackageRoot);
jspc.setOutputDir(generatedClasses);
jspc.setValidateXml(validateXml);
jspc.setClassPath(classpathStr.toString());
jspc.setCompile(true);
jspc.setSmapSuppressed(suppressSmap);
jspc.setSmapDumped(!suppressSmap);
jspc.setJavaEncoding(javaEncoding);
//Glassfish jspc only checks
try
{
jspc.setIgnoreJspFragmentErrors(ignoreJspFragmentErrors);
}
catch (NoSuchMethodError e)
{
getLog().debug("Tomcat Jasper does not support configuration option 'ignoreJspFragmentErrors': ignored");
}
try
{
if (schemaResourcePrefix != null)
{
jspc.setSchemaResourcePrefix(schemaResourcePrefix);
}
}
catch (NoSuchMethodError e)
{
getLog().debug("Tomcat Jasper does not support configuration option 'schemaResourcePrefix': ignored");
}
if (verbose)
{
jspc.setVerbose(99);
}
else
{
jspc.setVerbose(0);
}
jspc.execute();
Thread.currentThread().setContextClassLoader(currentClassLoader);
}
/**
* Until Jasper supports the option to generate the srcs in a
* different dir than the classes, this is the best we can do.
*
* @throws Exception
*/
public void cleanupSrcs()
throws Exception
{
//delete the .java files - depending on keepGenerated setting
if (!keepSources)
{
File generatedClassesDir = new File(generatedClasses);
File[] srcFiles = generatedClassesDir.listFiles(new FilenameFilter()
{
public boolean accept(File dir, String name)
{
if (name == null)
{
return false;
}
if (name.trim().equals(""))
{
return false;
}
if (name.endsWith(".java"))
{
return true;
}
return false;
}
});
for (int i = 0; (srcFiles != null) && (i < srcFiles.length); i++)
{
srcFiles[i].delete();
}
}
}
/**
* Take the web fragment and put it inside a copy of the
* web.xml file from the webAppSourceDirectory.
* <p/>
* You can specify the insertion point by specifying
* the string in the insertionMarker configuration entry.
* <p/>
* If you dont specify the insertionMarker, then the fragment
* will be inserted at the end of the file just before the
* </webapp>
*
* @throws Exception
*/
public void mergeWebXml()
throws Exception
{
if (mergeFragment)
{
BufferedReader webXmlReader = null;
String marker = null;
//open the src web.xml
File webXml = new File(webAppSourceDirectory + "/WEB-INF/web.xml");
File fragmentWebXml = new File(webXmlFragment);
if (!fragmentWebXml.exists())
{
getLog().info("No fragment web.xml file generated");
}
File mergedWebXml = new File(fragmentWebXml.getParentFile(), "web.xml");
PrintWriter mergedWebXmlWriter = new PrintWriter(new FileWriter(mergedWebXml));
if (webXml.exists())
{
webXmlReader = new BufferedReader(new FileReader(webXml));
}
if (webXmlReader != null)
{
//read up to the insertion marker or the </webapp> if there is no marker
boolean atInsertPoint = false;
boolean atEOF = false;
marker = (insertionMarker == null || insertionMarker.equals("") ? END_OF_WEBAPP : insertionMarker);
while (!atInsertPoint && !atEOF)
{
String line = webXmlReader.readLine();
if (line == null)
{
atEOF = true;
}
else if (line.indexOf(marker) >= 0)
{
atInsertPoint = true;
}
else
{
mergedWebXmlWriter.println(line);
}
}
}
else
{
mergedWebXmlWriter.append("<web-app>\n");
}
//put in the generated fragment
BufferedReader fragmentWebXmlReader = new BufferedReader(new FileReader(fragmentWebXml));
IO.copy(fragmentWebXmlReader, mergedWebXmlWriter);
if (webXmlReader != null)
{
// if we inserted just before the </web-app>, put it back in
if (END_OF_WEBAPP.equals(marker))
{
mergedWebXmlWriter.println(END_OF_WEBAPP);
}
// copy in the rest of the original web.xml file
IO.copy(webXmlReader, mergedWebXmlWriter);
webXmlReader.close();
}
else
{
mergedWebXmlWriter.append("\n</web-app>\n");
}
mergedWebXmlWriter.close();
fragmentWebXmlReader.close();
}
}
private void prepare()
throws Exception
{
//For some reason JspC doesn't like it if the dir doesn't
//already exist and refuses to create the web.xml fragment
File generatedSourceDirectoryFile = new File(generatedClasses);
if (!generatedSourceDirectoryFile.exists())
{
generatedSourceDirectoryFile.mkdirs();
}
}
/**
* Set up the execution classpath for Jasper.
* <p/>
* Put everything in the classesDirectory and all
* of the dependencies on the classpath.
*
* @param classLoader we use a Jetty WebAppClassLoader to load the classes
* @throws Exception
*/
private void setUpClassPath(WebAppClassLoader classLoader) throws Exception
{
String classesDir = classesDirectory.getCanonicalPath();
classesDir = classesDir + (classesDir.endsWith(File.pathSeparator) ? "" : File.separator);
classLoader.addClassPath(classesDir);
if (getLog().isDebugEnabled())
{
getLog().debug("Adding to classpath classes dir: " + classesDir);
}
for (Iterator iter = project.getArtifacts().iterator(); iter.hasNext();)
{
Artifact artifact = (Artifact) iter.next();
String filePath = artifact.getFile().getCanonicalPath();
if (!Artifact.SCOPE_TEST.equals(artifact.getScope()))
{
if (getLog().isDebugEnabled())
{
getLog().debug("Adding to classpath dependency file: " + filePath);
}
classLoader.addClassPath(filePath);
}
}
}
}