001: //========================================================================
002: //$Id: JspcMojo.java 1410 2006-12-15 11:33:13Z janb $
003: //Copyright 2006 Mort Bay Consulting Pty. Ltd.
004: //------------------------------------------------------------------------
005: //Licensed under the Apache License, Version 2.0 (the "License");
006: //you may not use this file except in compliance with the License.
007: //You may obtain a copy of the License at
008: //http://www.apache.org/licenses/LICENSE-2.0
009: //Unless required by applicable law or agreed to in writing, software
010: //distributed under the License is distributed on an "AS IS" BASIS,
011: //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: //See the License for the specific language governing permissions and
013: //limitations under the License.
014: //========================================================================
015:
016: package org.mortbay.jetty.jspc.plugin;
017:
018: import java.io.BufferedReader;
019: import java.io.BufferedWriter;
020: import java.io.File;
021: import java.io.FileReader;
022: import java.io.FileWriter;
023: import java.io.FilenameFilter;
024: import java.io.PrintWriter;
025: import java.net.URI;
026: import java.net.URL;
027: import java.util.ArrayList;
028: import java.util.Iterator;
029: import java.util.List;
030:
031: import org.apache.jasper.JspC;
032: import org.apache.maven.artifact.Artifact;
033: import org.apache.maven.plugin.AbstractMojo;
034: import org.apache.maven.plugin.MojoExecutionException;
035: import org.apache.maven.plugin.MojoFailureException;
036: import org.apache.maven.project.MavenProject;
037: import org.mortbay.jetty.webapp.WebAppClassLoader;
038: import org.mortbay.jetty.webapp.WebAppContext;
039: import org.mortbay.util.IO;
040:
041: /**
042: * <p>This goal will compile jsps for a webapp so that they can be included in a war.</p>
043: * <p>At runtime, the plugin will use the jsp2.0 jspc compiler if you are running
044: * on a 1.4 or lower jvm. If you are using a 1.5 jvm, then the jsp2.1 compiler will be selected.
045: * (this is the same behaviour as the <a href="http://jetty.mortbay.org/maven-plugin">jetty plugin</a> for executing webapps).
046: * </p>
047: * <p>
048: * Note that the same java compiler will be used as for on-the-fly compiled jsps, which will
049: * be the Eclipse java compiler.
050: * </p>
051: *
052: * <p>See <a href="howto.html">Usage</a> for instructions on using this plugin.
053: * </p>
054: * @author janb
055: *
056: * @goal jspc
057: * @phase process-classes
058: * @requiresDependencyResolution runtime
059: * @description Runs jspc compiler to produce .java and .class files
060: */
061: public class JspcMojo extends AbstractMojo {
062: public static final String END_OF_WEBAPP = "</web-app>";
063:
064: /**
065: * The maven project.
066: *
067: * @parameter expression="${project}"
068: * @required
069: * @readonly
070: */
071: private MavenProject project;
072:
073: /**
074: * File into which to generate the <servlet> and <servlet-mapping>
075: * tags for the compiled jsps
076: * @parameter expression="${basedir}/target/webfrag.xml"
077: */
078: private String webXmlFragment;
079:
080: /**
081: * Optional. A marker string in the src web.xml file which indicates where
082: * to merge in the generated web.xml fragment. Note that the marker string
083: * will NOT be preserved during the insertion. Can be left blank, in which
084: * case the generated fragment is inserted just before the </web-app> line
085: * @parameter
086: */
087: private String insertionMarker;
088:
089: /**
090: * Merge the generated fragment file with the web.xml from
091: * webAppSourceDirectory. The merged file will go into the same
092: * directory as the webXmlFragment.
093: *
094: * @parameter expression="true"
095: */
096: private boolean mergeFragment;
097:
098: /**
099: * The destination directory into which to put the
100: * compiled jsps.
101: *
102: * @parameter expression="${basedir}/target/classes"
103: */
104: private String generatedClasses;
105:
106: /**
107: * Controls whether or not .java files generated during compilation will be preserved.
108: *
109: * @parameter expression="false"
110: */
111: private boolean keepSources;
112:
113: /**
114: * Default root package for all generated classes
115: * @parameter expression="jsp"
116: */
117: private String packageRoot;
118:
119: /**
120: * Root directory for all html/jsp etc files
121: *
122: * @parameter expression="${basedir}/src/main/webapp"
123: * @required
124: */
125: private String webAppSourceDirectory;
126:
127: /**
128: * The location of the compiled classes for the webapp
129: *
130: * @parameter expression="${project.build.outputDirectory}"
131: */
132: private File classesDirectory;
133:
134: /**
135: * Whether or not to output more verbose messages during compilation.
136: * @parameter expression="false";
137: */
138: private boolean verbose;
139:
140: /**
141: * If true, validates tlds when parsing.
142: * @parameter expression="false";
143: */
144: private boolean validateXml;
145:
146: /**
147: * The encoding scheme to use.
148: * @parameter expression="UTF-8"
149: */
150: private String javaEncoding;
151:
152: /**
153: * Whether or not to generate JSR45 compliant debug info
154: *
155: * @parameter expression="true";
156: */
157: private boolean suppressSmap;
158:
159: /**
160: * Whether or not to ignore precompilation errors caused by
161: * jsp fragments.
162: *
163: * @parameter expression="false"
164: */
165: private boolean ignoreJspFragmentErrors;
166:
167: /**
168: * Allows a prefix to be appended to the standard schema locations
169: * so that they can be loaded from elsewhere.
170: *
171: * @parameter
172: */
173: private String schemaResourcePrefix;
174:
175: public void execute() throws MojoExecutionException,
176: MojoFailureException {
177: if (getLog().isDebugEnabled()) {
178: getLog().info("verbose=" + verbose);
179: getLog().info(
180: "webAppSourceDirectory=" + webAppSourceDirectory);
181: getLog().info("generatedClasses=" + generatedClasses);
182: getLog().info("webXmlFragment=" + webXmlFragment);
183: getLog().info("validateXml=" + validateXml);
184: getLog().info("packageRoot=" + packageRoot);
185: getLog().info("javaEncoding=" + javaEncoding);
186: getLog()
187: .info(
188: "insertionMarker="
189: + (insertionMarker == null
190: || insertionMarker
191: .equals("") ? END_OF_WEBAPP
192: : insertionMarker));
193: getLog().info("keepSources=" + keepSources);
194: getLog().info("mergeFragment=" + mergeFragment);
195: getLog().info("suppressSmap=" + suppressSmap);
196: getLog().info(
197: "ignoreJspFragmentErrors="
198: + ignoreJspFragmentErrors);
199: getLog().info(
200: "schemaResourcePrefix=" + schemaResourcePrefix);
201: }
202: try {
203: prepare();
204: compile();
205: cleanupSrcs();
206: mergeWebXml();
207: } catch (Exception e) {
208: throw new MojoFailureException(e,
209: "Failure processing jsps",
210: "Failure processing jsps");
211: }
212: }
213:
214: public void compile() throws Exception {
215: ClassLoader currentClassLoader = Thread.currentThread()
216: .getContextClassLoader();
217:
218: WebAppContext webAppContext = new WebAppContext();
219: webAppContext.setContextPath("/");
220: webAppContext.setWar(webAppSourceDirectory);
221:
222: WebAppClassLoader webAppClassLoader = new WebAppClassLoader(
223: currentClassLoader, webAppContext);
224: setUpClassPath(webAppClassLoader);
225: StringBuffer classpathStr = new StringBuffer();
226: URL[] urls = webAppClassLoader.getURLs();
227: for (int i = 0; i < urls.length; i++) {
228: if (getLog().isDebugEnabled())
229: getLog()
230: .debug("webappclassloader contains: " + urls[i]);
231: classpathStr.append(urls[i].getFile());
232: if (getLog().isDebugEnabled())
233: getLog().debug(
234: "added to classpath: " + urls[i].getFile());
235: classpathStr.append(System.getProperty("path.separator"));
236: }
237:
238: Thread.currentThread().setContextClassLoader(webAppClassLoader);
239:
240: JspC jspc = new JspC();
241: jspc.setWebXmlFragment(webXmlFragment);
242: jspc.setUriroot(webAppSourceDirectory);
243:
244: jspc.setPackage(packageRoot);
245: jspc.setOutputDir(generatedClasses);
246: jspc.setValidateXml(validateXml);
247: jspc.setClassPath(classpathStr.toString());
248: jspc.setCompile(true);
249: jspc.setSmapSuppressed(suppressSmap);
250: jspc.setSmapDumped(!suppressSmap);
251: jspc.setJavaEncoding(javaEncoding);
252:
253: //Glassfish jspc only checks
254:
255: try {
256: jspc.setIgnoreJspFragmentErrors(ignoreJspFragmentErrors);
257: } catch (NoSuchMethodError e) {
258: getLog()
259: .debug(
260: "Tomcat Jasper does not support configuration option 'ignoreJspFragmentErrors': ignored");
261: }
262:
263: try {
264: if (schemaResourcePrefix != null)
265: jspc.setSchemaResourcePrefix(schemaResourcePrefix);
266: } catch (NoSuchMethodError e) {
267: getLog()
268: .debug(
269: "Tomcat Jasper does not support configuration option 'schemaResourcePrefix': ignored");
270: }
271: if (verbose)
272: jspc.setVerbose(99);
273: else
274: jspc.setVerbose(0);
275: jspc.execute();
276:
277: Thread.currentThread()
278: .setContextClassLoader(currentClassLoader);
279: }
280:
281: /**
282: * Until Jasper supports the option to generate the srcs in a
283: * different dir than the classes, this is the best we can do.
284: * @throws Exception
285: */
286: public void cleanupSrcs() throws Exception {
287: //delete the .java files - depending on keepGenerated setting
288: if (!keepSources) {
289: File generatedClassesDir = new File(generatedClasses);
290: File[] srcFiles = generatedClassesDir
291: .listFiles(new FilenameFilter() {
292: public boolean accept(File dir, String name) {
293: if (name == null)
294: return false;
295: if (name.trim().equals(""))
296: return false;
297: if (name.endsWith(".java"))
298: return true;
299:
300: return false;
301: }
302: });
303:
304: for (int i = 0; (srcFiles != null) && (i < srcFiles.length); i++) {
305: srcFiles[i].delete();
306: }
307: }
308: }
309:
310: /**
311: * Take the web fragment and put it inside a copy of the
312: * web.xml file from the webAppSourceDirectory.
313: *
314: * You can specify the insertion point by specifying
315: * the string in the insertionMarker configuration entry.
316: *
317: * If you dont specify the insertionMarker, then the fragment
318: * will be inserted at the end of the file just before the
319: * </webapp>
320: * @throws Exception
321: */
322: public void mergeWebXml() throws Exception {
323: if (mergeFragment) {
324: //open the src web.xml
325: File webXml = new File(webAppSourceDirectory
326: + "/WEB-INF/web.xml");
327: if (!webXml.exists()) {
328: getLog()
329: .info(
330: webAppSourceDirectory
331: + "/WEB-INF/web.xml does not exist, cannot merge with generated fragment");
332: return;
333: }
334:
335: File fragmentWebXml = new File(webXmlFragment);
336: if (!fragmentWebXml.exists()) {
337: getLog().info("No fragment web.xml file generated");
338: }
339: File mergedWebXml = new File(
340: fragmentWebXml.getParentFile(), "web.xml");
341: BufferedReader webXmlReader = new BufferedReader(
342: new FileReader(webXml));
343: PrintWriter mergedWebXmlWriter = new PrintWriter(
344: new FileWriter(mergedWebXml));
345:
346: //read up to the insertion marker or the </webapp> if there is no marker
347: boolean atInsertPoint = false;
348: boolean atEOF = false;
349: String marker = (insertionMarker == null
350: || insertionMarker.equals("") ? END_OF_WEBAPP
351: : insertionMarker);
352: while (!atInsertPoint && !atEOF) {
353: String line = webXmlReader.readLine();
354: if (line == null)
355: atEOF = true;
356: else if (line.indexOf(marker) >= 0) {
357: atInsertPoint = true;
358: } else {
359: mergedWebXmlWriter.println(line);
360: }
361: }
362:
363: //put in the generated fragment
364: BufferedReader fragmentWebXmlReader = new BufferedReader(
365: new FileReader(fragmentWebXml));
366: IO.copy(fragmentWebXmlReader, mergedWebXmlWriter);
367:
368: // if we inserted just before the </web-app>, put it back in
369: if (marker.equals(END_OF_WEBAPP))
370: mergedWebXmlWriter.println(END_OF_WEBAPP);
371:
372: // copy in the rest of the original web.xml file
373: IO.copy(webXmlReader, mergedWebXmlWriter);
374:
375: webXmlReader.close();
376: mergedWebXmlWriter.close();
377: fragmentWebXmlReader.close();
378: }
379: }
380:
381: private void prepare() throws Exception {
382: //For some reason JspC doesn't like it if the dir doesn't
383: //already exist and refuses to create the web.xml fragment
384: File generatedSourceDirectoryFile = new File(generatedClasses);
385: if (!generatedSourceDirectoryFile.exists())
386: generatedSourceDirectoryFile.mkdirs();
387: }
388:
389: /**
390: * Set up the execution classpath for Jasper.
391: *
392: * Put everything in the classesDirectory and all
393: * of the dependencies on the classpath.
394: *
395: * @param classLoader we use a Jetty WebAppClassLoader to load the classes
396: * @throws Exception
397: */
398: private void setUpClassPath(WebAppClassLoader classLoader)
399: throws Exception {
400: String classesDir = classesDirectory.getCanonicalPath();
401: classesDir = classesDir
402: + (classesDir.endsWith(File.pathSeparator) ? ""
403: : File.separator);
404: classLoader.addClassPath(classesDir);
405: if (getLog().isDebugEnabled())
406: getLog().debug(
407: "Adding to classpath classes dir: " + classesDir);
408:
409: for (Iterator iter = project.getArtifacts().iterator(); iter
410: .hasNext();) {
411: Artifact artifact = (Artifact) iter.next();
412:
413: // Include runtime and compile time libraries
414: if (!Artifact.SCOPE_TEST.equals(artifact.getScope())) {
415: String filePath = artifact.getFile().getCanonicalPath();
416: if (getLog().isDebugEnabled())
417: getLog().debug(
418: "Adding to classpath dependency file: "
419: + filePath);
420:
421: classLoader.addClassPath(filePath);
422: }
423: }
424: }
425: }
|