blob: 401e77347517b5a8f9f29521ab271948d596e720 [file] [log] [blame]
/*
* $Id$
*
* 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.struts2;
import com.opensymphony.xwork2.util.finder.ClassLoaderInterfaceDelegate;
import com.opensymphony.xwork2.util.finder.UrlSet;
import com.opensymphony.xwork2.util.finder.ClassLoaderInterface;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import com.opensymphony.xwork2.util.URLUtil;
import com.opensymphony.xwork2.ActionContext;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.xwork.StringUtils;
import org.apache.commons.lang.xwork.ObjectUtils;
import org.apache.struts2.compiler.MemoryClassLoader;
import org.apache.struts2.compiler.MemoryJavaFileObject;
import org.apache.struts2.jasper.JasperException;
import org.apache.struts2.jasper.JspC;
import org.apache.struts2.jasper.compiler.JspUtil;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletContext;
import javax.servlet.jsp.JspPage;
import javax.tools.*;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.*;
/**
* Uses jasper to extract a JSP from the classpath to a file and compile it. The classpath used for
* compilation is built by finding all the jar files using the current class loader (Thread), plus
* directories.
*/
public class JSPLoader {
private static final Logger LOG = LoggerFactory.getLogger(JSPLoader.class);
private static MemoryClassLoader classLoader = new MemoryClassLoader();
private static final String DEFAULT_PACKAGE = "org.apache.struts2.jsp";
public Servlet load(String location) throws Exception {
location = StringUtils.substringBeforeLast(location, "?");
if (LOG.isDebugEnabled()) {
LOG.debug("Compiling JSP [#0]", location);
}
//use Jasper to compile the JSP into java code
JspC jspC = compileJSP(location);
String source = jspC.getSourceCode();
//System.out.print(source);
String className = toClassName(location);
//use Java Compiler API to compile the java code into a class
//the tlds that were discovered are added (their jars) to the classpath
compileJava(className, source, jspC.getTldAbsolutePaths());
//load the class that was just built
Class clazz = Class.forName(className, false, classLoader);
return createServlet(clazz);
}
private String toClassName(String location) {
String className = StringUtils.substringBeforeLast(location, ".jsp");
className = JspUtil.makeJavaPackage(className);
return DEFAULT_PACKAGE + "." + className + "_jsp";
}
/**
* Creates and inits a servlet
*/
private Servlet createServlet(Class clazz) throws IllegalAccessException, InstantiationException, ServletException {
Servlet servlet = (Servlet) clazz.newInstance();
JSPServletConfig config = new JSPServletConfig(ServletActionContext.getServletContext());
servlet.init(config);
return servlet;
}
/**
* Compiles the given source code into java bytecode
*/
private void compileJava(String className, final String source, Set<String> extraClassPath) throws IOException {
if (LOG.isTraceEnabled())
LOG.trace("Compiling [#0], source: [#1]", className, source);
JavaCompiler compiler =
ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics =
new DiagnosticCollector<JavaFileObject>();
//the generated bytecode is fed to the class loader
JavaFileManager jfm = new
ForwardingJavaFileManager<StandardJavaFileManager>(
compiler.getStandardFileManager(diagnostics, null, null)) {
@Override
public JavaFileObject getJavaFileForOutput(Location location,
String name,
JavaFileObject.Kind kind,
FileObject sibling) throws IOException {
MemoryJavaFileObject fileObject = new MemoryJavaFileObject(name, kind);
classLoader.addMemoryJavaFileObject(name, fileObject);
return fileObject;
}
};
//read java source code from memory
String fileName = className.replace('.', '/') + ".java";
SimpleJavaFileObject sourceCodeObject = new SimpleJavaFileObject(toURI(fileName), JavaFileObject.Kind.SOURCE) {
@Override
public CharSequence getCharContent(boolean
ignoreEncodingErrors)
throws IOException, IllegalStateException,
UnsupportedOperationException {
return source;
}
};
//build classpath
//some entries will be added multiple times, hence the set
List<String> optionList = new ArrayList<String>();
Set<String> classPath = new HashSet<String>();
//find available jars
ClassLoaderInterface classLoaderInterface = getClassLoaderInterface();
UrlSet urlSet = new UrlSet(classLoaderInterface);
List<URL> urls = urlSet.getUrls();
if (urls != null) {
for (URL url : urls) {
File file = FileUtils.toFile(URLUtil.normalizeToFileProtocol(url));
classPath.add(file.getAbsolutePath());
}
}
//UrlSet searches for dirs that end in WEB-INF/classes, so when running test
//from maven, it won't find test-classes dir
//find directories in the classpath
Enumeration<URL> rootUrls = classLoaderInterface.getResources("");
while (rootUrls.hasMoreElements()) {
URL url = rootUrls.nextElement();
URL normalized = URLUtil.normalizeToFileProtocol(url);
if (normalized == null) {
//this means that it is directory, not a jar, double check
File file = FileUtils.toFile(url);
if (file.exists() && file.isDirectory())
classPath.add(file.getAbsolutePath());
}
}
//add extra classpath entries (jars where tlds were found will be here)
for (Iterator<String> iterator = extraClassPath.iterator(); iterator.hasNext();) {
String entry = iterator.next();
classPath.add(entry);
}
String classPathString = StringUtils.join(classPath, ";");
if (LOG.isDebugEnabled()) {
LOG.debug("Compiling [#0] with classpath [#1]", className, classPathString);
}
optionList.addAll(Arrays.asList("-classpath", classPathString));
//compile
JavaCompiler.CompilationTask task = compiler.getTask(
null, jfm, diagnostics, optionList, null,
Arrays.asList(sourceCodeObject));
if (!task.call()) {
throw new StrutsException("Compilation failed:" + diagnostics.getDiagnostics().get(0).toString());
}
}
protected String getJarUrl(Class clazz) {
ProtectionDomain protectionDomain = clazz.getProtectionDomain();
CodeSource codeSource = protectionDomain.getCodeSource();
URL loc = codeSource.getLocation();
File file = FileUtils.toFile(loc);
return file.getAbsolutePath();
}
private JspC compileJSP(String location) throws JasperException {
JspC jspC = new JspC();
jspC.setClassLoaderInterface(getClassLoaderInterface());
jspC.setCompile(false);
jspC.setJspFiles(location);
jspC.setPackage(DEFAULT_PACKAGE);
jspC.execute();
return jspC;
}
private ClassLoaderInterface getClassLoaderInterface() {
ClassLoaderInterface classLoaderInterface = null;
ServletContext ctx = ServletActionContext.getServletContext();
if (ctx != null)
classLoaderInterface = (ClassLoaderInterface) ctx.getAttribute(ClassLoaderInterface.CLASS_LOADER_INTERFACE);
return (ClassLoaderInterface) ObjectUtils.defaultIfNull(classLoaderInterface, new ClassLoaderInterfaceDelegate(JSPLoader.class.getClassLoader()));
}
private static URI toURI(String name) {
try {
return new URI(name);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}