/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.internal.embedded;
import org.glassfish.api.deployment.archive.ReadableArchiveAdapter;
import java.io.*;
import java.net.URL;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.MalformedURLException;
import java.util.*;
import java.util.jar.Manifest;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.jar.JarEntry;
/**
* Abstraction for a scattered archive (parts disseminated in various directories)
*
* @author Jerome Dochez
*/
public class ScatteredArchive extends ReadableArchiveAdapter {
public static class Builder {
final String name;
File topDir = null;
File resources = null;
final List<URL> urls = new ArrayList<URL>();
final Map<String, File> metadata = new HashMap<String, File>();
/**
* Construct a new scattered archive builder with the minimum information
* By default, a scattered archive is not different from any other
* archive where all the files are located under a top level
* directory (topDir).
* Some files can then be scattered in different locations and be specified
* through the appropriate setters.
* Alternatively, topDir can be null to specify a truely scattered archive
* and all the locations must be specified.
*
* @param name archive name
* @param topDir top level directory
*/
public Builder(String name, File topDir) {
this.name = name;
this.topDir = topDir;
}
/**
* Construct a new scattered archive builder with a set of URLs as repository
* for locating archive resources (like .class files).
* @param name archive name
* @param urls set of resources repository
*/
public Builder(String name, Collection<URL> urls) {
this.name = name;
for (URL u : urls) {
this.urls.add(u);
}
}
/**
* Sets the location of resources files
*
* @param resources the resources directory
* @return itself
*/
public Builder resources(File resources) {
if (!resources.exists()) {
throw new IllegalArgumentException(resources.getAbsolutePath() + " not found");
}
this.resources = resources;
return this;
}
/**
* Add a new metadata locator for this scattered archive. A metadata is identified
* by its name (like WEB-INF/web.xml) and the location of the metadata is used when
* the embedded server is requesting the metadata file.
* The name for this metadata will be obtained by doing metadata.getName()
*
* @param metadata the metadata file location
*
* @return itself
*/
public Builder addMetadata(File metadata) {
if (!metadata.exists()) {
throw new IllegalArgumentException(metadata.getAbsolutePath() + " not found");
}
return addMetadata(metadata.getName(), metadata);
}
/**
* Add a new metadata locator for this scattered archive. A metadata is identified
* by its name (like WEB-INF/web.xml) and the location of the metadata is used when
* the embedded server is requesting the metadata file.
*
* @param name name of the metadata (eg WEB-INF/web.xml or web.xml or META-INF/ejb.xml
* or ejb.xml).
*
* @param metadata the metadata file location
*
* @return itself
*/
public Builder addMetadata(String name, File metadata) {
if (!metadata.exists()) {
throw new IllegalArgumentException(metadata.getAbsolutePath() + " not found");
}
this.metadata.put(name, metadata);
return this;
}
/**
* Adds a directory to the classes classpath. Will be used to retrieve requested .class
* files.
*
* @param location must be a directory location
* @return itself
*/
public Builder addClassPath(File location) {
if (!location.isDirectory()) {
throw new IllegalArgumentException("location is not a directory");
}
try {
this.urls.add(location.toURI().toURL());
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
}
return this;
}
/**
* Adds a URL for the classes classpath. Will be used to retrieve requested .class files
*
* @param classpath the new classpath element.
* @return itself
*/
public Builder addClassPath(URL classpath) {
this.urls.add(classpath);
return this;
}
/**
* Creates a new scattered jar file using this builder instance configuration.
* The resulting instance will behave like a jar file when introspected by the
* embedded instance.
*
* @return new scattered instance jar file
*/
public ScatteredArchive buildJar() {
return new ScatteredArchive(this, Builder.type.jar);
}
/**
* Creates a new scattered war file using this builder instance configuration.
* The resulting instance will behave like a war file when introspected by the
* embedded instance.
*
* @return the scattered instance war file
*/
public ScatteredArchive buildWar() {
return new ScatteredArchive(this, Builder.type.war);
}
/**
* Supported types of scattered archives.
*/
public enum type {
jar, war
}
}
final String name;
final File topDir;
final File resources;
final List<URL> urls = new ArrayList<URL>();
final Map<String, File> metadata = new HashMap<String, File>();
final Builder.type type;
final String prefix;
private ScatteredArchive(Builder builder, Builder.type type) {
name = builder.name;
topDir = builder.topDir;
resources = builder.resources;
urls.addAll(builder.urls);
if (topDir!=null && type!=Builder.type.war) {
try {
urls.add(topDir.toURI().toURL());
} catch (MalformedURLException ignore) {
}
}
metadata.putAll(builder.metadata);
this.type = type;
prefix = type==Builder.type.war?"WEB-INF/classes":null;
}
// todo : look at Open(URI), is it ok ?
/**
* Get the classpath URLs
*
* @return A read-only copy of the classpath URL Collection
*/
public Iterable<URL> getClassPath() {
return Collections.unmodifiableCollection(urls);
}
/**
* @return The resources directory
*/
public File getResourcesDir() {
return resources;
}
/**
* Returns the InputStream for the given entry name
* The file name must be relative to the root of the module.
*
* @param arg the file name relative to the root of the module.
* @return the InputStream for the given entry name or null if not found.
*/
public InputStream getEntry(String arg) throws IOException {
File f = getFile(arg);
if (f!=null && f.exists()) return new FileInputStream(f);
JarFile jar = getJarWithEntry(arg);
if (jar != null) {
ZipEntry entry = jar.getEntry(arg);
if (entry != null) {
return jar.getInputStream(entry);
}
}
return null;
}
@Override
public long getEntrySize(String arg) {
File f = getFile(arg);
if (f!=null && f.exists()) return f.length();
JarFile jar = getJarWithEntry(arg);
if (jar != null) {
ZipEntry entry = jar.getEntry(arg);
if (entry != null) {
return jar.getEntry(arg).getSize();
}
}
return 0L;
}
/**
* Returns whether or not a file by that name exists
* The file name must be relative to the root of the module.
*
* @param name the file name relative to the root of the module.
* @return does the file exist?
*/
public boolean exists(String name) throws IOException {
if ("WEB-INF".equals(name) && type == Builder.type.war) {
return true;
}
return getEntry(name) != null;
}
/**
* Returns an enumeration of the module file entries. All elements
* in the enumeration are of type String. Each String represents a
* file name relative to the root of the module.
* <p><strong>Currently under construction</strong>
*
* @return an enumeration of the archive file entries.
*/
public Enumeration<String> entries() {
// TODO: abstraction breakage. We need file-level abstraction for archive
// and then more structured abstraction.
Vector<String> entries = new Vector<String>();
File localResources = resources;
for (URL url : urls) {
try {
if (localResources!=null && localResources.toURI().toURL().sameFile(url)) {
localResources=null;
}
File f;
try {
f = new File(url.toURI());
} catch(URISyntaxException e) {
f = new File(url.getPath());
}
if (f.isFile()) {
JarFile jar = new JarFile(f);
Enumeration<JarEntry> jarEntries = jar.entries();
while (jarEntries.hasMoreElements()) {
JarEntry jarEntry = jarEntries.nextElement();
if (jarEntry.isDirectory()) {
continue;
}
entries.add(jarEntry.getName());
}
} else {
getListOfFiles(f, prefix, entries);
}
} catch (Exception e) {
e.printStackTrace();
}
}
if (localResources!=null) {
getListOfFiles(localResources, null, entries);
}
return entries.elements();
}
private void getListOfFiles(File directory, String prefix, List<String> list) {
if (!directory.isDirectory())
return;
for (File f : directory.listFiles()) {
String name = prefix==null?f.getName():prefix+"/"+f.getName();
if (f.isDirectory()) {
getListOfFiles(f, name ,list);
} else {
list.add(name);
}
}
}
/**
* Returns the manifest information for this archive
*
* @return the manifest info
*/
public Manifest getManifest() throws IOException {
InputStream is = getEntry(JarFile.MANIFEST_NAME);
if (is != null) {
try {
return new Manifest(is);
} finally {
is.close();
}
}
return new Manifest();
}
/**
* Returns the path used to create or open the underlying archive
* <p/>
* <p/>
* TODO: abstraction breakage:
* Several callers, most notably {@link org.glassfish.api.deployment.DeploymentContext#getSourceDir()}
* implementation, assumes that this URI is an URL, and in fact file URL.
* <p/>
* <p/>
* If this needs to be URL, use of {@link URI} is misleading. And furthermore,
* if its needs to be a file URL, this should be {@link File}.
*
* @return the path for this archive.
*/
public URI getURI() {
if (topDir != null) {
return topDir.toURI();
}
if (resources != null) {
return resources.toURI();
}
try {
//TODO : Fix this
if (urls.size() > 0) {
for (URL url : urls) {
File f = new File(url.toURI());
if (f.isFile())
return url.toURI();
}
return urls.get(0).toURI();
}
return null;
} catch (Exception e) {
return null;
}
}
/**
* Returns the name of the archive.
* <p/>
* Implementations should not return null.
*
* @return the name of the archive
*/
public String getName() {
return name;
}
/**
* Returns the archive type
* @return the archive type
*/
public Builder.type type() {
return type;
}
/**
* Returns an enumeration of the module file entries with the
* specified prefix. All elements in the enumeration are of
* type String. Each String represents a file name relative
* to the root of the module.
* <p><strong>Currently Not Supported</strong>
*
* @param s the prefix of entries to be included
* @return an enumeration of the archive file entries.
*/
public Enumeration<String> entries(String s) {
Enumeration <String> entries = entries();
Vector<String> prefixedEntries = new Vector();
while (entries.hasMoreElements()) {
String entry = (String)entries.nextElement();
if (entry.startsWith(s))
prefixedEntries.add(entry);
}
return prefixedEntries.elements();
}
@Override
public Collection<String> getDirectories() throws IOException {
return new ArrayList<String>();
}
public String toString() {
return super.toString() + " located at " + (topDir == null ? resources : topDir);
}
public File getFile(String name) {
if (metadata.containsKey(name)) {
return metadata.get(name);
}
String shortName = (name.indexOf("/") != -1 ? name.substring(name.indexOf("/") + 1) : name);
if (metadata.containsKey(shortName)) {
return metadata.get(shortName);
}
if (resources != null) {
File f = new File(resources, name);
if (f.exists()) {
return f;
}
}
if (prefix!=null) {
if (name.startsWith(prefix)) {
name = name.substring(prefix.length()+1);
}
}
for (URL url : urls) {
File f = null;
try {
f = new File(url.toURI());
} catch(URISyntaxException e) {
f = new File(url.getPath());
}
f = new File(f, name);
if (f.exists()) {
return f;
}
}
return null;
}
private JarFile getJarWithEntry(String name) {
for (URL url : urls) {
File f = null;
try {
f = new File(url.toURI());
} catch(URISyntaxException e) {
f = new File(url.getPath());
}
try {
if (f.getName().endsWith(".jar")) {
JarFile jar = new JarFile(f);
if (jar.getEntry(name) != null) {
return jar;
}
}
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
return null;
}
@Override
public void close() throws IOException {
}
}