/****************************************************************************** | |
* 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().getPath()); | |
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; | |
} | |
} |