tomcat support!

This commit is contained in:
benjobs 2017-12-11 21:19:22 +08:00
parent 084d2b26e7
commit bd83fbdcac
7 changed files with 832 additions and 0 deletions

View File

@ -0,0 +1,205 @@
package org.nutz.boot.starter.tomcat;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nutz.lang.Files;
import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.jar.JarFile;
public abstract class AbstractServletContainerFactory {
protected final Log logger = LogFactory.getLog(getClass());
private static final String[] COMMON_DOC_ROOTS = { "webapp", "public","static" };
private File documentRoot;
public AbstractServletContainerFactory() {
super();
}
public File getDocumentRoot() {
return this.documentRoot;
}
/**
* Returns the absolute document root when it points to a valid directory, logging a
* warning and returning {@code null} otherwise.
* @return the valid document root
*/
protected final File getValidDocumentRoot(String staticPath) {
File file = getDocumentRoot();
// If document root not explicitly set see if we are running from a war archive
file = file != null ? file : getWarFileDocumentRoot();
// If not a war archive maybe it is an exploded war
file = file != null ? file : getExplodedWarFileDocumentRoot();
// Or maybe there is a document root in a well-known location
file = file != null ? file : getCommonDocumentRoot();
//static file
file = file != null ? file : Files.findFile(staticPath);
if (file == null && this.logger.isDebugEnabled()) {
this.logger
.debug("None of the document roots " + Arrays.asList(COMMON_DOC_ROOTS)
+ " point to a directory and will be ignored.");
}
else if (this.logger.isDebugEnabled()) {
this.logger.debug("Document root: " + file);
}
return file;
}
private File getExplodedWarFileDocumentRoot() {
return getExplodedWarFileDocumentRoot(getCodeSourceArchive());
}
protected List<URL> getUrlsOfJarsWithMetaInfResources() {
ClassLoader classLoader = getClass().getClassLoader();
List<URL> staticResourceUrls = new ArrayList<URL>();
if (classLoader instanceof URLClassLoader) {
for (URL url : ((URLClassLoader) classLoader).getURLs()) {
try {
if ("file".equals(url.getProtocol())) {
File file = new File(url.getFile());
if (file.isDirectory()
&& new File(file, "META-INF/resources").isDirectory()) {
staticResourceUrls.add(url);
}
else if (isResourcesJar(file)) {
staticResourceUrls.add(url);
}
}
else {
URLConnection connection = url.openConnection();
if (connection instanceof JarURLConnection) {
if (isResourcesJar((JarURLConnection) connection)) {
staticResourceUrls.add(url);
}
}
}
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
}
return staticResourceUrls;
}
private boolean isResourcesJar(JarURLConnection connection) {
try {
return isResourcesJar(connection.getJarFile());
}
catch (IOException ex) {
return false;
}
}
private boolean isResourcesJar(File file) {
try {
return isResourcesJar(new JarFile(file));
}
catch (IOException ex) {
return false;
}
}
private boolean isResourcesJar(JarFile jar) throws IOException {
try {
return jar.getName().endsWith(".jar")
&& (jar.getJarEntry("META-INF/resources") != null);
}
finally {
jar.close();
}
}
File getExplodedWarFileDocumentRoot(File codeSourceFile) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Code archive: " + codeSourceFile);
}
if (codeSourceFile != null && codeSourceFile.exists()) {
String path = codeSourceFile.getAbsolutePath();
int webInfPathIndex = path
.indexOf(File.separatorChar + "WEB-INF" + File.separatorChar);
if (webInfPathIndex >= 0) {
path = path.substring(0, webInfPathIndex);
return new File(path);
}
}
return null;
}
private File getWarFileDocumentRoot() {
return getArchiveFileDocumentRoot(".war");
}
private File getArchiveFileDocumentRoot(String extension) {
File file = getCodeSourceArchive();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Code archive: " + file);
}
if (file != null && file.exists() && !file.isDirectory()
&& file.getName().toLowerCase().endsWith(extension)) {
return file.getAbsoluteFile();
}
return null;
}
private File getCommonDocumentRoot() {
for (String commonDocRoot : COMMON_DOC_ROOTS) {
URL url = Thread.currentThread().getContextClassLoader().getResource(commonDocRoot);
if (url!=null) {
File root = new File(url.getPath());
if (root.exists() && root.isDirectory()) {
return root.getAbsoluteFile();
}
}
}
return null;
}
private File getCodeSourceArchive() {
return getCodeSourceArchive(getClass().getProtectionDomain().getCodeSource());
}
File getCodeSourceArchive(CodeSource codeSource) {
try {
URL location = (codeSource == null ? null : codeSource.getLocation());
if (location == null) {
return null;
}
String path;
URLConnection connection = location.openConnection();
if (connection instanceof JarURLConnection) {
path = ((JarURLConnection) connection).getJarFile().getName();
}
else {
path = location.toURI().getPath();
}
if (path.contains("!/")) {
path = path.substring(0, path.indexOf("!/"));
}
return new File(path);
}
catch (Exception ex) {
return null;
}
}
protected final File getValidSessionStoreDir(boolean mkdirs) {
return new ApplicationTemp().getDir("servlet-sessions");
}
}

View File

@ -0,0 +1,158 @@
package org.nutz.boot.starter.tomcat;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.Enumeration;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
public class ApplicationHome {
private final File source;
private final File dir;
/**
* Create a new {@link ApplicationHome} instance.
*/
public ApplicationHome() {
this(null);
}
/**
* Create a new {@link ApplicationHome} instance for the specified source class.
* @param sourceClass the source class or {@code null}
*/
public ApplicationHome(Class<?> sourceClass) {
this.source = findSource(sourceClass == null ? getStartClass() : sourceClass);
this.dir = findHomeDir(this.source);
}
private Class<?> getStartClass() {
try {
ClassLoader classLoader = getClass().getClassLoader();
return getStartClass(classLoader.getResources("META-INF/MANIFEST.MF"));
}
catch (Exception ex) {
return null;
}
}
private Class<?> getStartClass(Enumeration<URL> manifestResources) {
while (manifestResources.hasMoreElements()) {
try {
InputStream inputStream = manifestResources.nextElement().openStream();
try {
Manifest manifest = new Manifest(inputStream);
String startClass = manifest.getMainAttributes()
.getValue("Start-Class");
if (startClass != null) {
return ClassUtils.forName(startClass,
getClass().getClassLoader());
}
}
finally {
inputStream.close();
}
}
catch (Exception ex) {
}
}
return null;
}
private File findSource(Class<?> sourceClass) {
try {
ProtectionDomain domain = (sourceClass == null ? null
: sourceClass.getProtectionDomain());
CodeSource codeSource = (domain == null ? null : domain.getCodeSource());
URL location = (codeSource == null ? null : codeSource.getLocation());
File source = (location == null ? null : findSource(location));
if (source != null && source.exists() && !isUnitTest()) {
return source.getAbsoluteFile();
}
return null;
}
catch (Exception ex) {
return null;
}
}
private boolean isUnitTest() {
try {
for (StackTraceElement element : Thread.currentThread().getStackTrace()) {
if (element.getClassName().startsWith("org.junit.")) {
return true;
}
}
}
catch (Exception ex) {
}
return false;
}
private File findSource(URL location) throws IOException {
URLConnection connection = location.openConnection();
if (connection instanceof JarURLConnection) {
return getRootJarFile(((JarURLConnection) connection).getJarFile());
}
return new File(location.getPath());
}
private File getRootJarFile(JarFile jarFile) {
String name = jarFile.getName();
int separator = name.indexOf("!/");
if (separator > 0) {
name = name.substring(0, separator);
}
return new File(name);
}
private File findHomeDir(File source) {
File homeDir = source;
homeDir = (homeDir == null ? findDefaultHomeDir() : homeDir);
if (homeDir.isFile()) {
homeDir = homeDir.getParentFile();
}
homeDir = (homeDir.exists() ? homeDir : new File("."));
return homeDir.getAbsoluteFile();
}
private File findDefaultHomeDir() {
String userDir = System.getProperty("user.dir");
return new File(StringUtils.hasLength(userDir) ? userDir : ".");
}
/**
* Returns the underlying source used to find the home directory. This is usually the
* jar file or a directory. Can return {@code null} if the source cannot be
* determined.
* @return the underlying source or {@code null}
*/
public File getSource() {
return this.source;
}
/**
* Returns the application home directory.
* @return the home directory (never {@code null})
*/
public File getDir() {
return this.dir;
}
@Override
public String toString() {
return getDir().toString();
}
}

View File

@ -0,0 +1,116 @@
package org.nutz.boot.starter.tomcat;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.io.File;
import java.security.MessageDigest;
public class ApplicationTemp {
private static final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
private final Class<?> sourceClass;
private volatile File dir;
/**
* Create a new {@link ApplicationTemp} instance.
*/
public ApplicationTemp() {
this(null);
}
/**
* Create a new {@link ApplicationTemp} instance for the specified source class.
* @param sourceClass the source class or {@code null}
*/
public ApplicationTemp(Class<?> sourceClass) {
this.sourceClass = sourceClass;
}
@Override
public String toString() {
return getDir().getAbsolutePath();
}
/**
* Return a sub-directory of the application temp.
* @param subDir the sub-directory name
* @return a sub-directory
*/
public File getDir(String subDir) {
File dir = new File(getDir(), subDir);
dir.mkdirs();
return dir;
}
/**
* Return the directory to be used for application specific temp files.
* @return the application temp directory
*/
public File getDir() {
if (this.dir == null) {
synchronized (this) {
byte[] hash = generateHash(this.sourceClass);
this.dir = new File(getTempDirectory(), toHexString(hash));
this.dir.mkdirs();
Assert.state(this.dir.exists(),
"Unable to create temp directory " + this.dir);
}
}
return this.dir;
}
private File getTempDirectory() {
String property = System.getProperty("java.io.tmpdir");
Assert.state(StringUtils.hasLength(property), "No 'java.io.tmpdir' property set");
File file = new File(property);
Assert.state(file.exists(), "Temp directory" + file + " does not exist");
Assert.state(file.isDirectory(), "Temp location " + file + " is not a directory");
return file;
}
private byte[] generateHash(Class<?> sourceClass) {
ApplicationHome home = new ApplicationHome(sourceClass);
MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA-1");
update(digest, home.getSource());
update(digest, home.getDir());
update(digest, System.getProperty("user.dir"));
update(digest, System.getProperty("java.home"));
update(digest, System.getProperty("java.class.path"));
update(digest, System.getProperty("sun.java.command"));
update(digest, System.getProperty("sun.boot.class.path"));
return digest.digest();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
private void update(MessageDigest digest, Object source) {
if (source != null) {
digest.update(getUpdateSourceBytes(source));
}
}
private byte[] getUpdateSourceBytes(Object source) {
if (source instanceof File) {
return getUpdateSourceBytes(((File) source).getAbsolutePath());
}
return source.toString().getBytes();
}
private String toHexString(byte[] bytes) {
char[] hex = new char[bytes.length * 2];
for (int i = 0; i < bytes.length; i++) {
int b = bytes[i] & 0xFF;
hex[i * 2] = HEX_CHARS[b >>> 4];
hex[i * 2 + 1] = HEX_CHARS[b & 0x0F];
}
return new String(hex);
}
}

View File

@ -0,0 +1,15 @@
package org.nutz.boot.starter.tomcat;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.util.StandardSessionIdGenerator;
class LazySessionIdGenerator extends StandardSessionIdGenerator {
@Override
protected void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
}
}

View File

@ -0,0 +1,69 @@
package org.nutz.boot.starter.tomcat;
import org.apache.catalina.Container;
import org.apache.catalina.Manager;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.session.ManagerBase;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
public class TomcatContext extends StandardContext {
private TomcatStarter starter;
private final boolean overrideLoadOnStart;
TomcatContext() {
this.overrideLoadOnStart = ReflectionUtils
.findMethod(StandardContext.class, "loadOnStartup", Container[].class)
.getReturnType() == boolean.class;
}
@Override
public boolean loadOnStartup(Container[] children) {
if (this.overrideLoadOnStart) {
return true;
}
return super.loadOnStartup(children);
}
@Override
public void setManager(Manager manager) {
if (manager instanceof ManagerBase) {
manager.setSessionIdGenerator(new LazySessionIdGenerator());
}
super.setManager(manager);
}
public void deferredLoadOnStartup() {
// Some older Servlet frameworks (e.g. Struts, BIRT) use the Thread context class
// loader to create servlet instances in this phase. If they do that and then try
// to initialize them later the class loader may have changed, so wrap the call to
// loadOnStartup in what we think its going to be the main webapp classloader at
// runtime.
ClassLoader classLoader = getLoader().getClassLoader();
ClassLoader existingLoader = null;
if (classLoader != null) {
existingLoader = ClassUtils.overrideThreadContextClassLoader(classLoader);
}
if (this.overrideLoadOnStart) {
// Earlier versions of Tomcat used a version that returned void. If that
// version is used our overridden loadOnStart method won't have been called
// and the original will have already run.
super.loadOnStartup(findChildren());
}
if (existingLoader != null) {
ClassUtils.overrideThreadContextClassLoader(existingLoader);
}
}
public void setStarter(TomcatStarter starter) {
this.starter = starter;
}
public TomcatStarter getStarter() {
return this.starter;
}
}

View File

@ -0,0 +1,172 @@
package org.nutz.boot.starter.tomcat;
import org.apache.catalina.Context;
import org.apache.catalina.WebResourceRoot.ResourceSetType;
import org.apache.catalina.core.StandardContext;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import javax.naming.directory.DirContext;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
public abstract class TomcatResources {
private final Context context;
TomcatResources(Context context) {
this.context = context;
}
void addResourceJars(List<URL> resourceJarUrls) {
for (URL url : resourceJarUrls) {
String file = url.getFile();
if (file.endsWith(".jar") || file.endsWith(".jar!/")) {
String jar = url.toString();
if (!jar.startsWith("jar:")) {
// A jar file in the file system. Convert to Jar URL.
jar = "jar:" + jar + "!/";
}
addJar(jar);
}
else {
addDir(file, url);
}
}
}
protected final Context getContext() {
return this.context;
}
/**
* Called to add a JAR to the resources.
* @param jar the URL spec for the jar
*/
protected abstract void addJar(String jar);
/**
* Called to add a dir to the resource.
* @param dir the dir
* @param url the URL
*/
protected abstract void addDir(String dir, URL url);
/**
* Return a {@link TomcatResources} instance for the currently running Tomcat version.
* @param context the tomcat context
* @return a {@link TomcatResources} instance.
*/
public static TomcatResources get(Context context) {
if (ClassUtils.isPresent("org.apache.catalina.deploy.ErrorPage", null)) {
return new Tomcat7Resources(context);
}
return new Tomcat8Resources(context);
}
/**
* {@link TomcatResources} for Tomcat 7.
*/
private static class Tomcat7Resources extends TomcatResources {
private final Method addResourceJarUrlMethod;
Tomcat7Resources(Context context) {
super(context);
this.addResourceJarUrlMethod = ReflectionUtils.findMethod(context.getClass(),
"addResourceJarUrl", URL.class);
}
@Override
protected void addJar(String jar) {
URL url = getJarUrl(jar);
if (url != null) {
try {
this.addResourceJarUrlMethod.invoke(getContext(), url);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}
private URL getJarUrl(String jar) {
try {
return new URL(jar);
}
catch (MalformedURLException ex) {
// Ignore
return null;
}
}
@Override
protected void addDir(String dir, URL url) {
if (getContext() instanceof StandardContext) {
try {
Class<?> fileDirContextClass = Class
.forName("org.apache.naming.resources.FileDirContext");
Method setDocBaseMethod = ReflectionUtils
.findMethod(fileDirContextClass, "setDocBase", String.class);
Object fileDirContext = fileDirContextClass.newInstance();
setDocBaseMethod.invoke(fileDirContext, dir);
Method addResourcesDirContextMethod = ReflectionUtils.findMethod(
StandardContext.class, "addResourcesDirContext",
DirContext.class);
addResourcesDirContextMethod.invoke(getContext(), fileDirContext);
}
catch (Exception ex) {
throw new IllegalStateException("Tomcat 7 reflection failed", ex);
}
}
}
}
/**
* {@link TomcatResources} for Tomcat 8.
*/
static class Tomcat8Resources extends TomcatResources {
Tomcat8Resources(Context context) {
super(context);
}
@Override
protected void addJar(String jar) {
addResourceSet(jar);
}
@Override
protected void addDir(String dir, URL url) {
addResourceSet(url.toString());
}
private void addResourceSet(String resource) {
try {
if (isInsideNestedJar(resource)) {
// It's a nested jar but we now don't want the suffix because Tomcat
// is going to try and locate it as a root URL (not the resource
// inside it)
resource = resource.substring(0, resource.length() - 2);
}
URL url = new URL(resource);
String path = "/META-INF/resources";
getContext().getResources().createWebResourceSet(
ResourceSetType.RESOURCE_JAR, "/", url, path);
}
catch (Exception ex) {
// Ignore (probably not a directory)
}
}
private boolean isInsideNestedJar(String dir) {
return dir.indexOf("!/") < dir.lastIndexOf("!/");
}
}
}

View File

@ -0,0 +1,97 @@
package org.nutz.boot.starter.tomcat;
import org.apache.catalina.loader.WebappClassLoader;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.net.URL;
public class TomcatWebappClassLoader extends WebappClassLoader {
private static final Log logger = LogFactory.getLog(TomcatWebappClassLoader.class);
public TomcatWebappClassLoader() {
super();
}
public TomcatWebappClassLoader(ClassLoader parent) {
super(parent);
}
@Override
public synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Class<?> result = findExistingLoadedClass(name);
result = (result == null ? doLoadClass(name) : result);
if (result == null) {
throw new ClassNotFoundException(name);
}
return resolveIfNecessary(result, resolve);
}
private Class<?> findExistingLoadedClass(String name) {
Class<?> resultClass = findLoadedClass0(name);
resultClass = (resultClass == null ? findLoadedClass(name) : resultClass);
return resultClass;
}
private Class<?> doLoadClass(String name) throws ClassNotFoundException {
checkPackageAccess(name);
if ((this.delegate || filter(name, true))) {
Class<?> result = loadFromParent(name);
return (result == null ? findClassIgnoringNotFound(name) : result);
}
Class<?> result = findClassIgnoringNotFound(name);
return (result == null ? loadFromParent(name) : result);
}
private Class<?> resolveIfNecessary(Class<?> resultClass, boolean resolve) {
if (resolve) {
resolveClass(resultClass);
}
return (resultClass);
}
@Override
protected void addURL(URL url) {
// Ignore URLs added by the Tomcat 8 implementation (see gh-919)
if (logger.isTraceEnabled()) {
logger.trace("Ignoring request to add " + url + " to the tomcat classloader");
}
}
private Class<?> loadFromParent(String name) {
if (this.parent == null) {
return null;
}
try {
return Class.forName(name, false, this.parent);
}
catch (ClassNotFoundException ex) {
return null;
}
}
private Class<?> findClassIgnoringNotFound(String name) {
try {
return findClass(name);
}
catch (ClassNotFoundException ex) {
return null;
}
}
private void checkPackageAccess(String name) throws ClassNotFoundException {
if (this.securityManager != null && name.lastIndexOf('.') >= 0) {
try {
this.securityManager
.checkPackageAccess(name.substring(0, name.lastIndexOf('.')));
}
catch (SecurityException ex) {
throw new ClassNotFoundException("Security Violation, attempt to use "
+ "Restricted Class: " + name, ex);
}
}
}
}