| /* |
| * 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. |
| */ |
| package org.apache.sling.maven.jspc; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.jar.JarFile; |
| |
| import javax.servlet.ServletContext; |
| |
| import org.apache.commons.logging.impl.LogFactoryImpl; |
| import org.apache.commons.logging.impl.SimpleLog; |
| import org.apache.maven.artifact.Artifact; |
| import org.apache.maven.artifact.DependencyResolutionRequiredException; |
| import org.apache.maven.plugin.AbstractMojo; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugins.annotations.LifecyclePhase; |
| import org.apache.maven.plugins.annotations.Mojo; |
| import org.apache.maven.plugins.annotations.Parameter; |
| import org.apache.maven.plugins.annotations.ResolutionScope; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.sling.commons.classloader.ClassLoaderWriter; |
| import org.apache.sling.commons.compiler.JavaCompiler; |
| import org.apache.sling.commons.compiler.impl.EclipseJavaCompiler; |
| import org.apache.sling.scripting.jsp.jasper.IOProvider; |
| import org.apache.sling.scripting.jsp.jasper.JasperException; |
| import org.apache.sling.scripting.jsp.jasper.JspCompilationContext; |
| import org.apache.sling.scripting.jsp.jasper.Options; |
| import org.apache.sling.scripting.jsp.jasper.compiler.JspConfig; |
| import org.apache.sling.scripting.jsp.jasper.compiler.JspRuntimeContext; |
| import org.apache.sling.scripting.jsp.jasper.compiler.TagPluginManager; |
| import org.apache.sling.scripting.jsp.jasper.compiler.TldLocationsCache; |
| import org.codehaus.plexus.util.DirectoryScanner; |
| |
| /** |
| * The <code>JspcMojo</code> is implements the Sling Maven JspC goal |
| * <code>jspc</code> compiling JSP into the target and creating a component |
| * descriptor for Declarative Services to use the JSP with the help of the |
| * appropriate adapter as component. |
| */ |
| @Mojo(name = "jspc", defaultPhase = LifecyclePhase.COMPILE, requiresDependencyResolution = ResolutionScope.COMPILE) |
| public class JspcMojo extends AbstractMojo implements Options { |
| |
| /** |
| * The Maven project. |
| */ |
| @Parameter( defaultValue = "${project}", readonly = true ) |
| private MavenProject project; |
| |
| /** |
| * Location of JSP source files. |
| */ |
| @Parameter( property = "jspc.sourceDirectory", defaultValue = "${project.build.scriptSourceDirectory}") |
| private File sourceDirectory; |
| |
| /** |
| * Target folder for the compiled classes. |
| */ |
| @Parameter ( property = "jspc.outputDirectory", defaultValue = "${project.build.outputDirectory}") |
| private String outputDirectory; |
| |
| @Parameter ( property = "jspc.jasper.classdebuginfo", defaultValue = "true") |
| private boolean jasperClassDebugInfo; |
| |
| @Parameter ( property = "jspc.jasper.enablePooling", defaultValue = "true") |
| private boolean jasperEnablePooling; |
| |
| @Parameter ( property = "jspc.jasper.ieClassId", defaultValue = "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93") |
| private String jasperIeClassId; |
| |
| @Parameter ( property = "jspc.jasper.genStringAsCharArray", defaultValue = "false") |
| private boolean jasperGenStringAsCharArray; |
| |
| @Parameter ( property = "jspc.jasper.keepgenerated", defaultValue = "true") |
| private boolean jasperKeepGenerated; |
| |
| @Parameter ( property = "jspc.jasper.mappedfile", defaultValue = "true") |
| private boolean jasperMappedFile; |
| |
| @Parameter ( property = "jspc.jasper.trimSpaces", defaultValue = "false") |
| private boolean jasperTrimSpaces; |
| |
| @Parameter ( property = "jspc.failOnError", defaultValue = "true") |
| private boolean failOnError; |
| |
| @Parameter ( property = "jspc.showSuccess", defaultValue = "false") |
| private boolean showSuccess; |
| |
| @Parameter ( property = "jspc.compilerTargetVM", defaultValue = "1.5") |
| private String compilerTargetVM; |
| |
| @Parameter ( property = "jspc.compilerSourceVM", defaultValue = "1.5") |
| private String compilerSourceVM; |
| |
| /** |
| * Comma separated list of extensions of files to be compiled by the plugin. |
| * @deprecated Use the {@link #includes} filter instead. |
| */ |
| @Deprecated |
| @Parameter ( property = "jspc.jspFileExtensions", defaultValue = "jsp,jspx") |
| private String jspFileExtensions; |
| |
| /** |
| * @deprecated Due to internal refactoring, this is not longer supported. |
| */ |
| @Deprecated |
| @Parameter ( property = "jspc.servletPackage", defaultValue = "org.apache.jsp") |
| private String servletPackage; |
| |
| /** |
| * Included JSPs, defaults to <code>"**/*.jsp"</code> |
| */ |
| @Parameter |
| private String[] includes; |
| |
| /** |
| * Excluded JSPs, empty by default |
| */ |
| @Parameter |
| private String[] excludes; |
| |
| private String uriSourceRoot; |
| |
| private List<String> pages = new ArrayList<String>(); |
| |
| private ServletContext context; |
| |
| private JspRuntimeContext rctxt; |
| |
| private URLClassLoader loader = null; |
| |
| /** |
| * Cache for the TLD locations |
| */ |
| private TldLocationsCache tldLocationsCache = null; |
| |
| private JspConfig jspConfig = null; |
| |
| private TagPluginManager tagPluginManager = null; |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.maven.plugin.Mojo#execute() |
| */ |
| public void execute() throws MojoExecutionException { |
| |
| try { |
| uriSourceRoot = sourceDirectory.getCanonicalPath(); |
| } catch (Exception e) { |
| uriSourceRoot = sourceDirectory.getAbsolutePath(); |
| } |
| |
| // ensure output directory |
| File outputDirectoryFile = new File(outputDirectory); |
| if (!outputDirectoryFile.isDirectory()) { |
| if (outputDirectoryFile.exists()) { |
| throw new MojoExecutionException(outputDirectory |
| + " exists but is not a directory"); |
| } |
| |
| if (!outputDirectoryFile.mkdirs()) { |
| throw new MojoExecutionException( |
| "Cannot create output directory " + outputDirectory); |
| } |
| } |
| |
| // have the files compiled |
| String oldValue = System.getProperty(LogFactoryImpl.LOG_PROPERTY); |
| try { |
| // ensure the JSP Compiler does not try to use Log4J |
| System.setProperty(LogFactoryImpl.LOG_PROPERTY, |
| SimpleLog.class.getName()); |
| |
| executeInternal(); |
| } catch (JasperException je) { |
| getLog().error("Compilation Failure", je); |
| throw new MojoExecutionException(je.getMessage(), je); |
| } finally { |
| if (oldValue == null) { |
| System.clearProperty(LogFactoryImpl.LOG_PROPERTY); |
| } else { |
| System.setProperty(LogFactoryImpl.LOG_PROPERTY, oldValue); |
| } |
| } |
| |
| project.addCompileSourceRoot(outputDirectory); |
| } |
| |
| /** |
| * Locate all jsp files in the webapp. Used if no explicit jsps are |
| * specified. |
| */ |
| private void scanFiles(File base) { |
| |
| DirectoryScanner scanner = new DirectoryScanner(); |
| scanner.setBasedir(base); |
| scanner.setIncludes(includes); |
| scanner.setExcludes(excludes); |
| scanner.scan(); |
| |
| Collections.addAll(pages, scanner.getIncludedFiles()); |
| } |
| |
| /** |
| * Executes the compilation. |
| * |
| * @throws JasperException If an error occurs |
| */ |
| private void executeInternal() throws JasperException { |
| if (getLog().isDebugEnabled()) { |
| getLog().debug("execute() starting for " + pages.size() + " pages."); |
| } |
| |
| try { |
| if (context == null) { |
| initServletContext(); |
| } |
| |
| if (includes == null) { |
| includes = new String[]{ "**/*.jsp" }; |
| } |
| |
| // No explicit pages, we'll process all .jsp in the webapp |
| if (pages.size() == 0) { |
| scanFiles(sourceDirectory); |
| } |
| |
| File uriRootF = new File(uriSourceRoot); |
| if (!uriRootF.exists() || !uriRootF.isDirectory()) { |
| throw new JasperException("The source location '" |
| + uriSourceRoot + "' must be an existing directory"); |
| } |
| |
| for (String nextjsp : pages) { |
| File fjsp = new File(nextjsp); |
| if (!fjsp.isAbsolute()) { |
| fjsp = new File(uriRootF, nextjsp); |
| } |
| if (!fjsp.exists()) { |
| if (getLog().isWarnEnabled()) { |
| getLog().warn("JSP file " + fjsp + " does not exist"); |
| } |
| continue; |
| } |
| String s = fjsp.getAbsolutePath(); |
| if (s.startsWith(uriSourceRoot)) { |
| nextjsp = s.substring(uriSourceRoot.length()); |
| } |
| if (nextjsp.startsWith("." + File.separatorChar)) { |
| nextjsp = nextjsp.substring(2); |
| } |
| |
| processFile(nextjsp); |
| } |
| |
| } catch (JasperException je) { |
| Throwable rootCause = je; |
| while (rootCause instanceof JasperException |
| && ((JasperException) rootCause).getRootCause() != null) { |
| rootCause = ((JasperException) rootCause).getRootCause(); |
| } |
| if (rootCause != je) { |
| rootCause.printStackTrace(); |
| } |
| throw je; |
| |
| } catch (/* IO */Exception ioe) { |
| throw new JasperException(ioe); |
| } |
| } |
| |
| private void processFile(final String file) throws JasperException { |
| try { |
| final String jspUri = file.replace('\\', '/'); |
| final JspCompilationContext clctxt = new JspCompilationContext(jspUri, false, this, context, rctxt, false); |
| |
| JasperException error = clctxt.compile(); |
| if (error != null) { |
| throw error; |
| } |
| if (showSuccess) { |
| getLog().info("Built File: " + file); |
| } |
| |
| } catch (final JasperException je) { |
| Throwable rootCause = je; |
| while (rootCause instanceof JasperException |
| && ((JasperException) rootCause).getRootCause() != null) { |
| rootCause = ((JasperException) rootCause).getRootCause(); |
| } |
| if (rootCause != je) { |
| getLog().error("General problem compiling " + file, rootCause); |
| } |
| |
| // Bugzilla 35114. |
| if (failOnError) { |
| throw je; |
| } |
| |
| // just log otherwise |
| getLog().error(je.getMessage()); |
| |
| } catch (Exception e) { |
| throw new JasperException(e); |
| } |
| } |
| |
| // ---------- Additional Settings ------------------------------------------ |
| |
| private void initServletContext() throws IOException, DependencyResolutionRequiredException { |
| if (loader == null) { |
| initClassLoader(); |
| } |
| |
| context = new JspCServletContext(getLog(), new URL("file:" + uriSourceRoot.replace('\\', '/') + '/')); |
| tldLocationsCache = new JspCTldLocationsCache(context, true, loader); |
| |
| JavaCompiler compiler = new EclipseJavaCompiler(); |
| ClassLoaderWriter writer = new JspCClassLoaderWriter(loader, new File(outputDirectory)); |
| IOProvider ioProvider = new JspCIOProvider(loader, compiler, writer); |
| rctxt = new JspRuntimeContext(context, this, ioProvider); |
| jspConfig = new JspConfig(context); |
| tagPluginManager = new TagPluginManager(context); |
| } |
| |
| |
| /** |
| * Initializes the classloader as/if needed for the given compilation |
| * context. |
| * |
| * @throws IOException If an error occurs |
| */ |
| private void initClassLoader() throws IOException, |
| DependencyResolutionRequiredException { |
| List<URL> classPath = new ArrayList<URL>(); |
| |
| // add output directory to classpath |
| final String targetDirectory = project.getBuild().getOutputDirectory(); |
| classPath.add(new File(targetDirectory).toURI().toURL()); |
| |
| // add artifacts from project |
| Set<Artifact> artifacts = project.getDependencyArtifacts(); |
| for (Artifact a: artifacts) { |
| final String scope = a.getScope(); |
| if ("provided".equals(scope) || "runtime".equals(scope) || "compile".equals(scope)) { |
| // we need to exclude the javax.servlet.jsp API, otherwise the taglib parser causes problems (see note below) |
| if (containsProblematicPackage(a.getFile())) { |
| continue; |
| } |
| classPath.add(a.getFile().toURI().toURL()); |
| } |
| } |
| |
| if (getLog().isDebugEnabled()) { |
| getLog().debug("Compiler classpath:"); |
| for (URL u: classPath) { |
| getLog().debug(" " + u); |
| } |
| } |
| |
| // this is dangerous to use this classloader as parent as the compilation will depend on the classes provided |
| // in the plugin dependencies. but if we omit this, we get errors by not being able to load the TagExtraInfo classes. |
| // this is because this plugin uses classes from the javax.servlet.jsp that are also loaded via the TLDs. |
| loader = new URLClassLoader(classPath.toArray(new URL[classPath.size()]), this.getClass().getClassLoader()); |
| } |
| |
| /** |
| * Checks if the given jar file contains a problematic java API that should be excluded from the classloader. |
| * @param file the file to check |
| * @return {@code true} if it contains a problematic package |
| * @throws IOException if an error occurrs. |
| */ |
| private boolean containsProblematicPackage(File file) throws IOException { |
| JarFile jar = new JarFile(file); |
| boolean isJSPApi = jar.getEntry("/javax/servlet/jsp/JspPage.class") != null; |
| jar.close(); |
| return isJSPApi; |
| } |
| |
| // ---------- Options interface -------------------------------------------- |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#genStringAsCharArray() |
| */ |
| public boolean genStringAsCharArray() { |
| return jasperGenStringAsCharArray; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#getClassDebugInfo() |
| */ |
| public boolean getClassDebugInfo() { |
| return jasperClassDebugInfo; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#getCompiler() |
| */ |
| public String getCompiler() { |
| // use JDTCompiler, which is the default |
| return null; |
| } |
| |
| public String getCompilerClassName() { |
| // use JDTCompiler, which is the default |
| return null; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#getCompilerSourceVM() |
| */ |
| public String getCompilerSourceVM() { |
| return compilerSourceVM; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#getCompilerTargetVM() |
| */ |
| public String getCompilerTargetVM() { |
| return compilerTargetVM; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#getErrorOnUseBeanInvalidClassAttribute() |
| */ |
| public boolean getErrorOnUseBeanInvalidClassAttribute() { |
| // not configurable |
| return true; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#getFork() |
| */ |
| public boolean getFork() { |
| // certainly don't fork (not required anyway) |
| return false; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#getIeClassId() |
| */ |
| public String getIeClassId() { |
| return jasperIeClassId; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#getJavaEncoding() |
| */ |
| public String getJavaEncoding() { |
| return "UTF-8"; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#getJspConfig() |
| */ |
| public JspConfig getJspConfig() { |
| return jspConfig; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#getKeepGenerated() |
| */ |
| public boolean getKeepGenerated() { |
| return jasperKeepGenerated; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#getMappedFile() |
| */ |
| public boolean getMappedFile() { |
| return jasperMappedFile; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#getScratchDir() |
| */ |
| public String getScratchDir() { |
| return outputDirectory; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#getSendErrorToClient() |
| */ |
| public boolean getSendErrorToClient() { |
| // certainly output any problems |
| return true; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#getTagPluginManager() |
| */ |
| public TagPluginManager getTagPluginManager() { |
| return tagPluginManager; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#getTldLocationsCache() |
| */ |
| public TldLocationsCache getTldLocationsCache() { |
| return tldLocationsCache; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#getTrimSpaces() |
| */ |
| public boolean getTrimSpaces() { |
| return jasperTrimSpaces; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#isPoolingEnabled() |
| */ |
| public boolean isPoolingEnabled() { |
| return jasperEnablePooling; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#isSmapDumped() |
| */ |
| public boolean isSmapDumped() { |
| // always include the SMAP (optionally, limit to if debugging) |
| return true; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#isSmapSuppressed() |
| */ |
| public boolean isSmapSuppressed() { |
| // require SMAP |
| return false; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.jasper.Options#isXpoweredBy() |
| */ |
| public boolean isXpoweredBy() { |
| // no XpoweredBy setting please |
| return false; |
| } |
| |
| public boolean getDisplaySourceFragment() { |
| // Display the source fragment on errors for maven compilation |
| return true; |
| } |
| } |