uno.anahata.mapacho.servlet.JarHandler Maven / Gradle / Ivy
The newest version!
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package uno.anahata.mapacho.servlet;
import uno.anahata.mapacho.common.jardiff.JarDiff;
import java.io.*;
import java.util.HashMap;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Pack200;
import java.util.zip.Deflater;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import uno.anahata.mapacho.common.http.HttpHeaders;
/**
*
* @author pablo
*/
@RequiredArgsConstructor
@Slf4j
public class JarHandler {
private final ServletContext servletContext;
private HashMap cache = new HashMap<>();
public DownloadResponse getDownloadResponse(DownloadRequest dr) throws IOException {
if (dr.getRequestedVersion() == null) {
//not interested in these requests
//something to think about here would be to check if there is any other version of the
//jar in the cache, because if there isn't one, then we may as well return the entire thing??
//would need to check if by doing so, jws goes into non versioned mode for subsequent updates
log.debug("Ignoring non versioned jar request " + dr);
return null;
}
if (!dr.isJarExists()) {
log.error("Could not locate jar in .jar or .pack.gz for " + dr.getJarPath());
return null;
}
log.debug("Processing versioned jar request " + dr);
boolean returnFullJar = isReturnFullJar(dr);
File jarFile = dr.getRequestedVersionJarFile();
File packGzJarFile = dr.getRequestedVersionJarPackGzFile();
File cacheJarFile = dr.getRequestedVersionCacheJarFile();
File cacheJarPackGzFile = dr.getRequestedVersionCacheJarPackGzFile();
log.info("Requested version plain jar cache location: {}" + cacheJarFile);
if (returnFullJar) {
if (!cacheJarFile.exists()) {
//copy jar to cache if not there
if (jarFile != null) {
log.info("Requested version plain jar not in cache, copying from {} to {}", jarFile,
cacheJarFile);
//should be done asynch really
copyAsynch(jarFile, cacheJarFile);
} else {
log.info("Requested version plain jar not in cache, unpacking asynchronously {} to {}",
packGzJarFile,
cacheJarFile);
unpackAsynch(packGzJarFile, cacheJarFile);
}
}
if (packGzJarFile != null) {
log.info("returning entire jar in " + HttpHeaders.PACK200_GZIP_ENCODING + " encoding: {}",
packGzJarFile);
FileDownloadResponse ret = new FileDownloadResponse(packGzJarFile);
ret.setContentEncoding(HttpHeaders.PACK200_GZIP_ENCODING);
ret.setMimeType(HttpHeaders.PACK200_MIME_TYPE);
ret.setVersionId(dr.getRequestedVersion());
return ret;
} else if (cacheJarPackGzFile.exists()) {
log.info("returning entire jar.pack.gz from cache : {}", cacheJarPackGzFile);
FileDownloadResponse ret = new FileDownloadResponse(cacheJarPackGzFile);
ret.setContentEncoding(HttpHeaders.PACK200_GZIP_ENCODING);
ret.setMimeType(HttpHeaders.PACK200_MIME_TYPE);
ret.setVersionId(dr.getRequestedVersion());
return ret;
} else {
log.info("returning entire jar but streaming along the way: {}", jarFile);
FileDownloadResponse ret = new FileDownloadResponse(jarFile);
ret.setPack(true);
ret.setGz(true);
ret.setTargetCacheName(cacheJarFile.getName() + ".pack.gz");
ret.setContentEncoding(HttpHeaders.PACK200_GZIP_ENCODING);
ret.setMimeType(HttpHeaders.PACK200_MIME_TYPE);
ret.setVersionId(dr.getRequestedVersion());
return ret;
}
}
//ok, so both requested and current are available
File diff = dr.getCacheDiffFile();
File diffPackGz = dr.getCacheDiffPackGzFile();
if (diffPackGz.exists()) {
log.info("JarDiff pack.gz existed in cache, returning diff {} length={}", diff, diff.length());
FileDownloadResponse ret = new FileDownloadResponse(diffPackGz);
ret.setMimeType(HttpHeaders.JARDIFF_MIMETYPE);
ret.setContentEncoding(HttpHeaders.PACK200_GZIP_ENCODING);
ret.setVersionId(dr.getRequestedVersion());
return ret;
}
if (!diff.exists()) {
//have to make diff.
if (!cacheJarFile.exists()) {
if (jarFile != null) {
FileUtils.copyFile(jarFile, cacheJarFile);
} else {
log.info("Requested version unpacked jar not in cache, unpacking {} to cache {} to generate JarDiff",
packGzJarFile, cacheJarFile);
unpack(packGzJarFile, cacheJarFile);
}
//could be done asynch
log.info("JarDiff {} not in cache, creating , currentVersionSize={}, requesteVersinSize={} ", diff,
dr.getCurrentVersionCacheJarFile().length(), cacheJarFile.length());
}
makeJarDiff(dr, cacheJarFile, diff);
}
//here we should probably check if the jardiff is bigger than the actual requested jar as the jardiff cannot be packed.
FileDownloadResponse ret = new FileDownloadResponse(diff);
ret.setMimeType(HttpHeaders.JARDIFF_MIMETYPE);
ret.setContentEncoding(HttpHeaders.PACK200_GZIP_ENCODING);
ret.setPack(true);
ret.setGz(true);
ret.setTargetCacheName(diffPackGz.getName());
ret.setVersionId(dr.getRequestedVersion());
return ret;
}
private static void makeJarDiff(DownloadRequest dr, File cacheJarFile, File diff) throws IOException {
File diffTemp = File.createTempFile(diff.getName(), ".tmp");
try (FileOutputStream fos = new FileOutputStream(diffTemp)) {
JarDiff.createPatch(dr.getCurrentVersionCacheJarFile(), cacheJarFile, fos, true);
}
log.info("JarDiff created in temp file size {}", diffTemp.length());
//packing the jardiff seems to cause SHA-256 problems, just returning unpacked
FileUtils.deleteQuietly(diff);
diffTemp.renameTo(diff);
log.info("JarDiff (not packed) stored in cache {} size={}", diff.length());
}
private boolean isReturnFullJar(DownloadRequest dr) {
boolean returnFullJar = false;
if (dr.getPath().contains("lib/")) {
log.info("Request is for lib/ jars, will return entire jar to reduce the chances of javaws bugs");
returnFullJar = true;
} else if (dr.getRequestedVersion().equals(dr.getCurrentVersion())) {
log.info("Request current versionId is same as current version id, returning entire jar");
returnFullJar = true;
} else if (dr.getCurrentVersion() == null) {
log.info("Request current versionId is null, returning entire jar");
returnFullJar = true;
} else {
//curr version and requested version specified but not matching
File currVersionJar = dr.getCurrentVersionCacheJarFile();
if (!currVersionJar.exists()) {
log.info(
"Current version unpacked jar {} not in cache, returning entire jar {}",
currVersionJar);
returnFullJar = true;
} else {
log.info(
"Current version unpacked jar {} (size = {}) exists in cache, will create jar diff",
currVersionJar, currVersionJar.length());
}
}
return returnFullJar;
}
/**
* Performs an unpak operation asynchronously.
*
* @param jarPackGzSource the .pack.gz
* @param jarDest the target jar file
*/
private static void unpackAsynch(File jarPackGzSource, File jarDest) {
//todo use thread pool
new Thread(() -> {
try {
unpack(jarPackGzSource, jarDest);
} catch (Exception e) {
log.error("Exception in asynchronous unpack operation");
}
}).start();
}
/**
* Performs an unpak operation asynchronously.
*
* @param source the .pack.gz
* @param dest the target jar file
*/
private static void copyAsynch(File source, File dest) {
//todo use thread pool
new Thread(() -> {
try {
FileUtils.copyFile(source, dest);
} catch (Exception e) {
log.error("Exception in asynchronous unpack operation");
}
}).start();
}
/**
* Unpacks a pack.gz into a jar
*
* @param jarPackGzSource
* @param jarDest
* @throws IOException
*/
private static void unpack(File jarPackGzSource, File jarDest) throws IOException {
File temp = File.createTempFile(jarDest.getName(), ".tmp");
log.info("unpacking {} to {}", jarPackGzSource, jarDest);
//could be done in a separate thread
long ts = System.currentTimeMillis();
try (InputStream in = new BufferedInputStream(new GZIPInputStream(new FileInputStream(jarPackGzSource)));
JarOutputStream out = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(temp)))) {
Pack200.Unpacker unpacker = Pack200.newUnpacker();
//unpacker.properties().putAll( props );
unpacker.unpack(in, out);
}
FileUtils.deleteQuietly(jarDest);
temp.renameTo(jarDest);
ts = System.currentTimeMillis() - ts;
log.info("unpacking {} to {} took {} ms.", jarPackGzSource, jarDest, ts);
}
// /**
// * Packs a jar file into pack.gz
// *
// * @param source
// * @param destination
// * @throws IOException
// */
// private static void pack(File source, File destination) throws IOException {
// long ts = System.currentTimeMillis();
// log.debug("Packing \n\t{} size={} to \n\t{}", source, source.length(), destination);
// File temp = File.createTempFile(destination.getName(), ".tmp");
//
// try (JarFile jar = new JarFile(source, false); OutputStream out = new FileOutputStream(temp)) {
//
// GZIPOutputStream gzipOut = new GZIPOutputStream(out) {
// {
// def.setLevel(Deflater.BEST_COMPRESSION);
// }
// };
// BufferedOutputStream bos = new BufferedOutputStream(gzipOut);
//
// Pack200.Packer packer = Pack200.newPacker();
// //packer.properties().putAll( props );
// packer.pack(jar, bos);
// bos.flush();
// gzipOut.finish();
// }
// log.debug("pack operation to temp file finished size = {}, renaming {} to {}", temp.length(), temp, destination);
// FileUtils.deleteQuietly(destination);
// temp.renameTo(destination);
// ts = System.currentTimeMillis() - ts;
// log.debug("after renaming packed file {}, size is {} took {} ms.", destination, destination.length(), ts);
// }
// public static void main(String[] args) throws Exception {
// File source = new File(
// "/tmp/com.anahata-JobTracking-app-1.1.19-SNAPSHOT.20150429.164652-local-maven-repo-to-1.1.19-SNAPSHOT.20150429.165037-local-maven-repo.jardiff.pack.gz8049565465949587518.tmp");
// File target = new File("/tmp/anahata-jws-cache/xxx.jardiff");
// unpack(source, target);
//
// }
}