/*
* Copyright 2012 The Solmix Project
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.gnu.org/licenses/
* or see the FSF site: http://www.fsf.org.
*/
package org.solmix.launch.webapp;
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.GenericServlet;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import org.solmix.launch.base.shared.LaunchConstants;
import org.solmix.launch.base.shared.Launcher;
import org.solmix.launch.base.shared.Loader;
import org.solmix.launch.base.shared.Notifiable;
/**
*
* @author ffz
* @version 0.0.1 2012-3-16
* @since 0.0.4
*/
public class SolmixServlet extends GenericServlet implements Notifiable
{
/**
* Auto generate serial UID.
*/
private static final long serialVersionUID = 1077520471637999526L;
/**
* The number times Sling will be tried to be started before giving up (value is 20). This number is chosen
* deliberately as generally Sling should start up smoothly. Whether any bundles within Sling start or not is not
* counted here.
*/
private static final int MAX_START_FAILURES = 20;
private Servlet internal;
private Thread startingThread;
private String home;
private Loader loader;
private int startFailureCounter;
private Map<String, String> properties;
private final String SOLMIX_HOME_PREFIX = "solmix.home.prefix";
private final String SOLMIX_HOME_PREFIX_DEFAULT = "resource";
public static final String DEFAULT_SOLMIX_HOME = "resource";
/**
* The starting delimiter of variable names (value is "${").
*/
private static final String DELIM_START = "${";
/**
* The ending delimiter of variable names (value is "}").
*/
private static final String DELIM_STOP = "}";
/**
* {@inheritDoc}
*
* @see javax.servlet.GenericServlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
*/
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
Servlet delegatee = internal;
if (delegatee != null) {
final HttpServletRequest request = (HttpServletRequest) req;
if (request.getPathInfo() == null && request.getServletPath() != null && request.getServletPath().endsWith(".jsp")) {
req = new HttpServletRequestWrapper(request) {
@Override
public String getPathInfo() {
return request.getServletPath();
}
@Override
public String getServletPath() {
return "";
}
};
}
delegatee.service(req, res);
} else if (startFailureCounter > MAX_START_FAILURES) {
((HttpServletResponse) res).sendError(HttpServletResponse.SC_NOT_FOUND);
} else {
startInternal(req);
}
}
@Override
public void init() {
this.properties = collectInitParameters();
putWebRootInSystem();
this.home = getSolmixHome(null);
if (this.home != null) {
startInternal();
} else {
log("Solmix Servlet cannot be started yet, because solmix.home is not defined yet");
}
log("Servlet " + getServletName() + " initialized");
}
/**
*
*/
private void putWebRootInSystem() {
String realroot = this.getServletContext().getRealPath("/");
System.setProperty(LaunchConstants.SOLMIX_WEB_ROOT, realroot);
}
/**
*
*/
private void startInternal() {
try {
File solmixBase = getSolmixBase(home);
this.loader = new Loader(new File(solmixBase, "lib")) {
@Override
protected void info(String msg) {
log(msg);
}
};
} catch (IllegalArgumentException iae) {
startupFailure(null, iae);
return;
}
synchronized (this) {
if (internal != null) {
log("Solmix Internal Servlet already started,nothing to do");
return;
} else if (startingThread != null) {
log("Solmix internal Servlet is being started by Thread " + startingThread);
return;
}
startingThread = Thread.currentThread();
Object object;
try {
object = loader.loadLaucher(LaunchConstants.DEFAULT_SOLMIX_SERVLET);
} catch (IllegalArgumentException iae) {
startupFailure("Cannot load Launcher Servlet " + LaunchConstants.DEFAULT_SOLMIX_SERVLET, iae);
return;
}
if (object instanceof Servlet) {
Servlet servletLauncher = (Servlet) object;
if (object instanceof Launcher) {
Launcher launcher = (Launcher) object;
launcher.setNotifiable(this);
launcher.setCommandLine(properties);
launcher.setSolmixHome(home);
}
SolmixSessionListener.startDelegate(servletLauncher.getClass().getClassLoader());
try {
log("starting framework...");
servletLauncher.init(getServletConfig());
this.internal = servletLauncher;
this.startFailureCounter = 0;
log("Startup completed...");
} catch (ServletException e) {
startupFailure(null, e);
}
}
synchronized (this) {
startingThread = null;
}
}
}
/**
* @param home
* @return
*/
private File getSolmixBase(String home) {
String solmixBase = properties.get(LaunchConstants.SOLMIX_BASE);
if (solmixBase == null || solmixBase.length() == 0) {
properties.put(LaunchConstants.SOLMIX_BASE, home);
return new File(home);
}
File baseDir = new File(solmixBase);
if (!baseDir.isAbsolute()) {
baseDir = new File(home, solmixBase);
}
properties.put(LaunchConstants.SOLMIX_BASE, baseDir.getAbsolutePath());
return baseDir;
}
/**
* 获取solmix.home值 <br>
* 首先从servlet initial configuration中获取 <br>
* 然后从servlet context configuration中获取<br>
* 任然没有?自动生成
*
* @param request
* @return home
*/
private String getSolmixHome(HttpServletRequest request) {
String source = null;
// access config and context to be able to log the solmix.home source
// 1. servlet config parameter
String home = getServletConfig().getInitParameter(LaunchConstants.SOLMIX_HOME);
if (home != null) {
source = "servlet parameter solmix.home";
} else {
// 2. servlet context parameter
home = getServletContext().getInitParameter(LaunchConstants.SOLMIX_HOME);
if (home != null) {
source = "servlet context parameter solmix.home";
} else {
// 3. servlet context path (Servlet API 2.5 and later)
try {
String contextPath = getServletContext().getContextPath();
home = toSolmixHome(contextPath);
source = "servlet context path";
} catch (NoSuchMethodError nsme) {
// 4.servlet context path (Servlet API 2.4 and earlier)
if (request != null) {
String contextPath = request.getContextPath();
home = toSolmixHome(contextPath);
source = "servlet context path (from request)";
} else {
log("ServletContext path not available here, delaying startup until first request");
return null;
}
}
}
}
// substitute any ${...} references and make absolute
home = substVars(home);
log("Setting solmix.home=" + home + " (" + source + ")");
return home;
}
/**
* @param home
* @return
*/
private String substVars(String val) {
if (val.contains(DELIM_START)) {
return substVars(val, null, null, properties);
}
return val;
}
private static String substVars(String val, String currentKey, Map<String, String> cycleMap, Map<String, String> configProps)
throws IllegalArgumentException {
// If there is currently no cycle map, then create
// one for detecting cycles for this invocation.
if (cycleMap == null) {
cycleMap = new HashMap<String, String>();
}
// Put the current key in the cycle map.
cycleMap.put(currentKey, currentKey);
// Assume we have a value that is something like:
// "leading ${foo.${bar}} middle ${baz} trailing"
// Find the first ending '}' variable delimiter, which
// will correspond to the first deepest nested variable
// placeholder.
int stopDelim = val.indexOf(DELIM_STOP);
// Find the matching starting "${" variable delimiter
// by looping until we find a start delimiter that is
// greater than the stop delimiter we have found.
int startDelim = val.indexOf(DELIM_START);
while (stopDelim >= 0) {
int idx = val.indexOf(DELIM_START, startDelim + DELIM_START.length());
if ((idx < 0) || (idx > stopDelim)) {
break;
} else if (idx < stopDelim) {
startDelim = idx;
}
}
// If we do not have a start or stop delimiter, then just
// return the existing value.
if ((startDelim < 0) && (stopDelim < 0)) {
return val;
}
// At this point, we found a stop delimiter without a start,
// so throw an exception.
else if (((startDelim < 0) || (startDelim > stopDelim)) && (stopDelim >= 0)) {
throw new IllegalArgumentException("stop delimiter with no start delimiter: " + val);
}
// At this point, we have found a variable placeholder so
// we must perform a variable substitution on it.
// Using the start and stop delimiter indices, extract
// the first, deepest nested variable placeholder.
String variable = val.substring(startDelim + DELIM_START.length(), stopDelim);
// Verify that this is not a recursive variable reference.
if (cycleMap.get(variable) != null) {
throw new IllegalArgumentException("recursive variable reference: " + variable);
}
// Get the value of the deepest nested variable placeholder.
// Try to configuration properties first.
String substValue = (configProps != null) ? configProps.get(variable) : null;
if (substValue == null) {
// Ignore unknown property values.
substValue = System.getProperty(variable, "");
}
// Remove the found variable from the cycle map, since
// it may appear more than once in the value and we don't
// want such situations to appear as a recursive reference.
cycleMap.remove(variable);
// Append the leading characters, the substituted value of
// the variable, and the trailing characters to get the new
// value.
val = val.substring(0, startDelim) + substValue + val.substring(stopDelim + DELIM_STOP.length(), val.length());
// Now perform substitution again, since there could still
// be substitutions to make.
val = substVars(val, currentKey, cycleMap, configProps);
// Return the value.
return val;
}
/**
* 返回根目录,如果没有设置,那么就再WEB-INF下加载。
*
* @param contextPath
* @return
*/
private String toSolmixHome(String contextPath) {
String prefix = System.getProperty(SOLMIX_HOME_PREFIX, SOLMIX_HOME_PREFIX_DEFAULT);
if (prefix.endsWith("/")) {
prefix = prefix.substring(0, prefix.length() - 1);
}
prefix = "/WEB-INF/" + prefix;
String scAbsolutePath = getServletContext().getRealPath("/");
if(scAbsolutePath.endsWith("/"))
scAbsolutePath = scAbsolutePath.substring(0, prefix.length() - 1);
return scAbsolutePath + prefix;
}
@Override
public String getServletInfo() {
if (internal != null) {
return internal.getServletInfo();
}
return "solmix Launcher Proxy";
}
@SuppressWarnings("unchecked")
private Map<String, String> collectInitParameters() {
HashMap<String, String> props = new HashMap<String, String>();
for (Enumeration<String> keys = getServletContext().getInitParameterNames(); keys.hasMoreElements();) {
String key = keys.nextElement();
props.put(key, getServletContext().getInitParameter(key));
}
for (Enumeration<String> keys = getServletConfig().getInitParameterNames(); keys.hasMoreElements();) {
String key = keys.nextElement();
props.put(key, getServletConfig().getInitParameter(key));
}
return props;
}
/**
* {@inheritDoc}
*
* @see org.solmix.loaders.shared.Notifiable#stopped()
*/
@Override
public void stopped() {
log("Solmix framework has been stopped");
internal = null;
SolmixSessionListener.stopDelegatee();
}
/**
* {@inheritDoc}
*
* @see org.solmix.loaders.shared.Notifiable#updated(java.io.File)
*/
@Override
public void updated(File updateFile) {
// drop reference to be able to restart.
synchronized (this) {
if (this.startingThread == null) {
internal = null;
SolmixSessionListener.stopDelegatee();
}
}
// ensure we have a VM as clean as possible
loader.cleanupVM();
}
private void startInternal(final ServletRequest request) {
if (startingThread == null) {
home = getSolmixHome((HttpServletRequest) request);
Thread starter = new Thread(new Runnable() {
@Override
public void run() {
startInternal();
}
}, "SolmixStarter_" + System.currentTimeMillis());
starter.setDaemon(true);
starter.start();
}
}
/**
* @param string
* @param ioe
*/
private void startupFailure(String message, Throwable cause) {
if (message == null) {
message = "Failed to start Solmix Servlet in" + home;
}
// unwrap to get the real cause
while (cause.getCause() != null) {
cause = cause.getCause();
}
// log it now and increase the failure counter
log(message, cause);
startFailureCounter++;
// ensure the startingSling fields is not set
synchronized (this) {
startingThread = null;
}
}
@Override
public void destroy() {
SolmixSessionListener.stopDelegatee();
if (internal != null) {
internal.destroy();
}
internal = null;
home = null;
loader = null;
}
}