package org.emrys.webosgi.core.resource.servlet;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.emrys.webosgi.common.ComActivator;
import org.emrys.webosgi.common.util.FileUtil;
import org.emrys.webosgi.core.WebComActivator;
import org.emrys.webosgi.core.handlers.AbstractFwkReqeustHandler;
import org.emrys.webosgi.core.handlers.IFwkHandlerChain;
import org.emrys.webosgi.core.internal.FwkRuntime;
import org.emrys.webosgi.core.jeewrappers.BundledHttpServletRequestWrapper;
import org.emrys.webosgi.core.jeewrappers.HttpServletResponseWrapper;
import org.emrys.webosgi.core.jsp.JspServletPool;
import org.emrys.webosgi.core.jsp.JasperServletWrapper;
import org.emrys.webosgi.core.resource.ResroucesCom;
import org.emrys.webosgi.core.resource.WebResComActivator;
import org.emrys.webosgi.core.resource.extension.BaseResource;
import org.emrys.webosgi.core.resource.extension.DefinesRoot;
import org.emrys.webosgi.core.resource.extension.IResourceVisitController;
import org.emrys.webosgi.core.resource.extension.ResFile;
import org.emrys.webosgi.core.resource.extension.ResFilter;
import org.emrys.webosgi.core.resource.extension.ResFolder;
import org.emrys.webosgi.core.resource.extension.ResPublishSVCRegister;
import org.emrys.webosgi.core.runtime.OSGiWebContainer;
import org.emrys.webosgi.core.service.IOSGiWebContainer;
import org.emrys.webosgi.core.service.IWABServletContext;
import org.emrys.webosgi.core.service.IWebApplication;
import org.emrys.webosgi.core.util.NamedThreadLocal;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
/**
* The Handler process request for web resources.
*
* @author Leo Chang
* @version 2010-6-1
*/
public class ResGetSvcHandler extends AbstractFwkReqeustHandler {
/**
*
*/
private static final long serialVersionUID = -8681318899660006305L;
/**
* If returned the folder information, the response's head content type
* should be as following.
*/
public static final String FOLDER_CONTENT_TYPE = "report/folder";// "text/plain"
// "text/html";
/**
* The request thread local variant: the current servlet request.
*/
private static final String RESPONSE_REQ = "REQ";
/**
* The request thread local variant: the current servlet response.
*/
private static final String RESPONSE_RESP = "RRESP";
/**
* The request thread local variant: the current bundle.
*/
private static final String RESPONSE_CUR_BUNDLE = "CURBUNDLE";
/**
* The request thread local variant: the result inputstream.
*/
private static final String RESPONSE_INPUT = "RIN";
/**
* The request thread local variant: the result folder report file.
*/
private static final String RESPONSE_FOLDER_REPORT_FILE = "RFRF";
/**
* The request thread local variant: whether the found resource is a folder.
*/
private static final String RESPONSE_IS_FOLDER = "RIF";
/**
* The request thread local variant: whether the found resource is a dynimal
* file and has been processed.
*/
private static final String RESPONSE_DYNIMIC_FILE = "RDF";
/**
* The request thread local variant: the current searching resource root.
*/
private static final String RESPONSE_CUR_VISIT_CTRL = "RCRT";
/**
* The request thread local variant: the temporary file's prefix.
*/
private static final String STATIC_FILE_NAME = "STFNAME";
/**
* The request thread lcoal variant: whether the resource not modified after
* the last request. If ture, this serlvet will return http 304 status.
*/
private static final String RES_NO_MODIFIED = "RES_NO_MODIFIED";
/**
* the folder report's charset.
*/
private static final String TEXT_CHARSET = "UTF-8";
/**
* The Resource publish register.
*/
private final ResPublishSVCRegister resRegister;
/**
* The OSGiWebContainer's reference.
*/
private final IOSGiWebContainer webContainer;
/**
* The start time of this servlet. This time be used as the resource last
* modified time.
*/
private long startTimeMillis = -1L;
/**
* Because the Servlet is a singleton instance for multiple thread to share,
* and it cann't have any status variants. Here use a ThreadLocal type to
* buffer the status data for each Request Thread.
*/
private static final ThreadLocal<Map<String, Object>> threadScope = new NamedThreadLocal<Map<String, Object>>(
ResGetSvcHandler.class.getName()) {
@Override
protected Map<String, Object> initialValue() {
Map<String, Object> varaintMap = new HashMap<String, Object>();
// initialize default values.
varaintMap.put(RESPONSE_IS_FOLDER, Boolean.FALSE);
varaintMap.put(RESPONSE_DYNIMIC_FILE, Boolean.FALSE);
return varaintMap;
}
};
private static final int PRIORITY = 150;
/**
* The default constructor.
*/
public ResGetSvcHandler(IOSGiWebContainer fwkContainer) {
super(fwkContainer);
resRegister = ResPublishSVCRegister.getInstance();
BundleContext bundleCtx = ResroucesCom.getInstance().getBundle()
.getBundleContext();
ServiceReference svcRef = bundleCtx
.getServiceReference(IOSGiWebContainer.class.getName());
webContainer = (IOSGiWebContainer) bundleCtx.getService(svcRef);
startTimeMillis = System.currentTimeMillis();
}
@Override
public int getPriority() {
return PRIORITY;
}
public void handle(BundledHttpServletRequestWrapper request,
HttpServletResponseWrapper response, IFwkHandlerChain handlerChain)
throws IOException, ServletException {
Object oldBundle = request.getBundle();
try {
getFwkContainerHelper().switchReqBundleContext(
ResroucesCom.getInstance().getBundle());
// Do resource get service.
service(request, response);
int state = response.getState();
if (state == HttpServletResponseWrapper.RESULT_CANCEL) {
// Reset the state. Note: not invoke reset, this will reset all
// former cookie and headers.
if (!request.isInclude())
response.resetStatus();
// Do handler chain continuely.
handlerChain.handle(request, response);
// FIXME: need to throw exception when included jsp file
// not found. To support servlet resource, here not do
// so temporarily.
// 4xx http status are not exception, not throw servelt
// exception.
} else if (/* state % 100 == 4 || */state % 100 == 5) {
// Construct full exception information when exception
// occurs.
String errMsg = response.getErrorMessage();
StringBuffer errInfoOperStr = new StringBuffer(
"Exception occured ");
if (request.isDispatched()) {
errInfoOperStr.append("when dispatched ");
if (request.isInclude())
errInfoOperStr.append("including resource ");
else
errInfoOperStr.append("forwarding to resource ");
} else
errInfoOperStr.append(" when request for resource ");
throw new ServletException(errInfoOperStr + "("
+ request.getRequestURI() + "). "
+ (errMsg != null ? errMsg : ""));
}
} finally {
// Revert bundle of Top Req
getFwkContainerHelper().switchReqBundleContext((Bundle) oldBundle);
}
}
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod();
// FIXME: Judge a found resource a jsp file should not so
// rash. A jsp file may allow POST, PUT, DELETE http methods.
if (method.equals("GET") || req.getServletPath().endsWith(".jsp")) {
// The check if the resource not modified after the time request has
// been done during search process.
doGet(req, resp);
} else {
// Case POST, PUT, DELETE method, skip resource search and return
// RESULT_CANCEL status.
resp.sendError(HttpServletResponseWrapper.RESULT_CANCEL);
}
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// Because the serlvet may be invoked many times in one Request
// thread for Request.include or forward. here buffer the old Thread
// local variants and revert them at last.
Object oldRESPONSE_INPUT = threadScope.get().get(RESPONSE_INPUT);
Object oldRESPONSE_FOLDER_REPORT_FILE = threadScope.get().get(
RESPONSE_FOLDER_REPORT_FILE);
Object oldRESPONSE_IS_FOLDER = threadScope.get()
.get(RESPONSE_IS_FOLDER);
Object oldReq = threadScope.get().get(RESPONSE_REQ);
Object oldResp = threadScope.get().get(RESPONSE_RESP);
Bundle oldBundle = (Bundle) threadScope.get().get(RESPONSE_CUR_BUNDLE);
Object oldDynimicFile = threadScope.get().get(RESPONSE_DYNIMIC_FILE);
Object oldSTATIC_FILE_NAME = threadScope.get().get(STATIC_FILE_NAME);
Object oldRES_NO_MODIFIED = threadScope.get().get(RES_NO_MODIFIED);
try {
// Bunffer the current req to thread local map.
threadScope.get().put(RESPONSE_INPUT, null);
threadScope.get().put(RESPONSE_FOLDER_REPORT_FILE, null);
threadScope.get().put(RESPONSE_IS_FOLDER, Boolean.FALSE);
threadScope.get().put(RESPONSE_CUR_BUNDLE, null);
threadScope.get().put(RESPONSE_DYNIMIC_FILE, Boolean.FALSE);
threadScope.get().put(RESPONSE_REQ, req);
threadScope.get().put(RESPONSE_RESP, resp);
doSearchResource(req, resp);
} catch (Exception e) {
if (e instanceof ServletException)
throw (ServletException) e;
if (e instanceof IOException)
throw (IOException) e;
throw new ServletException(e);
} finally {
// revert the thread load variants.
threadScope.get().put(RES_NO_MODIFIED, oldRES_NO_MODIFIED);
threadScope.get().put(RESPONSE_REQ, oldReq);
threadScope.get().put(RESPONSE_RESP, oldResp);
threadScope.get().put(RESPONSE_CUR_BUNDLE, oldBundle);
threadScope.get().put(RESPONSE_DYNIMIC_FILE, oldDynimicFile);
threadScope.get().put(RESPONSE_INPUT, oldRESPONSE_INPUT);
threadScope.get().put(RESPONSE_FOLDER_REPORT_FILE,
oldRESPONSE_FOLDER_REPORT_FILE);
threadScope.get().put(RESPONSE_IS_FOLDER, oldRESPONSE_IS_FOLDER);
threadScope.get().put(STATIC_FILE_NAME, oldSTATIC_FILE_NAME);
}
}
/**
* Do resource search from published resource repository. If the desiring
* file is a dymical file like jsp, it will be process be apache jasper jsp
* servlet. If the desiring file is a folder, the folder's structure
* information will be returned if its {@link IResourceVisitController}
* allows.
*
* @param req
* @param resp
* @throws Exception
*/
private void doSearchResource(HttpServletRequest req,
HttpServletResponse resp) throws Exception {
try {
String originalPath = req.getServletPath();
if (originalPath.length() == 0)
resp.sendError(HttpServletResponseWrapper.RESULT_CANCEL);
// If the first segment of req path is not Web Bundle's urlPattern,
// add the host bundle's urlPattern before it.
String path = null;
try {
path = ajustOriginalReqPath(originalPath);
} catch (Exception e) {
e.printStackTrace();
throw new ServletException(e);
}
// If return null, no need to do following process.
if (path == null)
return;
if (path != null && path.length() > 0) {
// FIXME: this repositories can be buffer as static to optimize
// the performance?
List<DefinesRoot> reps = resRegister
.getVirtualRepositories(false);
// Sort the DefinesRoots to let the host web bundle's web
// Content root be the first be search.
WebComActivator hostBundleActivator = FwkRuntime.getInstance()
.getHostBundleActivator();
if (hostBundleActivator != null) {
for (int i = 0; i < reps.size(); i++) {
DefinesRoot root = reps.get(i);
if (hostBundleActivator.getBundle().equals(
root.getSourceBundle())) {
if (root.getResources().size() == 1) {
if (root.getResources().get(0).getPath()
.equals(
hostBundleActivator
.getWebContextPath())) {
DefinesRoot tmp = reps.get(0);
reps.set(0, root);
reps.set(i, tmp);
break;
}
}
}
}
}
boolean hostWebRootProecessed = false;
Iterator<DefinesRoot> it = reps.iterator();
while (it.hasNext()) {
DefinesRoot curResRoot = it.next();
// Buffer the context bundle to thread local variant map.
Bundle contextBundle = curResRoot.getSourceBundle();
threadScope.get().put(RESPONSE_CUR_BUNDLE, contextBundle);
// Get the resource visit controller and judge the
// authority.
IResourceVisitController controller = curResRoot
.getVisitControler();
// Buffer the current Resource Define Root to thread local
// variant map. Do not
// need to judge null.
threadScope.get().put(RESPONSE_CUR_VISIT_CTRL, controller);
if (controller == null || controller.canRead(req)) {
// Search the published resource root in a tree
// construct for the
// resource presented by the request path. The result
// will buffered into
// thread local variants map.
if (hostWebRootProecessed && !path.equals(originalPath))
searchForRes(curResRoot, originalPath);
else
searchForRes(curResRoot, path);
// Check if found a dynimic file and processed by jasper
// jsp serlvet.
if (Boolean.TRUE.equals(threadScope.get().get(
RESPONSE_DYNIMIC_FILE))) {
// Jsp file not add to found resource buffer and not
// set last modified time. This means each jsp file
// request will do a new search.
return;
}
// Check if need to update to client by the thread local
// mark RES_NO_MODIFIED. The mark was set during search
// process. If true, no need to consider the found
// RESPONSE_INPUT variant, coz it's null in this case.
if (Boolean.TRUE.equals(threadScope.get().get(
RES_NO_MODIFIED))) {
// Also need to set Response Mime Type if
// RES_NO_MODIFIED status.
String findFileName = originalPath;
if (findFileName != null) {
ServletContext ctx = FwkRuntime.getInstance()
.getWebContainer().findServletContext(
contextBundle);
// Set cache header again.
maybeSetCacheHeaders(resp);
resp.setContentType(ctx
.getMimeType(findFileName));
resp
.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return;
}
}
// If the response content returned in a input stream,
// write it to HttpResponse. Otherwise, the resource
// cann't be found and sent CANCEL status.
InputStream resSrcInput = (InputStream) threadScope
.get().get(RESPONSE_INPUT);
if (resSrcInput != null) {
// Set MIME type before write data.
// Set mine type of http head according to the file
// name found.
// Check if found a directory and its sub resource
// be reported as a string.
if (Boolean.TRUE.equals(threadScope.get().get(
RESPONSE_IS_FOLDER))) {
resp.setContentType(FOLDER_CONTENT_TYPE
+ ";charset=" + TEXT_CHARSET);
} else {
String findFileName = (String) threadScope
.get().get(STATIC_FILE_NAME);
if (findFileName != null) {
ServletContext ctx = FwkRuntime
.getInstance().getWebContainer()
.findServletContext(contextBundle);
resp.setContentType(ctx
.getMimeType(findFileName));
}
}
try {
// Set the last modified response head. This
// method should be done before the response be
// write out.
maybeSetCacheHeaders(resp);
int availableByteCount = resSrcInput
.available();
if (availableByteCount > 0) {
try {
ServletOutputStream output = resp
.getOutputStream();
while ((availableByteCount = resSrcInput
.available()) > 0) {
byte[] data = new byte[availableByteCount];
resSrcInput.read(data);
output.write(data);
}
} catch (IOException e) {
// If client abort, Socket Reset
// Exception
// ocurres and ingore
// it.
// e.printStackTrace();
}
} else {
resp
.setStatus(HttpServletResponse.SC_NO_CONTENT);
// FIXME: If folder or file empty, here
// write a NULL char to client ,this may
// cause some exception.
// if
// (Boolean.TRUE.equals(threadScope.get().get(RESPONSE_IS_FOLDER)))
// resp.getWriter().write(0);
resp.setContentLength(0);
}
} finally {
resSrcInput.close();
resSrcInput = null;
}
return;
} else {
// If result inputstream is null because the found
// folder not allowed to
// browse. Return 404 error status.
Boolean isForbbidenBrowsingFolder = (Boolean) threadScope
.get().get(RESPONSE_IS_FOLDER);
if (isForbbidenBrowsingFolder) {
// Send the 404 status
resp
.sendError(
HttpServletResponseWrapper.SC_UNAUTHORIZED,
"not found resource for path:"
+ req.getRequestURI());
return;
}
}
}
hostWebRootProecessed = true;
}
}
// Search all WebComponent's ServletContext by call its
// getResource(path)
Collection<ComActivator> activators = FwkRuntime.getInstance()
.getAllComponentActivators();
for (Iterator<ComActivator> it = activators.iterator(); it
.hasNext();) {
ComActivator activator = it.next();
if (!(activator instanceof WebResComActivator)
&& activator instanceof WebComActivator) {
WebComActivator webComActivator = ((WebComActivator) activator);
String resPath = originalPath;
if (originalPath.startsWith("/"
+ webComActivator.getServiceNSPrefix() + "/")) {
// No need to remove the current web bundle's prefix,
// BundledServletContext
// will do it for us. And the processDynimicFile()
// method also need this
// prefix to identifing the jsp file.
/*
* resPath = resPath.replaceFirst("/" +
* webComActivator.getServiceNSPrefix(), "");
*/
IWABServletContext ctx = ((WebComActivator) activator)
.getBundleServletContext();
if (ctx.isStaticResource(resPath)) {
URL url = ctx.getResource(resPath);
if (url != null) {
String lastSeg = new Path(url.getPath())
.lastSegment();
if (lastSeg != null
&& (lastSeg.endsWith(".jsp") || lastSeg
.endsWith(".jsf"))) {
processJspFile(null, resPath, ctx
.getBundle());
} else {
InputStream resSrcInput = url.openStream();
ServletOutputStream output = resp
.getOutputStream();
int availableByteCount = 0;
while ((availableByteCount = resSrcInput
.available()) > 0) {
byte[] data = new byte[availableByteCount];
resSrcInput.read(data);
output.write(data);
}
resSrcInput.close();
resSrcInput = null;
resp.setContentType(ctx.getMimeType(url
.getFile()));
}
return;
}
}
}
}
}
// Send the customized CANCEL status marking not any resource can be
// found and service be Canceled.
resp.sendError(HttpServletResponseWrapper.RESULT_CANCEL);
} finally {
// delete created folder report file if any.
File tmpFile = (File) threadScope.get().get(
RESPONSE_FOLDER_REPORT_FILE);
if (tmpFile != null)
tmpFile.deleteOnExit();
}
}
/**
* Adjust the request path.
*
* @param oReqPath
* @return
*/
private String ajustOriginalReqPath(String oReqPath) throws Exception {
IPath path = new Path(oReqPath);
String firstSeg = path.segment(0);
if (firstSeg != null) {
Set<IWABServletContext> set = webContainer
.getAllBundledServletContext();
for (IWABServletContext ctx : set) {
String webBundlePrefix = ctx.getWABContextPath();
if (webBundlePrefix.equals("/" + firstSeg))
return oReqPath;
}
}
IWABServletContext hostServletContext = null;
// If this req path is "/" and host web context exists, do search its
// welcome pages and redirect.
if (oReqPath.equals("/")
&& (hostServletContext = webContainer.findHostServletContext()) != null) {
// Try to load the welcome page from the root of a web bundle, then
// redirect the response.
List<String> welcomePages = hostServletContext.getWelcomePages();
for (Iterator<String> it = welcomePages.iterator(); it.hasNext();) {
String welcomeFilePath = it.next();
if (hostServletContext.getResource(welcomeFilePath) != null) {
BundledHttpServletRequestWrapper req = (BundledHttpServletRequestWrapper) threadScope
.get().get(RESPONSE_REQ);
// oReqPath =
// path.append(welcomeFilePath).toPortableString();
// req.setPathInfo(oReqPath);
HttpServletResponse resp = (HttpServletResponse) threadScope
.get().get(RESPONSE_RESP);
StringBuffer rawUrl = req.getRequestURL();
if (rawUrl.charAt(rawUrl.length() - 1) == '/')
rawUrl.deleteCharAt(rawUrl.length() - 1);
resp.sendRedirect(rawUrl.append(
new Path(welcomeFilePath).makeAbsolute()
.toPortableString()).toString());
return null;
}
}
}
return oReqPath;
}
private void maybeSetCacheHeaders(HttpServletResponse resp) {
// if (!resp.containsHeader("Cache-Control"))
// If is debugging, set the cache time as 1 second. Otherwise,
// Set client cache header, the max age as 3*24*3600 = 259200 seconds =
// 72 hours.
if (ResroucesCom.getInstance().isDebugging())
resp.setHeader("Cache-Control", "public,max-age=1");
else
resp.setHeader("Cache-Control", "public,max-age=259200");
}
/**
* Search from each Resource Repository root for desiring resource the path
* presented.
*
* @param r
* @param reqPath
* @throws Exception
*/
private void searchForRes(DefinesRoot r, String reqPath) throws Exception {
List<BaseResource> reses = r.getResources();
for (BaseResource res : reses) {
Object parentVisitController = threadScope.get().get(
RESPONSE_CUR_VISIT_CTRL);
try {
IResourceVisitController visitController = res
.getVisitControler();
// Buffer the current Resource Define Root to thread local
// variant map. If null, not
// set
// to null, just use parent Resource Define's Visit Controller.
if (visitController != null)
threadScope.get().put(RESPONSE_CUR_VISIT_CTRL,
visitController);
HttpServletRequest req = (HttpServletRequest) threadScope.get()
.get(RESPONSE_REQ);
if (visitController == null || visitController.canRead(req)) {
boolean found = visitRes(res, reqPath, null, null, null);
if (found)
break;
}
} finally {
threadScope.get().put(RESPONSE_CUR_VISIT_CTRL,
parentVisitController);
}
}
}
/**
* Visit each Resource for the give path.
*
* @param res
* @param reqRawPath
* @param reqPath
* @param localPath
* @param aliasPath
* @return
* @throws Exception
*/
private boolean visitRes(BaseResource res, String reqRawPath,
IPath reqPath, IPath localPath, IPath aliasPath) throws Exception {
HttpServletRequest req = (HttpServletRequest) threadScope.get().get(
RESPONSE_REQ);
if (reqPath == null)
reqPath = new Path(reqRawPath);
if (localPath == null)
localPath = new Path("/");
if (aliasPath == null)
aliasPath = new Path("/");
String path = res.getPath();
if (path == null || path.length() == 0)
return false;
String alias = res.getAlias();
if (alias == null || alias.length() == 0)
alias = path;
IPath newLocalPath = localPath.append(path);
IPath newAliasPath = aliasPath.append(alias);
String quickID = res.getQuickID();
if (quickID != null && !quickID.startsWith("/"))
quickID = "/" + quickID;
if (res instanceof ResFile) {
if ((quickID != null && quickID.length() > 0 && reqRawPath
.equals(new Path(res.getQuickID()).toPortableString()))
|| reqPath.equals(newAliasPath)) {
File localFile = null;
// Get file from resolver.
if (res.getResolver() != null)
localFile = res.getResolver().resolve(req, path, alias,
quickID);
if (localFile == null)
localFile = newLocalPath.toFile();
if (localFile != null && localFile.exists()) {
boolean processedDynimicFile = false;
String fileExt = new Path(localFile.getAbsolutePath())
.getFileExtension();
// If found file is directory, file extension will be null;
if (fileExt != null
&& (fileExt.equals("jsp") || fileExt.equals("jsf") || fileExt
.equals("jspx"))) {
processedDynimicFile = true;
// Decide this resource is under the bundle's webContent
// and need process.
Bundle currentContextBundle = (Bundle) threadScope
.get().get(RESPONSE_CUR_BUNDLE);
processJspFile(localFile, newAliasPath
.toPortableString(), currentContextBundle);
}
if (!processedDynimicFile) {
try {
// If not modified after the last request, skip
// output.
if (!checkLastModifiedTime(localFile)) {
threadScope.get().put(RES_NO_MODIFIED, true);
return true;
}
FileInputStream fin = new FileInputStream(localFile);
threadScope.get().put(STATIC_FILE_NAME,
localFile.getName());
threadScope.get().put(RESPONSE_INPUT, fin);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
} else {
threadScope.get().put(RESPONSE_DYNIMIC_FILE,
Boolean.TRUE);
}
return true;
}
}
}
if (res instanceof ResFolder) {
ResFolder resFolder = ((ResFolder) res);
List<ResFilter> filters = resFolder.getFilter();
if ((quickID != null && quickID.length() > 0 && reqPath
.equals(new Path(res.getQuickID()).toPortableString()))
|| reqPath.equals(newAliasPath)) {
// check if folder browse allowed???
IResourceVisitController visitController = (IResourceVisitController) threadScope
.get().get(RESPONSE_CUR_VISIT_CTRL);
if (visitController != null
&& !visitController.canBrowseFolder(req)) {
// Buffer the status marking that a folder be found.
threadScope.get().put(RESPONSE_IS_FOLDER, Boolean.TRUE);
return true;
}
File resolvedFolder = null;
// Get file from resolver.
if (res.getResolver() != null)
resolvedFolder = res.getResolver().resolve(req, path,
alias, quickID);
// Case the folder report to large, here create a temporary file
// to store the
// content.
File tmpFile;
FileOutputStream fout;
try {
tmpFile = File.createTempFile("FolderRep"
+ Thread.currentThread().getId(), "tmp");
fout = new FileOutputStream(tmpFile);
PrintWriter writer = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(fout, TEXT_CHARSET)));
List<BaseResource> subReses = resFolder.getResources();
if (subReses.size() > 0) {
for (BaseResource r : subReses) {
String childAlias = r.getAlias();
if (childAlias == null)
childAlias = r.getPath();
IPath reqPathRelResPath = FileUtil.makeRelativeTo(
newAliasPath.append(childAlias), reqPath);
writer.println(newAliasPath
.append(reqPathRelResPath));
reportVirtualRes(r, writer, newAliasPath,
newLocalPath, reqPath, filters);
}
} else {
// If not sub resource extension exists, get all real
// resource in local
// file
// system.
File localFile = null;
if (resolvedFolder != null) {
if (resolvedFolder.isDirectory()
&& resolvedFolder.exists())
localFile = resolvedFolder;
} else {
String contextPath = req.getSession()
.getServletContext().getRealPath("/");
localFile = new Path(contextPath).append(
newLocalPath).toFile();
}
if (localFile != null && localFile.exists()) {
reportLocalRes(localFile, writer, reqPath,
newAliasPath, filters);
}
}
writer.flush();
writer.close();
// Buffer the status marking that a folder be found.
threadScope.get().put(RESPONSE_IS_FOLDER, Boolean.TRUE);
// buffer the result stream to thread local variant.
threadScope.get().put(RESPONSE_INPUT,
new FileInputStream(tmpFile));
// buffer the create temporary file to thread local variant
// and delete it at
// last.
threadScope.get().put(RESPONSE_FOLDER_REPORT_FILE, tmpFile);
} catch (Exception e) {
e.printStackTrace();
}
return true;
} else {
// Only when the pfefix of current Resource is the required
// path, search sun
// resource.
if ((quickID != null && quickID.length() > 0 && new Path(res
.getQuickID()).isPrefixOf(reqPath))
|| newAliasPath.isPrefixOf(reqPath)) {
List<BaseResource> subReses = ((ResFolder) res)
.getResources();
if (subReses.size() > 0) {
for (BaseResource r : subReses) {
boolean ok = visitRes(r, reqRawPath, reqPath,
newLocalPath, newAliasPath);
if (ok)
return true;
}
} else {
File resolvedFolder = null;
// Get file from resolver.
if (res.getResolver() != null)
resolvedFolder = res.getResolver().resolve(req,
path, alias, quickID);
if (resolvedFolder == null) {
String contextPath = req.getSession()
.getServletContext().getRealPath("/");
resolvedFolder = new Path(contextPath).append(
newLocalPath).toFile();
}
boolean found = searchLocalRes(resolvedFolder, reqPath,
newAliasPath, filters);
if (found)
return true;
}
}
}
}
return false;
}
/**
* Report the virtual resource in of folder.
*
* @param r
* @param writer
* @param newAliasPath
* @param reqPath
* @param parentLocalPath
* @param filters
*/
private void reportVirtualRes(BaseResource res, PrintWriter writer,
IPath parentAliasPath, IPath rawReqPath, IPath parentLocalPath,
List<ResFilter> filters) {
if (!(res instanceof ResFolder))
return;
ResFolder resFolder = (ResFolder) res;
HttpServletRequest req = (HttpServletRequest) threadScope.get().get(
RESPONSE_REQ);
String path = resFolder.getPath();
if (path == null || path.length() == 0)
return;
String alias = res.getAlias();
String quickID = res.getQuickID();
if (alias == null || alias.length() == 0)
alias = path;
IPath newAliasPath = parentAliasPath.append(alias);
IPath newLocalPath = parentLocalPath.append(path);
File resolvedFolder = null;
// Get file from resolver.
if (res.getResolver() != null)
resolvedFolder = res.getResolver().resolve(req, path, alias,
quickID);
List<BaseResource> subReses = resFolder.getResources();
if (resolvedFolder == null && subReses != null && subReses.size() > 0) {
for (BaseResource r : subReses) {
String childAlias = r.getAlias();
if (childAlias == null)
childAlias = r.getPath();
IPath reqPathRelResPath = FileUtil.makeRelativeTo(newAliasPath
.append(childAlias), rawReqPath);
writer.println(newAliasPath.append(reqPathRelResPath));
reportVirtualRes(r, writer, newAliasPath, newLocalPath,
rawReqPath, filters);
}
} else {
// If not sub resource extension exists, get all real resource in
// local file
// system.
File localFile = null;
if (resolvedFolder != null) {
if (resolvedFolder.isDirectory() && resolvedFolder.exists())
localFile = resolvedFolder;
} else {
String contextPath = req.getSession().getServletContext()
.getRealPath("/");
localFile = new Path(contextPath).append(newLocalPath).toFile();
}
if (localFile != null && localFile.exists()) {
reportLocalRes(localFile, writer, rawReqPath, newAliasPath,
filters);
}
}
writer.flush();
}
/**
* Report a local resource in a folder.
*
* @param parentFile
* @param writer
* @param rawReqPath
* @param aliasPath
* @param filters
*/
private void reportLocalRes(File parentFile, PrintWriter writer,
IPath rawReqPath, IPath aliasPath, List<ResFilter> filters) {
File[] cfs = parentFile.listFiles();
if (cfs.length > 0) {
for (File f : cfs) {
if (!f.exists())
continue;
// Let real file name as alias name here.
if (filterName(f.getName(), filters)) {
IPath newAliasPath = aliasPath
.append(new Path(f.getName()));
IPath reqPathRelResPath = FileUtil.makeRelativeTo(
newAliasPath, rawReqPath);
writer.println(reqPathRelResPath.toPortableString());
if (f.isDirectory()) {
reportLocalRes(f, writer, rawReqPath, newAliasPath,
null);
}
}
}
}
writer.flush();
}
/**
* Search resource form resources in local file system.
*
* @param parentFile
* @param rawReqPath
* @param aliasPath
* @param filters
* @return
* @throws Exception
*/
private boolean searchLocalRes(File parentFile, IPath rawReqPath,
IPath aliasPath, List<ResFilter> filters) throws Exception {
File[] cfs = parentFile.listFiles();
if (cfs == null)
return false;
for (File f : cfs) {
if (!f.exists())
continue;
// Let real file name as alias name here.
IPath newAliasPath = aliasPath.append(new Path(f.getName()));
if (filterName(f.getName(), filters)) {
if (rawReqPath.equals(newAliasPath)) {
if (f.isDirectory()) {
File tmpFile;
FileOutputStream bo;
try {
tmpFile = File.createTempFile("FolderRep"
+ Thread.currentThread().getId(), "tmp");
bo = new FileOutputStream(tmpFile);
} catch (IOException e1) {
e1.printStackTrace();
continue;
}
PrintWriter writer;
try {
writer = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(bo, TEXT_CHARSET)));
reportLocalRes(f, writer, rawReqPath, newAliasPath,
null);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
threadScope.get().put(RESPONSE_IS_FOLDER, Boolean.TRUE);
threadScope.get().put(RESPONSE_FOLDER_REPORT_FILE,
tmpFile);
try {
threadScope.get().put(RESPONSE_INPUT,
new FileInputStream(tmpFile));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
} else {
boolean processedDynimicFile = false;
String fileExt = new Path(f.getAbsolutePath())
.getFileExtension();
// If found file is directory, file extension will be
// null;
if (fileExt != null
&& (fileExt.equals("jsp")
|| fileExt.equals("jsf") || fileExt
.equals("jspx"))) {
// Decide this resource is under the bundle's
// webContent and need process.
Bundle currentContextBundle = (Bundle) threadScope
.get().get(RESPONSE_CUR_BUNDLE);
processedDynimicFile = true;
processJspFile(f, newAliasPath.toPortableString(),
currentContextBundle);
// Buffer the status marking that the dynimic
// file found and be
// processed successfully.
threadScope.get().put(RESPONSE_DYNIMIC_FILE,
Boolean.TRUE);
}
if (!processedDynimicFile) {
try {
// If not modified after the last request, skip
// output.
if (!checkLastModifiedTime(f)) {
threadScope.get()
.put(RES_NO_MODIFIED, true);
return true;
}
threadScope.get().put(STATIC_FILE_NAME,
f.getName());
threadScope.get().put(RESPONSE_INPUT,
new FileInputStream(f));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
return true;
} else if (f.isDirectory()) {
// Only when the pfefix of current Resource is the required
// path, search sun
// resource.
if (newAliasPath.isPrefixOf(rawReqPath)) {
boolean found = searchLocalRes(f, rawReqPath,
newAliasPath, null);
if (found)
return true;
}
}
}
}
return false;
}
/**
* Check if the found resource not modified since the time request
* delivered. If true, http 304 status will be returned directly.
*
* @param localFile
* @param req
*
* @return
*/
private boolean checkLastModifiedTime(File localFile) {
HttpServletRequest curReq = (HttpServletRequest) threadScope.get().get(
RESPONSE_REQ);
HttpServletResponse curResp = (HttpServletResponse) threadScope.get()
.get(RESPONSE_RESP);
// If not contains the head, return -1
long ifLastModifiedSince = curReq.getDateHeader("If-Modified-Since");
/*
* if (ifLastModifiedSince < 0) return true;
*/
IResourceVisitController curVisitController = (IResourceVisitController) threadScope
.get().get(RESPONSE_CUR_VISIT_CTRL);
long lastMidifiedTimeMills = -1;
if (curVisitController != null)
lastMidifiedTimeMills = curVisitController
.getLastModifiedTimeMillis(curReq, localFile);
if (lastMidifiedTimeMills <= 0)
lastMidifiedTimeMills = startTimeMillis;
// Adjust the lastMidifiedTimeMills to second.
lastMidifiedTimeMills = lastMidifiedTimeMills - lastMidifiedTimeMills
% 1000;
curResp.setDateHeader("Last-Modified", lastMidifiedTimeMills);
return ifLastModifiedSince < lastMidifiedTimeMills;
}
/**
* If a found file is a dynimical file liks jsp, jsf. etc, let apache jasper
* servler to process it.
*
* @param jspFile
* @param virtualPath
* @param targetBundle
* @return
* @throws IOException
* @throws ServletException
*/
private boolean processJspFile(File jspFile, String virtualPath,
Bundle targetBundle) throws Exception {
IWebApplication webApp = FwkRuntime.getInstance().getAppliction(
targetBundle);
// Check and make sure this web application actived
if (!FwkRuntime.getInstance().makeSureWabActive(webApp))
throw new IllegalStateException(
"Web bundle not be actived to handle dymamic resource at this time and wait timeout.");
// here should not remove the bundle prefix before the virtual path.
// if servlet path is the same, the jasper may not compile the jsp just
// use the form class
// file. here set the servlet name to virtual name to hint that
// these are different. The
// BundleServletContext will remove the prefix for us.
// get the target bundle's servlet config.
/*
* if (newResPath.startsWith("/" + activator.getServiceNSPrefix() +
* "/")) newResPath = newResPath.replaceFirst("/" +
* activator.getServiceNSPrefix() + "/", "/");
*/
// Get a poolled servlet to process jsp file.
JasperServletWrapper jspServlet = JspServletPool.getInstance(webApp
.getBundleServletContext());
// ClassLoader c = jspServlet.getClass().getClassLoader();
// if newResPath is the same, the jasper may not compile the jsp
// just use the form class
// file. here set the servlet name to virtual name to hint that
// these are different.
// get the target bundle's servlet config.
// ServletConfig newConfig = new
// BundledServletConfig(targetBundle,getJspServletConfig(),
// virtualPath);
// jspServlet.init(newConfig);
BundledHttpServletRequestWrapper topReq = (BundledHttpServletRequestWrapper) webContainer
.getReqThreadVariants().get(OSGiWebContainer.THREAD_V_REQUEST);
HttpServletRequest req = (HttpServletRequest) threadScope.get().get(
RESPONSE_REQ);
ServletResponse resp = (ServletResponse) threadScope.get().get(
RESPONSE_RESP);
BundledHttpServletRequestWrapper wrapper = BundledHttpServletRequestWrapper
.getHttpServletRequestWrapper(req, targetBundle);
Bundle oldBundle = topReq.getBundle();
String oldServletPath = topReq.getServletPath();
String oldPathInfo = topReq.getPathInfo();
try {
// Switch to another bundled servelt request.
// No need to swith thread context class loader, coz Jsp Servlet's
// class loader has been changed.
topReq.setBundle(targetBundle);
// wrapper.setBundle(targetBundle);
wrapper.setServletPath(virtualPath);
wrapper.setPathInfo(null);
jspServlet.service(wrapper, resp);
// jspServlet.destroy();
} finally {
if (topReq.equals(wrapper)) {
topReq.setBundle(oldBundle);
topReq.setServletPath(oldServletPath);
topReq.setPathInfo(oldPathInfo);
} else {
BundledHttpServletRequestWrapper.releaseRequestWrapper(wrapper);
}
}
return true;
}
/**
* Filter resource by a folder's filters.
*
* @param name
* @param filters
* @return
*/
private boolean filterName(String name, List<ResFilter> filters) {
if (filters == null || filters.size() == 0)
return true;
else {
boolean result = true;
for (ResFilter filter : filters) {
boolean include = filter.isIncluded();
String patternStr = filter.getPattern();
if (patternStr != null && patternStr.length() > 0) {
Pattern p = Pattern.compile(patternStr);
Matcher m = p.matcher(name);
result = m.matches();
} else
result = include;
if (result == false)
return false;
}
}
return true;
}
}