/* * Copyright (C) 2013 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.container.ar; import org.exoplatform.commons.utils.SecurityHelper; import org.exoplatform.container.xml.Deserializer; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.security.PrivilegedExceptionAction; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; /** * Defines an archive with all its properties * * @author <a href="mailto:nfilotto@exoplatform.com">Nicolas Filotto</a> * @version $Id$ * */ public class Archive { private static final Log LOG = ExoLogger.getLogger("exo.kernel.container.Archive"); /** * The specific handler for the archive {@link URL} */ private static final ArchiveURLStreamHandler HANDLER = new ArchiveURLStreamHandler(); /** * The protocol used to define an URL that defines a resource into an archive */ public static final String PROTOCOL = "ar"; /** * The most common description of a Web Application ARchive */ public static final Archive WAR = new Archive("war", false, true, null); /** * The most common description of a Enterprise Application ARchive */ public static final Archive EAR = new Archive("ear", false, true, Collections.singleton(WAR)); /** * The type of the archive */ private final String type; /** * Indicates whether the archive is replaceable with a directory without extension */ private final boolean useDirWoExt; /** * Indicates whether the archive can be a directory */ private final boolean allowsDir; /** * The archives that can be included in the current archive */ private final Set<Archive> subArchives; /** * The default constructor */ public Archive(String type, boolean useDirWoExt, boolean allowsDir, Set<Archive> subArchives) { this.type = type; this.useDirWoExt = useDirWoExt; this.allowsDir = allowsDir; this.subArchives = subArchives; } /** * @return the type of the archive */ public String getType() { return type; } /** * Indicates whether the archive is replaceable with a directory without extension */ public boolean isUseDirWoExt() { return useDirWoExt; } /** * Indicates whether the archive can be a directory */ public boolean isAllowsDir() { return allowsDir; } /** * @return the archives that can be included in the current archive */ public Set<Archive> getSubArchives() { return subArchives; } /** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((type == null) ? 0 : type.hashCode()); return result; } /** * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Archive other = (Archive)obj; if (type == null) { if (other.type != null) return false; } else if (!type.equals(other.type)) return false; return true; } /** * @see java.lang.Object#toString() */ @Override public String toString() { return "Archive [type=" + type + ", useDirWoExt=" + useDirWoExt + ", allowsDir=" + allowsDir + ", subArchives=" + subArchives + "]"; } /** * Gives a Collection of URL corresponding to the configuration files that could be found under the given directories * inside archives with the given suffixes * @param appDeployDirectories the list of directory to scan * @param appDeployArchives the list of archives to scan * @param configuration the relative path to the configuration file * @return the URL of the configuration files that could be found * @throws IOException If we cannot access to the content of the archives for some reasons */ public static Collection<URL> getConfigurationURL(List<String> appDeployDirectories, Set<Archive> appDeployArchives, String configuration) throws IOException { if (configuration == null || configuration.isEmpty()) throw new IllegalArgumentException("The path to the configuration cannot be empty"); if (appDeployDirectories == null || appDeployDirectories.isEmpty() || appDeployArchives == null || appDeployArchives.isEmpty()) return Collections.emptyList(); Collection<URL> result = new LinkedHashSet<URL>(); for (String directory : appDeployDirectories) { File dir = new File(directory); if (!dir.exists()) { LOG.debug("The directory {} doesn't exist", directory); continue; } result.addAll(getConfigurationURL(dir, appDeployArchives, configuration, true)); } return result; } /** * Gives a Collection of URL corresponding to the configuration files that could be found under the given directory * inside archives with the given suffixes * @param dir the directory to scan * @param appDeployArchives the list of archives to scan * @param configuration the relative path to the configuration file * @param enableRecursion enable the recursion * @return the URL of the configuration files that could be found * @throws IOException If we cannot access to the content of the archives for some reasons */ private static Collection<URL> getConfigurationURL(File dir, final Set<Archive> appDeployArchives, String configuration, final boolean enableRecursion) throws IOException { final Map<String, Archive> types = new HashMap<String, Archive>(); for (Archive archive : appDeployArchives) { types.put(archive.getType(), archive); } Collection<URL> result = new LinkedHashSet<URL>(); File[] files = dir.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { Archive extension = null; int index = name.lastIndexOf('.'); if (index != -1) { String ext = name.substring(index + 1).toLowerCase(); extension = types.get(ext); } File f = new File(dir, name); if (f.isDirectory()) { if (extension == null) { if (!enableRecursion) { return false; } for (Archive archive : appDeployArchives) { if (archive.isUseDirWoExt()) { return true; } } } else if (extension.isAllowsDir()) { return true; } } else if (extension != null) { if (!extension.isUseDirWoExt()) { return true; } File file = new File(dir, name.substring(0, name.length() - extension.getType().length() - 1)); return !file.exists() || !file.isDirectory(); } return false; } }); Arrays.sort(files); for (File file : files) { if (file.isDirectory()) { File f = new File(file, configuration); if (f.exists() && f.isFile()) { result.add(parse(f.getAbsolutePath())); } else { int index = file.getName().lastIndexOf('.'); Archive extension = null; if (index == -1) { if (!enableRecursion) { continue; } for (Archive archive : appDeployArchives) { if (archive.isUseDirWoExt()) { extension = archive; break; } } } else { String ext = file.getName().substring(index + 1).toLowerCase(); extension = types.get(ext); } if (extension == null || extension.getSubArchives() == null || extension.getSubArchives().isEmpty()) { continue; } result.addAll(getConfigurationURL(file, extension.getSubArchives(), configuration, false)); } } else { ZipFile f = new ZipFile(file); try { ZipEntry entry = f.getEntry(configuration); if (entry != null && f.getEntry(configuration + "/") == null) { result.add(parse(file.getAbsolutePath() + "!/" + configuration)); } else { int indexExt = file.getName().lastIndexOf('.'); if (indexExt == -1) throw new IllegalStateException("Cannot find the extension of the file " + file.getAbsolutePath()); String extFile = file.getName().substring(indexExt + 1).toLowerCase(); if (extFile == null) throw new IllegalStateException("Cannot find the extension of the file " + file.getAbsolutePath()); Archive extension = types.get(extFile); if (extension == null) throw new IllegalStateException("Cannot find the archive corresponding to the file " + file.getAbsolutePath()); if (extension.getSubArchives() == null || extension.getSubArchives().isEmpty()) continue; final Map<String, Archive> subTypes = new HashMap<String, Archive>(); for (Archive archive : extension.getSubArchives()) { subTypes.put(archive.getType(), archive); } Enumeration<? extends ZipEntry> entries = f.entries(); Map<String, Archive> names = new TreeMap<String, Archive>(); while (entries.hasMoreElements()) { ZipEntry ze = entries.nextElement(); String name = ze.getName(); int index = name.indexOf('/'); if (index > 0) { if (index < name.length() - 1) { // skip sub directories and files continue; } name = name.substring(0, index); } index = name.lastIndexOf('.'); if (index == -1) { // We skip files and directories without extension } String ext = name.substring(index + 1).toLowerCase(); if (subTypes.containsKey(ext)) { names.put(ze.getName(), subTypes.get(ext)); } } for (String name : names.keySet()) { Archive a = names.get(name); ZipEntry ze = f.getEntry(name); if (ze.isDirectory()) { if (!a.isAllowsDir()) { continue; } ze = f.getEntry(name + configuration); if (ze != null && f.getEntry(name + configuration + "/") == null) { result.add(parse(file.getAbsolutePath() + "!/" + name + configuration)); } } else { ZipInputStream zis = new ZipInputStream(f.getInputStream(ze)); try { ZipEntry subZP; while ((subZP = zis.getNextEntry()) != null) { if (!subZP.isDirectory() && subZP.getName().equals(configuration)) { result.add(parse(file.getAbsolutePath() + "!/" + name + "!/" + configuration)); break; } } } finally { try { zis.close(); } catch (IOException e) { LOG.debug("Could not close the zip input stream"); } } } } } } finally { try { f.close(); } catch (IOException e) { LOG.debug("Could not close the zip file"); } } } } return result; } /** * Creates an archive URL from a String representation of that URL * @param url the String representation * @return the corresponding {@link URL} * @throws MalformedURLException If the URL is incorrect */ public static URL createArchiveURL(String url) throws MalformedURLException { url = Deserializer.resolveVariables(url); // we ensure that we don't have windows path separator in the url url = url.replace('\\', '/'); final String sUrl = url; return SecurityHelper.doPrivilegedMalformedURLExceptionAction(new PrivilegedExceptionAction<URL>() { public URL run() throws Exception { return new URL(null, sUrl, HANDLER); } }); } /** * Indicates whether or not the provided URL is an archive URL * @param url the String representation of the URL to check * @return <code>true</code> if it is an archive URL, false otherwise */ public static boolean isArchiveURL(String url) { return url.startsWith("ar:"); } /** * Converts a string to an archive {@link URL} * @param path2Convert the path to convert into an archive {@link URL} * @return an archive {@link URL} * @throws MalformedURLException if the {@link URL} could not be created */ static URL parse(String path2Convert) throws MalformedURLException { if (File.separatorChar != '/') path2Convert = path2Convert.replace(File.separatorChar, '/'); if (!path2Convert.startsWith("/")) path2Convert = "/" + path2Convert; return new URL(Archive.PROTOCOL, null, -1, path2Convert, Archive.HANDLER); } }