JarPackaging.java

package sk.iway.iwcm.io;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

import sk.iway.iwcm.Constants;
import sk.iway.iwcm.Logger;
import sk.iway.iwcm.Tools;

/**
 * JarPackaging.java - trieda pre podporu JarPackagingu (ukladania JSP a statickych suborov do JAR archivov)
 * ticket #34006
 * https://stackoverflow.com/questions/5013917/can-i-serve-jsps-from-inside-a-jar-in-lib-or-is-there-a-workaround
 * https://alexismp.wordpress.com/2010/04/28/web-inflib-jarmeta-infresources/
 * https://www.javacodegeeks.com/2013/08/servlet-3-0-overview.html
 *
 * @author $Author: jeeff $
 */
public class JarPackaging
{
   private static String JAR_LOCATION_ADMIN = null; //NOSONAR
   private static String JAR_LOCATION_COMPONENTS = null; //NOSONAR
   private static String JAR_LOCATION_CORE = null; //NOSONAR

   protected JarPackaging() {
      //utility class
   }

   public static void initialize()
   {
      JAR_LOCATION_COMPONENTS = initializeJarLocation("/META-INF/resources/components/_common/combine.jsp");

      if (Tools.isNotEmpty(JAR_LOCATION_COMPONENTS))
      {
         JAR_LOCATION_ADMIN = initializeJarLocation("/META-INF/resources/admin/v9/dist/views/dashboard/overview.html");
         JAR_LOCATION_CORE = initializeJarLocation("/sk/iway/iwcm/common/AdminTools.class");

         Logger.println(JarPackaging.class, "enableJspJarPackaging=true");
         Constants.setBoolean("enableJspJarPackaging", true);
         return;
      }

      Logger.println(JarPackaging.class, "enableJspJarPackaging=false");
   }

   /**
    * Vrati cestu k JAR suboru obsahujucemu danu cestu
    */
   private static String initializeJarLocation(String path)
   {
      URL u = JarPackaging.class.getResource(path);
      if (u != null)
      {
         //je to nieco ako jar:file:/Users/jeeff/.gradle/caches/modules-2/files-2.1/sk.iway/webjet9admin/8.6-SNAPSHOT/e8615553500ae75b655eb175276b945fe82799bd/webjet9admin-8.6-SNAPSHOT.jar!/META-INF/resources/WEB-INF/build.properties
         String fileLocation = u.getPath();
         if (u.getPath().contains(".jar")==false) return null;

         int i = fileLocation.indexOf(":");
         int j = fileLocation.indexOf(".jar!");
         if (j > i) fileLocation = fileLocation.substring(i+1, j+4);

         //na windowse to vrati cestu ako /c:/nieco co nie je platne, musi to byt c:/nieco
         if (fileLocation.charAt(2) == ':')
         {
            Logger.println(JarPackaging.class, "Detected :, probably windows, fixing path="+path);
            fileLocation = fileLocation.substring(1);
         }

         Logger.println(JarPackaging.class, "Found jarpackaging JAR: path="+path+" fileLocation="+fileLocation);

         return fileLocation;
      }
      return null;
   }

   private static String addBaseFolder(String virtualPath)
   {
      return "/META-INF/resources"+virtualPath;
   }

   private static String getJarFileLocation(String virtualPath)
   {
      if (virtualPath.startsWith("/WEB-INF/classes/")) return JAR_LOCATION_CORE;

      //toto je vynimka, mame to v components kedze je to potrebne aj pre public nod
      if (virtualPath.equals("/WEB-INF/mime.types") || virtualPath.equals("/WEB-INF/build.properties")) return JAR_LOCATION_COMPONENTS;

      if (virtualPath.startsWith("/admin") || virtualPath.startsWith("/WEB-INF/")) return JAR_LOCATION_ADMIN;

      return JAR_LOCATION_COMPONENTS;
   }

   public static boolean isJarPackaging(String virtualPath)
   {
      if (Constants.getBoolean("enableJspJarPackaging")==false) return false;

      Logger.debug(JarPackaging.class, "isJarPackaging("+virtualPath+")");

      if (virtualPath.equals("/components/"+Constants.getInstallName()) || virtualPath.startsWith("/components/"+Constants.getInstallName()+"/")) return false;

      //TODO: doplnit nejake dalsie vynimky podla konf. premennej

      //kvoli performance je to takto natvrdo
      if (virtualPath.startsWith("/admin") || virtualPath.startsWith("/components") || virtualPath.startsWith("/apps") ||
          virtualPath.equals("/WEB-INF/build.properties") || virtualPath.equals("/WEB-INF/sql/autoupdate.xml") || virtualPath.equals("/WEB-INF/sql/autoupdate-webjet9.xml") ||
          virtualPath.startsWith("/WEB-INF/sql/blank_web") || virtualPath.startsWith("/WEB-INF/classes/text") || virtualPath.startsWith("/WEB-INF/fonts/") ||
          virtualPath.equals("/WEB-INF/mime.types") || virtualPath.equals("/WEB-INF/struts-config.xml"))
      {
         //tu nekontrolujeme ci existuje, automaticky je to JarPackaging, az nasledne sa k tomu pridaju fyzicke subory v listFiles
         if ("/components".equals(virtualPath) || "/apps".equals(virtualPath))
         {
            return true;
         }

         //overme ci existuje realny subor, ak ano, tak to nie je jarpackaging
         //musime pouzit File, lebo IwcmFile by bolo nazad zacyklenie!!!
         //TODO: nejaka cache, asi idealne na cele isJarPackaging
         File f = new File(Tools.getRealPath(virtualPath));
         if (f.exists()) return false;

         //subor neexistuje, takze je to v jarku
         return true;
      }

      return false;
   }

   public static IwcmFile[] listFiles(String virtualPath)
   {
      //Logger.debug(JarPackaging.class, "listFiles("+virtualPath+")");

      FileSystem fs = null;
      DirectoryStream<Path> directoryStream = null;
      try
      {
         List<IwcmFile> files = new ArrayList<>();
         String realPath = Tools.getRealPath(virtualPath);

         // Run with JAR file
         Path jarFile = Paths.get(getJarFileLocation(virtualPath));

         Logger.debug(JarPackaging.class, "listFiles("+virtualPath+"), jarFile="+jarFile);

         Set<String> existingFileNames = new HashSet<>();
         ClassLoader loader = null;
         fs = FileSystems.newFileSystem(jarFile, loader);
         try {
            directoryStream = Files.newDirectoryStream(fs.getPath(addBaseFolder(virtualPath)));
            for(Path p: directoryStream)
            {
               Logger.debug(JarPackaging.class,"FileName="+p.getFileName()+" root="+p.getRoot());

               IwcmFile f = new IwcmFile(realPath, p.getFileName().toString());
               files.add(f);
               existingFileNames.add(Tools.replace(p.getFileName().toString(), "/", ""));
            }
         } catch (NotDirectoryException nde) {
            //adresar v JAR neexistuje, nevadi, skusime este realny suborovy system
         }

         //ak je nieco v JarPackagingu pridaj pripadne skutocne adresare na disku, jedna sa hlavne o /components kde su komponenty aj v JARku aj custom na disku
         File physicalDir = new File(Tools.getRealPath(virtualPath));
         if (physicalDir.exists())
         {
            File[] physicalFiles = physicalDir.listFiles();
            if (physicalFiles != null)
            {
               for (File file : physicalFiles)
               {
                  if (existingFileNames.contains(file.getName())) continue;
                  IwcmFile f = new IwcmFile(file);
                  files.add(f);
                  existingFileNames.add(file.getName());
               }
            }
         }

         return files.toArray(new IwcmFile[0]);
      }
      catch (IOException ex)
      {
         sk.iway.iwcm.Logger.error(ex);
      }
      finally
      {
         if (directoryStream != null) try { directoryStream.close(); } catch (IOException ex) { sk.iway.iwcm.Logger.error(ex); }
         if (fs != null) try { fs.close(); } catch (IOException ex) { sk.iway.iwcm.Logger.error(ex); }
      }

      return new IwcmFile[0];
   }

   public static boolean isDirectory(String virtualPath)
   {
      //TODO: overit nejako, ci je to subor alebo adresar
      URL u = JarPackaging.class.getResource("/META-INF/resources"+virtualPath);
      //Logger.debug(JarPackaging.class, "isDirectory("+virtualPath+"), url="+u);
      if (u != null && virtualPath.contains(".")==false) return true;

      if (u == null) {
         //over, ci to nie je skutocny adresar na disku
         File f = new File(Tools.getRealPath(virtualPath));
         if (f.exists() && f.isDirectory()) return true;
      }

      return false;
   }

   public static boolean isFile(String virtualPath)
   {
      //TODO: overit nejako, ci je to subor alebo adresar
      URL u = JarPackaging.class.getResource("/META-INF/resources"+virtualPath);
      Logger.debug(JarPackaging.class, "isFile("+virtualPath+"), url="+u);
      if (u != null && virtualPath.contains(".")) return true;

      return false;
   }

   public static boolean exists(String virtualPath)
   {
      URL u = JarPackaging.class.getResource("/META-INF/resources"+virtualPath);
      Logger.debug(JarPackaging.class, "exists("+virtualPath+"), url="+u);
      if (u != null) return true;

      return false;
   }

   public static InputStream getInputStream(String virtualPath)
   {
      return JarPackaging.class.getResourceAsStream(addBaseFolder(virtualPath));
   }

   public static long getSize(String virtualPath)
   {
      long size = 0;
      JarFile jarFile = null;
      try
      {
         jarFile = new JarFile(getJarFileLocation(virtualPath));
         //po skumani sa vola jarFile.getEntry bez uvodneho lomitka
         size = jarFile.getEntry(addBaseFolder(virtualPath).substring(1)).getSize();
      }
      catch (Exception ex)
      {
         sk.iway.iwcm.Logger.error(ex);
      }
      finally {
         if (jarFile != null) try { jarFile.close(); } catch (Exception ex) { sk.iway.iwcm.Logger.error(ex); }
      }
      return size;
   }

   public static long getLastMofified(String virtualPath)
   {
      long lastmodified = 0;
      JarFile jarFile = null;
      try
      {
         jarFile = new JarFile(getJarFileLocation(virtualPath));
         //po skumani sa vola jarFile.getEntry bez uvodneho lomitka
         ZipEntry entry = jarFile.getEntry(addBaseFolder(virtualPath).substring(1));
         if (entry != null) lastmodified = entry.getTime();
      }
      catch (Exception ex)
      {
         sk.iway.iwcm.Logger.error(ex);
      }
      finally {
         if (jarFile != null) try { jarFile.close(); } catch (Exception ex) { sk.iway.iwcm.Logger.error(ex); }
      }
      return lastmodified;
   }

   public static List<String> getJarLocations()
   {
      List<String> locations = new ArrayList<>();
      locations.add(JAR_LOCATION_ADMIN);
      locations.add(JAR_LOCATION_COMPONENTS);
      locations.add(JAR_LOCATION_CORE);

      return locations;
   }
}