All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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);
//
//    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy