package com.n11.imic;
import com.n11.imic.cache.CacheManager;
import com.n11.imic.cache.CachedImage;
import com.n11.imic.config.ImageScalerConfiguration;
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import net.sf.ehcache.Element;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.http.HttpHeaders;
import org.mitre.dsmiley.httpproxy.ProxyServlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ImageScalerServlet extends ProxyServlet {
private static final long serialVersionUID = -4875037205150829084L;
private static final Logger LOGGER = LoggerFactory.getLogger(ImageScalerServlet.class);
private SimpleDateFormat dateFormat;
private static final Pattern ptScaleOriginalImage = Pattern.compile("^/([^/]+)/(.+)$");
@Override
public void init(ServletConfig servletConfig) throws ServletException {
ImageScalerConfiguration.getInstance().initConfiguration();
CacheManager.getInstance().initConfiguration();
ImageIO.scanForPlugins();
dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if (cacheExists(request) && useCache(request, response)) {
return;
}
serviceFromFileSystem(request, response);
}
private void serviceFromFileSystem(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
Matcher mtScaleImage = ptScaleOriginalImage.matcher(request.getPathInfo());
if (mtScaleImage.matches()) {
String scaleParam = mtScaleImage.group(1);
if (ImageScalerConfiguration.getInstance().getDefaultScaleParameters().containsKey(scaleParam)) {
ScalerParam scaler = ImageScalerConfiguration.getInstance().getDefaultScaleParameters().get(scaleParam).copy();
setAnimatedGifMode(request, scaler);
serviceWithLocalStorage(request, response, scaler, mtScaleImage.group(2));
} else {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
} else {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
}
private boolean cacheExists(HttpServletRequest servletRequest) {
return CacheManager.getInstance().isCacheEnabled()
&& !("no-cache".equalsIgnoreCase(servletRequest.getHeader("Cache-Control")));
}
private boolean useCache(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException {
CacheManager.getInstance().lockReadCache();
Element cachedImage = CacheManager.getInstance().getImageFromCache(servletRequest.getPathInfo());
CacheManager.getInstance().unlockReadCache();
if (cachedImage != null) {
Object obj = cachedImage.getObjectValue();
CachedImage ci = (CachedImage) obj;
for (Map.Entry<String, String> headerEntry : ci.getHttpHeaders().entrySet()) {
servletResponse.setHeader(headerEntry.getKey(), headerEntry.getKey().equals(HttpHeaders.CONTENT_LENGTH) ? String.valueOf(ci.getImageDataLength()) : headerEntry.getValue());
}
IOUtils.copy(new ByteArrayInputStream(ci.getImageData()), servletResponse.getOutputStream());
return true;
}
return false;
}
private void setAnimatedGifMode(HttpServletRequest servletRequest, ScalerParam requestScalingParameters) {
String agifmode = servletRequest.getParameter("agifmode");
requestScalingParameters.setAnimatedGifMode(AnimatedGIFMode.valueFrom(agifmode));
}
private void serviceWithLocalStorage(HttpServletRequest servletRequest, HttpServletResponse servletResponse, ScalerParam requestScalingParameters, String url) throws IOException {
FileInputStream originalImageFile = null;
Map<String, String> responseHeaders = new HashMap<String, String>();
try {
String requestedFile = FilenameUtils.normalize(ImageScalerConfiguration.getInstance().getSourceLocation() + File.separator + URLDecoder.decode(url, "UTF-8"));
File file = new File(requestedFile);
originalImageFile = new FileInputStream(file);
String contentType = findContentType(file, originalImageFile);
addHeadersToResponse(responseHeaders, requestScalingParameters, file, contentType);
byte[] data = copyResponseEntity(originalImageFile, responseHeaders, requestScalingParameters, contentType, file.length());
if (ArrayUtils.isNotEmpty(data)) {
CacheManager.getInstance().putToCacheIfCacheExists(servletRequest.getPathInfo(), data, responseHeaders);
IOUtils.copy(new ByteArrayInputStream(data), servletResponse.getOutputStream());
} else {
originalImageFile.getChannel().position(0);
IOUtils.copy(originalImageFile, servletResponse.getOutputStream());
}
} catch (Exception e) {
LOGGER.error("Error servicing the file ",e);
} finally {
for (Map.Entry<String, String> e : responseHeaders.entrySet()) {
servletResponse.setHeader(e.getKey(), e.getValue());
}
closeQuietly(originalImageFile);
closeQuietly(servletResponse.getOutputStream());
}
}
private void addHeadersToResponse(Map<String, String> responseHeaders, ScalerParam requestScalingParameters, File file, String contentType) {
if (contentType.matches("^image/gif")) {
responseHeaders.put(HttpHeaders.CONTENT_TYPE, "image/png");
}
responseHeaders.put(HttpHeaders.CONTENT_TYPE, contentType);
responseHeaders.put(HttpHeaders.CONTENT_LENGTH, String.valueOf(file.length()));
responseHeaders.put(HttpHeaders.CACHE_CONTROL, "max-age=" + requestScalingParameters.getHttpExpires());
responseHeaders.put(HttpHeaders.LAST_MODIFIED, dateFormat.format(file.lastModified()));
responseHeaders.put(HttpHeaders.EXPIRES, dateFormat.format(DateUtils.addSeconds(new Date(), requestScalingParameters.getHttpExpires())));
responseHeaders.put(HttpHeaders.AGE, "0");
}
private String findContentType(File file, FileInputStream originalImageFile) throws IOException {
ContentInfoUtil contentInfoUtil = new ContentInfoUtil();
ContentInfo contentInfo = contentInfoUtil.findMatch(originalImageFile);
String contentType = contentInfo.getContentType().getMimeType();
originalImageFile.getChannel().position(0);
if (contentType == null) {
contentType = getServletContext().getMimeType(file.getName());
}
if (contentType == null) {
contentType = URLConnection.guessContentTypeFromStream(originalImageFile);
originalImageFile.getChannel().position(0);
}
if (contentType == null) {
contentType = "application/octet-stream";
}
return contentType;
}
private byte[] copyResponseEntity(InputStream originalData, Map<String, String> headers, ScalerParam requestScalingParameters, String contentType, long contentLength) {
try {
BufferedImage result = ImageScaler.scaleImage(originalData, contentType, requestScalingParameters);
ByteArrayOutputStream baos = new ByteArrayOutputStream(Math.abs(requestScalingParameters.getTargetWidth() * requestScalingParameters.getTargetHeight() * 2));
ImageScaler.writeImageToStream(result, baos, contentType, requestScalingParameters);
headers.put(HttpHeaders.CONTENT_LENGTH, String.valueOf(baos.size()));
result.flush();
return baos.toByteArray();
} catch (Exception e) {
LOGGER.error("Exception Occured while copying the response entity", e);
headers.put(HttpHeaders.CONTENT_LENGTH, String.valueOf(contentLength));
headers.put(HttpHeaders.CONTENT_TYPE, contentType);
return ArrayUtils.EMPTY_BYTE_ARRAY;
}
}
@Override
public void destroy() {
CacheManager.getInstance().shutdownCacheManager();
super.destroy();
}
@Override
protected void closeQuietly(Closeable resource) {
if (resource == null) {
return;
}
try {
resource.close();
} catch (IOException e) {
LOGGER.error("An error occurred while closing resource.", e);
}
}
}