package org.apache.s4.base.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.io.ByteStreams;
import com.google.common.io.Closeables;
import com.google.common.io.Files;
import com.google.inject.Inject;
import com.google.inject.name.Named;

/**
 * Helper class for creating S4RLoader instances for a given S4R file.
 * 
 */
public class S4RLoaderFactory {

    private static Logger logger = LoggerFactory.getLogger(S4RLoaderFactory.class);

    @Inject(optional = true)
    @Named("s4.tmp.dir")
    File tmpDir;

    /**
     * Explodes the s4r archive in a user specified directory through "s4.tmpdir" parameter, and prepares a classloader
     * that will load classes and resources from, first, the application classes, then the dependencies.
     * 
     * Inspired from Hadoop's application classloading implementation (RunJar class).
     * 
     * @param s4rPath
     *            path to s4r
     * @return classloader that loads resources from the s4r in a predefined order
     */
    public S4RLoader createS4RLoader(String s4rPath) {
        if (tmpDir == null) {
            tmpDir = Files.createTempDir();
            tmpDir.deleteOnExit();
            logger.warn(
                    "s4.tmp.dir not specified, using temporary directory [{}] for unpacking S4R. You should rather specify a non-temporary directory.",
                    tmpDir.getAbsolutePath());
        }
        JarFile jar = null;
        try {
            jar = new JarFile(s4rPath);
            Enumeration<JarEntry> entries = jar.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                if (!entry.isDirectory()) {
                    File to = new File(tmpDir, entry.getName());
                    Files.createParentDirs(to);
                    InputStream is = jar.getInputStream(entry);
                    OutputStream os = new FileOutputStream(to);
                    try {
                        ByteStreams.copy(is, os);
                    } finally {
                        Closeables.closeQuietly(is);
                        Closeables.closeQuietly(os);
                    }
                }
            }

            List<URL> classpath = new ArrayList<URL>();
            addDirLibsToClassPath(classpath, "/app");
            addDirLibsToClassPath(classpath, "/lib");
            classpath.add(new File(tmpDir.getAbsolutePath() + "/").toURI().toURL());

            S4RLoader s4rLoader = new S4RLoader(classpath.toArray(new URL[] {}));
            return s4rLoader;

        } catch (IOException e) {
            logger.error("Cannot process S4R [{}]: {}", s4rPath, e.getClass().getName() + "/" + e.getMessage());
            throw new RuntimeException("Cannot create S4R classloader", e);
        }
    }

    private void addDirLibsToClassPath(List<URL> classpath, String dir) throws MalformedURLException {
        File[] libs = new File(tmpDir, dir).listFiles();
        if (libs != null) {
            for (int i = 0; i < libs.length; i++) {
                if (!libs[i].isDirectory()) {
                    classpath.add(libs[i].toURI().toURL());
                }
            }
        }
    }
}

@@ -932,16 +932,34 @@
 ve in a 
+subdirectory of a 
 user spe
@@ -990,16 +990,17 @@
  %22s4.tmp
+.
 dir%22 par
@@ -1010,16 +1010,23 @@
 ter, and
+%0A     *
  prepare
@@ -1040,23 +1040,16 @@
 ssloader
-%0A     *
  that wi
@@ -1124,16 +1124,23 @@
 then the
+%0A     *
  depende
@@ -1150,24 +1150,97 @@
 es.%0A     * %0A
+     * Uses a temporary directory if s4.tmp.dir is not provided.%0A     * %0A
      * Inspi
@@ -1515,24 +1515,52 @@
  s4rPath) %7B%0A
+        File s4rDir = null;%0A
         if (
@@ -1589,19 +1589,19 @@
         
-tmp
+s4r
 Dir = Fi
@@ -1629,27 +1629,27 @@
             
-tmp
+s4r
 Dir.deleteOn
@@ -1786,21 +1786,19 @@
 You 
-should rather
+may want to
  spe
@@ -1804,16 +1804,23 @@
 ecify a 
+parent 
 non-temp
@@ -1862,44 +1862,519 @@
     
-tmpDir.getAbsolutePath());%0A        %7D
+s4rDir.getAbsolutePath());%0A        %7D else %7B%0A            s4rDir = new File(tmpDir, s4rPath.substring(s4rPath.lastIndexOf(File.separator)) + %22-%22%0A                    + System.currentTimeMillis());%0A            if (!s4rDir.mkdir()) %7B%0A                throw new RuntimeException(%22Cannot create directory for unzipping S4R file in %5B%22%0A                        + s4rDir.getAbsolutePath() + %22%5D. Aborting deployment.%22);%0A            %7D%0A        %7D%0A        logger.info(%22Unzipping S4R archive in %5B%7B%7D%5D%22, s4rDir.getAbsolutePath());%0A
 %0A   
@@ -2694,27 +2694,27 @@
  = new File(
-tmp
+s4r
 Dir, entry.g
@@ -3269,16 +3269,24 @@
 asspath,
+ s4rDir,
  %22/app%22)
@@ -3336,99 +3336,22 @@
 th, 
-%22/lib%22);%0A            classpath.add(new File(tmpDir.getAbsolutePath() + %22/%22).toURI().toURL()
+s4rDir, %22/lib%22
 );%0A%0A
@@ -3762,16 +3762,29 @@
 asspath,
+ File s4rDir,
  String 
@@ -3846,27 +3846,27 @@
  = new File(
-tmp
+s4r
 Dir, dir).li

Use subdirectories for unzipping s4r files in s4.tmp.dir, if provided