/****************************************************************************** * Copyright (c) 2006, 2010 VMware Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0 * is available at http://www.opensource.org/licenses/apache2.0.php. * You may elect to redistribute this code under either of these licenses. * * Contributors: * VMware Inc. *****************************************************************************/ package org.eclipse.gemini.blueprint.test.internal.util.jar; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.jar.Manifest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.gemini.blueprint.test.internal.util.jar.storage.MemoryStorage; import org.eclipse.gemini.blueprint.test.internal.util.jar.storage.Storage; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.util.StringUtils; /** * Helper class for creating Jar files. Note that this class is stateful and the * same instance should not be reused. * * @author Costin Leau * */ public class JarCreator { private static final Log log = LogFactory.getLog(JarCreator.class); public static final String EVERYTHING_PATTERN = "/**/*"; private static final String CLASS_EXT = ".class"; private String[] contentPattern = new String[] { EVERYTHING_PATTERN }; private ResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver(); private Storage storage = new MemoryStorage(); private String rootPath = determineRootPath(); private boolean addFolders = true; /** collection of packages contained by this jar */ private Collection containedPackages = new TreeSet(); /** * Resources' root path (the root path does not become part of the jar). * * @return the root path */ public String determineRootPath() { return Thread.currentThread().getContextClassLoader().getResource(".").toString(); } /** * Actual jar creation. * * @param manifest to use * @param entries array of resource to include in the jar * @return the number of bytes written to the underlying stream. * * @throws IOException */ protected int addJarContent(Manifest manifest, Map entries) throws IOException { // load manifest // add it to the jar if (log.isTraceEnabled()) { if (manifest != null) log.trace("Adding MANIFEST.MF [" + manifest.getMainAttributes().entrySet() + "]"); log.trace("Adding entries:"); Set key = entries.keySet(); for (Iterator iter = key.iterator(); iter.hasNext();) { log.trace(iter.next()); } } return JarUtils.createJar(manifest, entries, storage.getOutputStream()); } /** * Create a jar using the current settings and return a {@link Resource} * pointing to the jar. * * @param manifest */ public Resource createJar(Manifest manifest) { return createJar(manifest, resolveContent()); } /** * Create a jar using the current settings and return a {@link Resource} * pointing to the jar. * * @param manifest */ public Resource createJar(Manifest manifest, Map content) { try { addJarContent(manifest, content); return storage.getResource(); } catch (IOException ex) { throw (RuntimeException) new IllegalStateException("Cannot create jar").initCause(ex); } } /** * Small utility method used for determining the file name by striping the * root path from the file full path. * * @param rootPath * @param resource * @return */ private String determineRelativeName(String rootPath, Resource resource) { try { String path = StringUtils.cleanPath(resource.getURL().toExternalForm()); return path.substring(path.indexOf(rootPath) + rootPath.length()); } catch (IOException ex) { throw (RuntimeException) new IllegalArgumentException("illegal resource " + resource.toString()).initCause(ex); } } /** * Transform the pattern and rootpath into actual resources. * * @return * @throws Exception */ private Resource[][] resolveResources() { ResourcePatternResolver resolver = getPatternResolver(); String[] patterns = getContentPattern(); Resource[][] resources = new Resource[patterns.length][]; // transform Strings into Resources for (int i = 0; i < patterns.length; i++) { StringBuilder buffer = new StringBuilder(rootPath); // do checking on lost slashes if (!rootPath.endsWith(JarUtils.SLASH) && !patterns[i].startsWith(JarUtils.SLASH)) buffer.append(JarUtils.SLASH); buffer.append(patterns[i]); try { resources[i] = resolver.getResources(buffer.toString()); } catch (IOException ex) { IllegalStateException re = new IllegalStateException("cannot resolve pattern " + buffer.toString()); re.initCause(ex); throw re; } } return resources; } /** * Resolve the jar content based on its path. Will return a map containing * the entries relative to the jar root path as keys and Spring Resource * pointing to the actual resources as values. It will also determine the * packages contained by this package. * * @return */ public Map resolveContent() { Resource[][] resources = resolveResources(); URL rootURL; String rootP = getRootPath(); try { rootURL = new URL(rootP); } catch (MalformedURLException ex) { throw (RuntimeException) new IllegalArgumentException("illegal root path given " + rootP).initCause(ex); } String rootPath = StringUtils.cleanPath(rootURL.getPath()); // remove duplicates Map entries = new TreeMap(); // save contained bundle packages containedPackages.clear(); // empty stream used for folders Resource folderResource = new ByteArrayResource(new byte[0]); // add folder entries also for (int i = 0; i < resources.length; i++) { for (int j = 0; j < resources[i].length; j++) { String relativeName = determineRelativeName(rootPath, resources[i][j]); // be consistent when adding resources to jar if (!relativeName.startsWith("/")) relativeName = "/" + relativeName; entries.put(relativeName, resources[i][j]); // look for class entries if (relativeName.endsWith(CLASS_EXT)) { // determine package (exclude first char) String clazzName = relativeName.substring(1, relativeName.length() - CLASS_EXT.length()).replace( '/', '.'); // remove class name int index = clazzName.lastIndexOf('.'); if (index > 0) clazzName = clazzName.substring(0, index); // add it to the collection containedPackages.add(clazzName); } String token = relativeName; // get folder and walk up to the root if (addFolders) { // add META-INF entries.put("/META-INF/", folderResource); int slashIndex; // stop at root folder while ((slashIndex = token.lastIndexOf('/')) > 1) { // add the folder with trailing / entries.put(token.substring(0, slashIndex + 1), folderResource); // walk the tree token = token.substring(0, slashIndex); } // add root folder //entries.put("/", folderResource); } } } if (log.isTraceEnabled()) log.trace("The following packages were discovered in the bundle: " + containedPackages); return entries; } public Collection getContainedPackages() { return containedPackages; } /** * @return Returns the contentPattern. */ public String[] getContentPattern() { return contentPattern; } /** * Pattern for content matching. Note that using {@link #EVERYTHING_PATTERN} * can become problematic on windows due to file system locking. * * @param contentPattern The contentPattern to set. */ public void setContentPattern(String[] contentPattern) { this.contentPattern = contentPattern; } /** * @return Returns the patternResolver. */ public ResourcePatternResolver getPatternResolver() { return patternResolver; } /** * @param patternResolver The patternResolver to set. */ public void setPatternResolver(ResourcePatternResolver patternResolver) { this.patternResolver = patternResolver; } /** * @return Returns the jarStorage. */ public Storage getStorage() { return storage; } /** * @param jarStorage The jarStorage to set. */ public void setStorage(Storage jarStorage) { this.storage = jarStorage; } /** * @param */ public String getRootPath() { return rootPath; } /** * @param rootPath The rootPath to set. */ public void setRootPath(String rootPath) { this.rootPath = rootPath; } /** * @return Returns the addFolders. */ public boolean isAddFolders() { return addFolders; } /** * Whether the folders in which the files reside, should be added to the * archive. Default is true since otherwise, the archive will contains only * files and no folders. * * @param addFolders The addFolders to set. */ public void setAddFolders(boolean addFolders) { this.addFolders = addFolders; } }