/** * Copyright (C) 2009 eXo Platform SAS. * * 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 should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.portal.application; import java.awt.geom.AffineTransform; import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URLDecoder; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import javax.imageio.ImageIO; 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 org.exoplatform.commons.utils.PropertyManager; import org.exoplatform.container.web.AbstractFilter; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; public class ResourceRequestFilter extends AbstractFilter { protected static Log log = ExoLogger.getLogger(ResourceRequestFilter.class); // private static final Charset UTF_8 = Charset.forName("UTF-8"); private FilterConfig cfg; private ImageType[] imageTypes = ImageType.values(); private ConcurrentMap<String, FutureTask<Image>> mirroredImageCache = new ConcurrentHashMap<String, FutureTask<Image>>(); public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; public static final String LAST_MODIFIED = "Last-Modified"; public void afterInit(FilterConfig filterConfig) { cfg = filterConfig; log.info("Cache eXo Resource at client: " + !PropertyManager.isDevelopping()); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; final String uri = URLDecoder.decode(httpRequest.getRequestURI(), "UTF-8"); final HttpServletResponse httpResponse = (HttpServletResponse) response; // ExoContainer portalContainer = getContainer(); // final SkinService skinService = (SkinService) portalContainer.getComponentInstanceOfType(SkinService.class); long ifModifiedSince = httpRequest.getDateHeader(IF_MODIFIED_SINCE); // /* * if (uri.endsWith(".css")) { // Check if cached resource has not been modifed, return 304 code long cssLastModified = * skinService.getLastModified(uri); if (isNotModified(ifModifiedSince, cssLastModified)) { * httpResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED); return; } * response.setContentType("text/css; charset=UTF-8"); * * final OutputStream out = response.getOutputStream(); final BinaryOutput output = new BinaryOutput() { public Charset * getCharset() { return UTF_8; } public void write(byte b) throws IOException { out.write(b); } public void * write(byte[] bytes) throws IOException { out.write(bytes); } public void write(byte[] bytes, int off, int len) throws * IOException { out.write(bytes, off, len); } }; ResourceRenderer renderer = new ResourceRenderer() { public * BinaryOutput getOutput() throws IOException { return output; } public void setExpiration(long seconds) { if (seconds * > 0) { httpResponse.addHeader("Cache-Control", "max-age=" + seconds + ",s-maxage=" + seconds); } else { * httpResponse.setHeader("Cache-Control", "no-cache"); } * * long lastModified = skinService.getLastModified(uri); processIfModified(lastModified, httpResponse); } }; * * // try { skinService.renderCSS(renderer, uri); if (log.isDebugEnabled()) { log.debug("Use a merged CSS: " + uri); } } * catch (Exception e) { if (e instanceof SocketException) { //Should we print something/somewhere exception message } * else { log.error("Could not render css " + uri, e); httpResponse.setStatus(HttpServletResponse.SC_NOT_FOUND); } } } * else { */ // Fast matching final int len = uri.length(); if (len >= 7 && uri.charAt(len - 7) == '-' && uri.charAt(len - 6) == 'r' && uri.charAt(len - 5) == 't') { for (final ImageType imageType : imageTypes) { if (imageType.matches(uri)) { final String resource = uri.substring(httpRequest.getContextPath().length(), len - 7) + uri.substring(len - 4); FutureTask<Image> futureImg = mirroredImageCache.get(resource); if (futureImg == null) { FutureTask<Image> tmp = new FutureTask<Image>(new Callable<Image>() { public Image call() throws Exception { InputStream in = cfg.getServletContext().getResourceAsStream(resource); if (in == null) { return null; } // BufferedImage img = ImageIO.read(in); log.debug("Read image " + uri + " (" + img.getWidth() + "," + img.getHeight() + ")"); AffineTransform tx = AffineTransform.getScaleInstance(-1, 1); tx.translate(-img.getWidth(null), 0); AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); img = op.filter(img, null); log.debug("Mirrored image " + uri + " (" + img.getWidth() + "," + img.getHeight() + ")"); ByteArrayOutputStream baos = new ByteArrayOutputStream(1000); ImageIO.write(img, imageType.getFormat(), baos); baos.close(); return new Image(imageType, baos.toByteArray()); } }); // futureImg = mirroredImageCache.putIfAbsent(resource, tmp); if (futureImg == null) { futureImg = tmp; futureImg.run(); } } // try { Image img = futureImg.get(); if (img != null) { // Check if cached resource has not been modifed, return 304 code long imgLastModified = img.getLastModified(); if (isNotModified(ifModifiedSince, imgLastModified)) { httpResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED); return; } httpResponse.setContentType(img.type.getMimeType()); httpResponse.setContentLength(img.bytes.length); httpResponse.setHeader("Cache-Control", "max-age=2592000,s-maxage=2592000"); processIfModified(imgLastModified, httpResponse); OutputStream out = httpResponse.getOutputStream(); out.write(img.bytes); out.close(); } else { mirroredImageCache.remove(resource); httpResponse.setStatus(HttpServletResponse.SC_NOT_FOUND); } return; } catch (InterruptedException e) { // Find out what is relevant to do log.error(e.getMessage(), e); } catch (ExecutionException e) { // Cleanup log.error(e.getMessage(), e); mirroredImageCache.remove(resource); } } } } // if (!PropertyManager.isDevelopping()) { httpResponse.addHeader("Cache-Control", "max-age=2592000,s-maxage=2592000"); } else { if (uri.endsWith(".jstmpl") || uri.endsWith(".js")) { httpResponse.setHeader("Cache-Control", "no-cache"); } if (log.isDebugEnabled()) log.debug(" Load Resource: " + uri); } chain.doFilter(request, response); /* * } */ } /** * Add Last-Modified Http header to HttpServetResponse */ public void processIfModified(long lastModified, HttpServletResponse httpResponse) { httpResponse.setDateHeader(ResourceRequestFilter.LAST_MODIFIED, lastModified); } /** * If cached resource has not changed since date in http header (If_Modified_Since), return true Else return false; * * @param ifModifedSince - String, and HttpHeader element * @param lastModified * @param httpResponse * @return */ private boolean isNotModified(long ifModifedSince, long lastModified) { if (!PropertyManager.isDevelopping()) { if (ifModifedSince >= lastModified) { return true; } } return false; } public void destroy() { } }