/*
* $Id: IWBundleResourceFilter.java,v 1.56 2009/03/11 08:47:50 civilis Exp $
* Created on 27.1.2005
*
* Copyright (C) 2005 Idega Software hf. All Rights Reserved.
*
* This software is the proprietary information of Idega hf. Use is subject to
* license terms.
*/
package com.idega.servlet.filter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarInputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import javax.faces.context.FacesContext;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.idega.core.file.business.FileIconSupplier;
import com.idega.idegaweb.DefaultIWBundle;
import com.idega.idegaweb.IWBundle;
import com.idega.idegaweb.IWMainApplication;
import com.idega.idegaweb.IWModuleLoader;
import com.idega.util.CoreConstants;
import com.idega.util.CoreUtil;
import com.idega.util.FileUtil;
import com.idega.util.IOUtil;
import com.idega.util.ListUtil;
import com.idega.util.StringHandler;
import com.idega.util.StringUtil;
import com.idega.util.resources.ResourcesAdder;
/**
* <p>
* Filter that can feed out resources (images/css etc.) from a set directory for
* all bundles.<br>
* This can be set with a System property idegaweb.bundles.resource.dir to to be
* the directory for the Eclipse workspace when developing. (Setting
* -Didegaweb.bundles.resource.dir=/idega/eclipse/workspace in the tomcat plugin
* preference pane).
* </p>
*
* Last modified: $Date: 2009/03/11 08:47:50 $ by $Author: civilis $
*
* @author <a href="mailto:tryggvil@idega.com">tryggvil</a>
* @version $Revision: 1.56 $
*/
public class IWBundleResourceFilter extends BaseFilter {
private static final Logger LOGGER = Logger.getLogger(IWBundleResourceFilter.class.getName());
protected boolean feedFromSetBundleDir = false;
protected boolean feedFromJarFiles = IWMainApplication.loadBundlesFromJars;
protected String sBundlesDirectory;
protected Map<String, Boolean> flushedResources = new HashMap<String, Boolean>();
public static String BUNDLES_STANDARD_DIR = "/idegaweb/bundles/";
static String BUNDLE_SUFFIX = DefaultIWBundle.BUNDLE_FOLDER_STANDARD_SUFFIX;
private static String SVG = "svg";
private static String JSP = "jsp";
private static String XHTML = "xhtml";
private static String PSVG = "psvg";
private static String AXIS_JWS = "jws";
/*
* (non-Javadoc)
*
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
@Override
public void init(FilterConfig arg0) throws ServletException {
String directory = System.getProperty(DefaultIWBundle.SYSTEM_BUNDLES_RESOURCE_DIR);
if (directory != null) {
this.sBundlesDirectory = directory;
this.feedFromSetBundleDir = true;
}
}
/*
* (non-Javadoc)
*
* @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
* javax.servlet.ServletResponse, javax.servlet.FilterChain)
*/
@Override
public void doFilter(ServletRequest sreq, ServletResponse sres, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) sreq;
HttpServletResponse response = (HttpServletResponse) sres;
String requestUriWithoutContextPath = getURIMinusContextPath(request);
/* Removing ;jessionid before checking in cache */
if (requestUriWithoutContextPath.contains(CoreConstants.SEMICOLON)) {
requestUriWithoutContextPath = requestUriWithoutContextPath.substring(
0, requestUriWithoutContextPath.indexOf(CoreConstants.SEMICOLON));
}
if (!flushedResources.containsKey(requestUriWithoutContextPath)) {
IWMainApplication iwma = getIWMainApplication(request);
String webappDir = iwma.getApplicationRealPath();
boolean fileExists = false;
if (this.feedFromSetBundleDir) {
try {
if (!speciallyHandleFile(request, this.sBundlesDirectory, webappDir, requestUriWithoutContextPath)) {
File realFile = getFileInWorkspace(this.sBundlesDirectory, requestUriWithoutContextPath);
if (realFile.exists()) {
feedOutFile(request,response, realFile);
return;
}
}
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Error serving file from workspace", e);
}
}
if (feedFromJarFiles || !fileExists) {
if (requestUriWithoutContextPath.startsWith(BUNDLES_STANDARD_DIR)) {
//check if we have flushed the file from the jar before and then do nothing OR flush it and then do nothing
//THIS IS VERY SIMPLE CACHING that invalidates on restart
try {
File realFile = copyResourceFromJarOrCustomContentToWebapp(iwma, requestUriWithoutContextPath, null, true);
//old way without flushing to webapp
//String mimeType = getMimeType(pathWithinBundle);
//feedOutFile(request, response, mimeType, stream);
if (realFile == null) {
LOGGER.warning("File does not exist at " + requestUriWithoutContextPath);
} else {
feedOutFile(request, response, realFile);
flushedResources.put(requestUriWithoutContextPath, Boolean.TRUE);
LOGGER.log(Level.FINE, "Flushed file to webapp : " + requestUriWithoutContextPath);
return;
}
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Error serving file from jar: " + requestUriWithoutContextPath, e);
}
}
}
}
// if file is specially handled, flushed from the jar file or any error occurs, then let the server keep on going with it
chain.doFilter(sreq, sres);
}
protected static String getBundleFromRequest(String requestUriWithoutContextPath) {
requestUriWithoutContextPath = requestUriWithoutContextPath.trim();
int index = requestUriWithoutContextPath.indexOf(BUNDLE_SUFFIX);
if (index == -1) {
if (requestUriWithoutContextPath.startsWith("http://")) {
return null;
}
requestUriWithoutContextPath = StringHandler.replace(requestUriWithoutContextPath, BUNDLES_STANDARD_DIR, CoreConstants.EMPTY);
int firstSlashIndex = requestUriWithoutContextPath.indexOf(CoreConstants.SLASH);
if (firstSlashIndex == -1) {
return requestUriWithoutContextPath;
}
return requestUriWithoutContextPath.substring(0, firstSlashIndex);
}
return requestUriWithoutContextPath.substring(BUNDLES_STANDARD_DIR.length(), index);
}
protected static String getResourceWithinBundle(String requestUriWithoutContextPath) {
String rest = null;
int index = requestUriWithoutContextPath.indexOf(BUNDLE_SUFFIX);
if (index != -1) {
rest = requestUriWithoutContextPath.substring(index + BUNDLE_SUFFIX.length() + 1);
} else {
String URIWithoutBundlesURI = requestUriWithoutContextPath.substring(BUNDLES_STANDARD_DIR.length()+1);
index = URIWithoutBundlesURI.indexOf(CoreConstants.SLASH);
rest = URIWithoutBundlesURI.substring(index);
}
if (rest.indexOf(";jsessionid") != -1)
rest = rest.substring(0, rest.indexOf(";jsessionid"));
return rest;
}
/**
* <p>
* Copies a resource from within a Jar File into the webapp folder if it doesn't
* already exists
* </p>
* @param iwma
* @param requestUriWithoutContextPath
*/
public synchronized static File copyResourceFromJarToWebapp(IWMainApplication iwma, String requestUriWithoutContextPath) {
return copyResourceFromJarOrCustomContentToWebapp(iwma, requestUriWithoutContextPath, null);
}
public synchronized static File copyResourceFromJarOrCustomContentToWebapp(
IWMainApplication iwma,
String requestUriWithoutContextPath,
String fileContent
) {
return copyResourceFromJarOrCustomContentToWebapp(iwma, requestUriWithoutContextPath, fileContent, true);
}
private synchronized static File copyResourceFromJarOrCustomContentToWebapp(
IWMainApplication iwma,
String requestUriWithoutContextPath,
String fileContent,
boolean alwaysReturnFile
) {
String bundleIdentifier = getBundleFromRequest(requestUriWithoutContextPath);
if (StringUtil.isEmpty(bundleIdentifier)) {
if (requestUriWithoutContextPath.startsWith(CoreConstants.WEBDAV_SERVLET_URI + CoreConstants.PATH_FILES_ROOT + CoreConstants.SLASH)) {
return getFileFromRepository(requestUriWithoutContextPath);
}
LOGGER.warning("Unknown bundle identifier, can not return requested file: " + requestUriWithoutContextPath);
return null;
}
String pathWithinBundle = getResourceWithinBundle(requestUriWithoutContextPath);
requestUriWithoutContextPath = StringHandler.replace(requestUriWithoutContextPath, CoreConstants.SLASH + CoreConstants.SLASH, File.separator);
String webappFilePath = iwma.getApplicationRealPath() + requestUriWithoutContextPath;
File webappFile = new File(webappFilePath);
IWBundle bundle = iwma.getBundle(bundleIdentifier);
long bundleLastModified = bundle.getResourceTime(pathWithinBundle);
if (webappFile.exists()) {
long webappLastModified = webappFile.lastModified();
if (webappLastModified > bundleLastModified) {
if (alwaysReturnFile) {
return webappFile;
}
return null;
}
}
if (StringUtil.isEmpty(fileContent) && requestUriWithoutContextPath.indexOf(ResourcesAdder.OPTIMIZIED_RESOURCES) != -1) {
if (alwaysReturnFile) {
return webappFile;
}
return null;
}
return copyFileContentToWebApp(iwma, requestUriWithoutContextPath, fileContent, pathWithinBundle, bundle, bundleLastModified);
}
private synchronized static File copyFileContentToWebApp(
IWMainApplication iwma,
String requestUriWithoutContextPath,
String content,
String pathWithinBundle,
IWBundle bundle,
long lastModified
) {
InputStream stream = null;
String webappFilePath = iwma.getApplicationRealPath() + requestUriWithoutContextPath;
String doubleSlash = CoreConstants.SLASH + CoreConstants.SLASH;
try {
//Special Windows handling:
char separatorChar = File.separatorChar;
//This is to handle the case when the URI contains the '/' character (in requestUriWithoutContextPath)
// this '/' needs to be replaced with '\' for Windows
if (separatorChar == FileUtil.WINDOWS_FILE_SEPARATOR) {
webappFilePath = webappFilePath.replace(FileUtil.UNIX_FILE_SEPARATOR, FileUtil.WINDOWS_FILE_SEPARATOR);
}
if (StringUtil.isEmpty(content)) {
if (webappFilePath.indexOf(doubleSlash) != -1) {
webappFilePath = StringHandler.replace(webappFilePath, doubleSlash, File.separator);
}
if (pathWithinBundle.indexOf(doubleSlash) != -1) {
pathWithinBundle = StringHandler.replace(pathWithinBundle, doubleSlash, File.separator);
}
stream = bundle.getResourceInputStream(pathWithinBundle);
} else {
stream = StringHandler.getStreamFromString(content);
}
File webappFile = FileUtil.getFileAndCreateRecursiveIfNotExists(webappFilePath);
FileUtil.streamToFile(stream, webappFile);
webappFile.setLastModified(lastModified);
return webappFile;
} catch (FileNotFoundException fnf) {
if (pathWithinBundle.startsWith(CoreConstants.WEBDAV_SERVLET_URI + CoreConstants.PATH_FILES_ROOT + CoreConstants.SLASH)) {
return getFileFromRepository(pathWithinBundle);
} else {
if (requestUriWithoutContextPath.indexOf(doubleSlash) != -1) {
requestUriWithoutContextPath = StringHandler.replace(requestUriWithoutContextPath, doubleSlash, File.separator);
}
LOGGER.log(Level.WARNING, "Could not copy resource from jar (" + bundle.getBundleIdentifier() + ") to " + requestUriWithoutContextPath, fnf);
}
} catch (Exception e) {
if (requestUriWithoutContextPath.indexOf(doubleSlash) != -1) {
requestUriWithoutContextPath = StringHandler.replace(requestUriWithoutContextPath, doubleSlash, File.separator);
}
LOGGER.log(Level.WARNING, "Could not copy resource from jar (" + bundle.getBundleIdentifier() + ") to " + requestUriWithoutContextPath, e);
} finally {
IOUtil.closeInputStream(stream);
}
return null;
}
private static File getFileFromRepository(String path) {
try {
File file = CoreUtil.getFileFromRepository(path);
if (file == null || !file.exists()) {
throw new FileNotFoundException("File '" + path + "' can not be loaded from repository");
}
return file;
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Error getting file '" + path + "' from repository", e);
}
return null;
}
/**
* Loads ALL resources (if any found) from bundle's JAR directory to real web app's directory
*
* @param iwma
* @param bundle
* @param pathInBundle - like 'resources/resourcesToLoadDirectory/'
*/
@SuppressWarnings("unchecked")
public synchronized static final List<File> copyAllFilesFromJarDirectory(IWMainApplication iwma, IWBundle bundle, String pathInBundle) {
Set<String> paths = iwma.getResourcePaths(IWModuleLoader.DEFAULT_LIB_PATH);
if (ListUtil.isEmpty(paths)) {
return null;
}
String expectedBundleJar = new StringBuilder(bundle.getBundleIdentifier()).append("-").toString();
String bundleJar = null;
for (Iterator<String> pathsIter = paths.iterator(); (pathsIter.hasNext() && bundleJar == null);) {
bundleJar = pathsIter.next();
if (bundleJar.indexOf(expectedBundleJar) == -1) {
bundleJar = null; // Not bundle's JAR file
}
}
if (StringUtil.isEmpty(bundleJar)) {
return null;
}
if (bundleJar.startsWith(File.separator)) {
bundleJar = bundleJar.replaceFirst(File.separator, CoreConstants.EMPTY);
}
String jarPath = IWMainApplication.getDefaultIWMainApplication().getApplicationRealPath() + bundleJar;
JarInputStream jarStream = null;
try {
jarStream = new JarInputStream(new FileInputStream(new File(jarPath)));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
if (jarStream == null) {
return null;
}
if (pathInBundle.startsWith(File.separator)) {
pathInBundle = pathInBundle.replaceFirst(File.separator, CoreConstants.EMPTY);
}
String badBundlePathStart = "bundle" + File.separator;
if (pathInBundle.startsWith(badBundlePathStart)) {
pathInBundle = pathInBundle.replaceFirst(badBundlePathStart, CoreConstants.EMPTY);
}
String applicationPath = iwma.getApplicationRealPath();
if (!applicationPath.endsWith(File.separator)) {
applicationPath += File.separator;
}
String bundleRootPath = bundle.getRootVirtualPath();
if (bundleRootPath.startsWith(File.separator)) {
bundleRootPath = bundleRootPath.replaceFirst(File.separator, CoreConstants.EMPTY);
}
if (!bundleRootPath.endsWith(File.separator)) {
bundleRootPath += File.separator;
}
String realDirectoryForExtractedFiles = applicationPath + bundleRootPath;
InputStream stream = null;
File file = null;
boolean needToCopyFile = true;
List<File> copiedFiles = new ArrayList<File>();
String realPathToFile = null;
try {
for (ZipEntry entry = null; (entry = jarStream.getNextEntry()) != null;) {
if (entry.getName().startsWith((pathInBundle))) {
if (!entry.isDirectory()) {
realPathToFile = new StringBuilder(realDirectoryForExtractedFiles).append(entry.getName()).toString();
file = new File(realPathToFile);
// If file doesn't exist OR modified LATER than file copied into web application - need to re-copy file
needToCopyFile = !file.exists() || bundle.getResourceTime(entry.getName()) > file.lastModified();
if (needToCopyFile) {
file = FileUtil.getFileAndCreateRecursiveIfNotExists(realPathToFile);
stream = IOUtil.getStreamFromCurrentZipEntry(jarStream);
FileUtil.streamToFile(stream, file);
file.setLastModified(bundle.getResourceTime(pathInBundle));
IOUtil.closeInputStream(stream);
}
copiedFiles.add(file);
}
}
}
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
IOUtil.closeInputStream(stream);
IOUtil.closeInputStream(jarStream);
}
return copiedFiles;
}
/**
* @param realFile
*/
// private boolean d(HttpServletRequest request,String
// bundleIdentifier,String filePathInBundle,File file) {
private boolean speciallyHandleFile(HttpServletRequest request, String workspaceDir, String webappDir, String requestUriWithoutContextPath) {
String fileEnding = getFileEnding(requestUriWithoutContextPath);
if (fileEnding == null) {
LOGGER.warning(this.getClass().getName() +": file ending is null!");
return false;
}
if (fileEnding.equalsIgnoreCase(PSVG)) {
copyWorkspaceFileToWebapp(workspaceDir, webappDir, requestUriWithoutContextPath);
return true;
}
else if (fileEnding.equalsIgnoreCase(SVG)) {
copyWorkspaceFileToWebapp(workspaceDir, webappDir, requestUriWithoutContextPath);
return true;
}
else if (fileEnding.equalsIgnoreCase(JSP)) {
copyWorkspaceFileToWebapp(workspaceDir, webappDir, requestUriWithoutContextPath);
return true;
}
else if (fileEnding.equalsIgnoreCase(XHTML)) {
copyWorkspaceFileToWebapp(workspaceDir, webappDir, requestUriWithoutContextPath);
return true;
}
else if (fileEnding.equalsIgnoreCase(AXIS_JWS)) {
// do nothing: should be handled by axis:
return true;
}
return false;
}
private String getFileEnding(String filePath) {
// String fileName = realFile.getName();
String fileName = filePath;
int index = fileName.lastIndexOf(".");
if (index != -1) {
return fileName.substring(index + 1, fileName.length());
}
return null;
}
/**
* @param response
* @param realFile
*/
private void feedOutFile(HttpServletRequest request, HttpServletResponse response, File realFile) {
try {
FileInputStream fis = new FileInputStream(realFile);
String mimeType = getMimeType(realFile);
feedOutFile(request, response, mimeType,fis);
} catch (FileNotFoundException e) {
LOGGER.warning("File not found: " + realFile.getPath());
}
}
/**
* @param response
* @param realFile
*/
private void feedOutFile(HttpServletRequest request, HttpServletResponse response,String mimeType, InputStream streamToResource) {
try {
if (mimeType != null) {
response.setContentType(mimeType);
}
OutputStream out = response.getOutputStream();
int buffer = 1000;
byte[] barray = new byte[buffer];
int read = streamToResource.read(barray);
while (read != -1) {
out.write(barray, 0, read);
read = streamToResource.read(barray);
}
streamToResource.close();
out.flush();
out.close();
} catch (IOException e) {
LOGGER.warning("Error streaming resource to " + request.getRequestURI());
}
}
protected String getMimeType(String filePath){
String mimeType = FileIconSupplier.getInstance().guessMimeTypeFromFileName(filePath);
return mimeType;
}
protected String getMimeType(File realFile) {
String mimeType = getMimeType(realFile.getName());
return mimeType;
}
/*
* (non-Javadoc)
*
* @see javax.servlet.Filter#destroy()
*/
@Override
public void destroy() {
}
/**
* <p>
* Copies a file to a the real webapplication folder from the (eclipse)
* workspace if the lastmodified timestamp is more recent on the file in the
* workspace.
* </p>
*
* @param workspaceDir
* Something like '/home/tryggvil/eclipseworkspace/'
* @param webappDir
* Something like
* '/home/tryggvil/eclipseworkspace/applications/mywebapp/target/mywebapp/'
* @param requestUriWithoutContextPath
* Something like
* '/idegaweb/bundles/com.idega.core.bundle/jsp/myjsp.jsp'
*/
public static synchronized File copyWorkspaceFileToWebapp(String workspaceDir, String webappDir, String requestUriWithoutContextPath) {
if (webappDir.endsWith(File.separator)) {
// cut the slash:
webappDir = webappDir.substring(0, webappDir.length() - 1);
}
if (workspaceDir.endsWith(File.separator)) {
// cut the slash:
workspaceDir = workspaceDir.substring(0, workspaceDir.length() - 1);
}
File fileInWorkspace = getFileInWorkspace(workspaceDir, requestUriWithoutContextPath);
File fileInWebapp = new File(webappDir, requestUriWithoutContextPath);
long webappModified = fileInWebapp.lastModified();
long workspaceLastModified = fileInWorkspace.lastModified();
if (workspaceLastModified > webappModified) {
try {
if (!fileInWebapp.exists()) {
FileUtil.createFileIfNotExistent(fileInWebapp);
}
FileUtil.copyFile(fileInWorkspace, fileInWebapp);
fileInWebapp.setLastModified(workspaceLastModified);
}
catch (FileNotFoundException e) {
LOGGER.warning("File not found: " + fileInWorkspace.getPath());
return null;
}
catch (IOException e) {
LOGGER.warning("Error copying file: " + fileInWorkspace.getPath());
return null;
}
}
return fileInWebapp;
}
public static void checkCopyOfResourceToWebapp(FacesContext context, String resourceURI) {
checkCopyOfResourceToWebapp(IWMainApplication.getIWMainApplication(context), resourceURI);
}
public static void checkCopyOfResourceToWebapp(IWMainApplication iwma, String resourceURI) {
String bundlesProperty = System.getProperty(DefaultIWBundle.SYSTEM_BUNDLES_RESOURCE_DIR);
File copiedFile = null;
if (bundlesProperty != null) {
String webappDir = iwma.getApplicationRealPath();
String workspaceDir = bundlesProperty;
String pathToBundleFileInWorkspace = resourceURI;
copiedFile = copyWorkspaceFileToWebapp(workspaceDir, webappDir, pathToBundleFileInWorkspace);
}
if (copiedFile == null || IWMainApplication.loadBundlesFromJars) {
// One more try to copy resource
copyResourceFromJarOrCustomContentToWebapp(iwma, resourceURI, null, true);
}
}
/**
* <p>
* Gets the file or tries to guess to its location inside in the 'workspace'
* out from a requestUri.
* </p>
*
* @param workspaceDir
* @param requestUriWithoutContextPath
* @return
*/
public static File getFileInWorkspace(String workspaceDir, String requestUriWithoutContextPath) {
if (requestUriWithoutContextPath.startsWith(BUNDLES_STANDARD_DIR)) {
// cut it from the string as the bundle is directly under the workspace
// but keep the last slash:
requestUriWithoutContextPath = requestUriWithoutContextPath.substring(BUNDLES_STANDARD_DIR.length() - 1);
}
String sFileInWorkspace = workspaceDir + requestUriWithoutContextPath;
File fileInWorkspace = new File(sFileInWorkspace);
if (!fileInWorkspace.exists()) {
// Hack: trying to remove the .bundle suffix if the suffix doesn't exist
// on the folder in the workspace:
int index = sFileInWorkspace.indexOf(BUNDLE_SUFFIX);
if (index != -1) {
sFileInWorkspace = sFileInWorkspace.substring(0, index) + sFileInWorkspace.substring(index + BUNDLE_SUFFIX.length());
}
fileInWorkspace = new File(sFileInWorkspace);
}
return fileInWorkspace;
}
}