org.jboss.shrinkwrap.impl.base.ArchiveBase Maven / Gradle / Ivy
Show all versions of shrinkwrap-impl-base Show documentation
/*
* JBoss, Home of Professional Open Source
* Copyright 2009, Red Hat Middleware LLC, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.shrinkwrap.impl.base;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.HashSet;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ArchiveFormat;
import org.jboss.shrinkwrap.api.ArchivePath;
import org.jboss.shrinkwrap.api.ArchivePaths;
import org.jboss.shrinkwrap.api.Assignable;
import org.jboss.shrinkwrap.api.Configuration;
import org.jboss.shrinkwrap.api.Filter;
import org.jboss.shrinkwrap.api.Filters;
import org.jboss.shrinkwrap.api.IllegalArchivePathException;
import org.jboss.shrinkwrap.api.Node;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.ArchiveAsset;
import org.jboss.shrinkwrap.api.asset.Asset;
import org.jboss.shrinkwrap.api.asset.NamedAsset;
import org.jboss.shrinkwrap.api.exporter.StreamExporter;
import org.jboss.shrinkwrap.api.formatter.Formatter;
import org.jboss.shrinkwrap.api.formatter.Formatters;
import org.jboss.shrinkwrap.api.importer.ArchiveImportException;
import org.jboss.shrinkwrap.impl.base.io.IOUtil;
import org.jboss.shrinkwrap.impl.base.path.BasicPath;
import org.jboss.shrinkwrap.spi.ArchiveFormatAssociable;
import org.jboss.shrinkwrap.spi.Configurable;
import org.jboss.shrinkwrap.spi.Identifiable;
/**
* Base implementation of {@link Archive}. Contains support for operations (typically overloaded) that are not specific
* to any particular storage implementation, and may be delegated to other forms.
*
* @author ALR
* @author John Bailey
* @version $Revision: $
*/
public abstract class ArchiveBase> implements Archive, Configurable, ArchiveFormatAssociable,
Identifiable {
// -------------------------------------------------------------------------------------||
// Class Members -----------------------------------------------------------------------||
// -------------------------------------------------------------------------------------||
/**
* Logger
*/
private static final Logger log = Logger.getLogger(ArchiveBase.class.getName());
// -------------------------------------------------------------------------------------||
// Instance Members --------------------------------------------------------------------||
// -------------------------------------------------------------------------------------||
/**
* Name of the archive
*/
private final String name;
/**
* Configuration for this archive
*/
private final Configuration configuration;
/**
* Globally-unique ID for this archive
*/
private String id;
// -------------------------------------------------------------------------------------||
// Constructor -------------------------------------------------------------------------||
// -------------------------------------------------------------------------------------||
/**
* Constructor
*
* Creates a new Archive with the specified name
*
* @param name
* Name of the archive
* @param configuration
* The configuration for this archive
* @throws IllegalArgumentException
* If the name was not specified
*/
protected ArchiveBase(final String name, final Configuration configuration) throws IllegalArgumentException {
// Precondition checks
Validate.notNullOrEmpty(name, "name must be specified");
Validate.notNull(configuration, "configuration must be specified");
// Set
this.name = name;
this.configuration = configuration;
this.setId(UUID.randomUUID().toString());
}
// -------------------------------------------------------------------------------------||
// Required Implementations -----------------------------------------------------------||
// -------------------------------------------------------------------------------------||
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.spi.ArchiveFormatAssociable#getArchiveFormat()
*/
@Override
public ArchiveFormat getArchiveFormat() {
return ArchiveFormat.UNKNOWN;
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#add(org.jboss.shrinkwrap.api.asset.Asset, java.lang.String)
*/
@Override
public T add(final Asset asset, final String target) throws IllegalArgumentException {
// Precondition checks
Validate.notNullOrEmpty(target, "target must be specified");
Validate.notNull(asset, "asset must be specified");
// Make a Path from the target
final ArchivePath path = new BasicPath(target);
// Delegate
return this.add(asset, path);
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#add(org.jboss.shrinkwrap.api.asset.Asset, java.lang.String,
* java.lang.String)
*/
@Override
public T add(final Asset asset, final String target, final String name) throws IllegalArgumentException {
Validate.notNull(target, "target must be specified");
final ArchivePath path = ArchivePaths.create(target);
return this.add(asset, path, name);
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#add(org.jboss.shrinkwrap.api.asset.Asset,
* org.jboss.shrinkwrap.api.ArchivePath, java.lang.String)
*/
@Override
public T add(final Asset asset, final ArchivePath path, final String name) {
// Precondition checks
Validate.notNull(path, "No path was specified");
Validate.notNullOrEmpty(name, "No target name name was specified");
Validate.notNull(asset, "No asset was was specified");
// Make a relative path
final ArchivePath resolvedPath = new BasicPath(path, name);
// Delegate
return this.add(asset, resolvedPath);
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#get(java.lang.String)
*/
@Override
public Node get(final String path) throws IllegalArgumentException {
// Precondition checks
Validate.notNullOrEmpty(path, "No path was specified");
// Make a Path
final ArchivePath realPath = new BasicPath(path);
// Delegate
return get(realPath);
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#getAsType(java.lang.Class, java.lang.String)
*/
@Override
public > X getAsType(Class type, String path) {
Validate.notNull(path, "Path must be specified");
return getAsType(type, ArchivePaths.create(path));
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#getAsType(java.lang.Class, org.jboss.shrinkwrap.api.Filter)
*/
@Override
public > Collection getAsType(Class type, Filter filter) {
Validate.notNull(type, "Type must be specified");
Validate.notNull(filter, "Filter must be specified");
Collection archives = new ArrayList<>();
Map matches = getContent(filter);
for (ArchivePath path : matches.keySet()) {
archives.add(getAsType(type, path));
}
return archives;
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#getAsType(java.lang.Class, org.jboss.shrinkwrap.api.ArchivePath)
*/
@Override
public > X getAsType(Class type, ArchivePath path) {
Validate.notNull(type, "Type must be specified");
Validate.notNull(path, "ArchivePath must be specified");
Node content = get(path);
if (content == null) {
return null;
}
Asset asset = content.getAsset();
if (asset == null) {
return null;
}
if (asset instanceof ArchiveAsset) {
ArchiveAsset archiveAsset = (ArchiveAsset) asset;
return archiveAsset.getArchive().as(type);
}
ArchiveFormat archiveFormat = this.configuration.getExtensionLoader()
.getArchiveFormatFromExtensionMapping(type);
return getAsType(type, path, archiveFormat);
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#getAsType(java.lang.Class, java.lang.String,
* org.jboss.shrinkwrap.api.ArchiveFormat)
*/
@Override
public > X getAsType(final Class type, final String path, final ArchiveFormat archiveFormat) {
Validate.notNull(path, "ArchiveFormat must be specified");
return getAsType(type, ArchivePaths.create(path), archiveFormat);
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#getAsType(java.lang.Class, org.jboss.shrinkwrap.api.ArchivePath,
* org.jboss.shrinkwrap.api.ArchiveFormat)
*/
@Override
public > X getAsType(Class type, ArchivePath path, final ArchiveFormat archiveFormat) {
Validate.notNull(type, "Type must be specified");
Validate.notNull(path, "ArchivePath must be specified");
Validate.notNull(archiveFormat, "ArchiveFormat must be specified");
// Get stream importer/exporter bindings for specified format
final ArchiveFormatStreamBindings formatBinding = new ArchiveFormatStreamBindings(archiveFormat);
Node content = get(path);
if (content == null) {
return null;
}
Asset asset = content.getAsset();
if (asset == null) {
return null;
}
// If we already have an archive asset, just return it as-is; don't reimport
if (asset.getClass() == ArchiveAsset.class) {
return ArchiveAsset.class.cast(asset).getArchive().as(type);
}
InputStream stream = null;
try {
stream = asset.openStream();
X archive = ShrinkWrap.create(formatBinding.getImporter(), path.get()).importFrom(stream).as(type);
delete(path);
add(new ArchiveAsset(archive, formatBinding.getExporter()), path);
return archive;
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
throw new ArchiveImportException("Stream not closed after import", e);
}
}
}
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#getAsType(java.lang.Class, org.jboss.shrinkwrap.api.Filter,
* org.jboss.shrinkwrap.api.ArchiveFormat)
*/
@Override
public > Collection getAsType(Class type, Filter filter,
final ArchiveFormat archiveFormat) {
Validate.notNull(type, "Type must be specified");
Validate.notNull(filter, "Filter must be specified");
Validate.notNull(archiveFormat, "ArchiveFormat must be specified");
Collection archives = new ArrayList<>();
Map matches = getContent(filter);
for (ArchivePath path : matches.keySet()) {
archives.add(getAsType(type, path, archiveFormat));
}
return archives;
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#filter(Filter)
*/
@Override
public T filter(Filter filter) {
return this.shallowCopy(filter).as(getActualClass());
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#add(org.jboss.shrinkwrap.api.Archive, org.jboss.shrinkwrap.api.ArchivePath,
* java.lang.Class)
*/
@Override
public T add(final Archive> archive, final ArchivePath path, Class extends StreamExporter> exporter) {
// Precondition checks
Validate.notNull(path, "No path was specified");
Validate.notNull(archive, "No archive was specified");
Validate.notNull(exporter, "No exporter was specified");
// Make a Path
final String archiveName = archive.getName();
final ArchivePath contentPath = new BasicPath(path, archiveName);
// Create ArchiveAsset
final ArchiveAsset archiveAsset = new ArchiveAsset(archive, exporter);
// Delegate
return add(archiveAsset, contentPath);
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#add(NamedAsset))
*/
@Override
public T add(NamedAsset namedAsset) {
Validate.notNull(namedAsset, "No named asset was specified");
return add(namedAsset, namedAsset.getName());
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#addAsDirectory(java.lang.String)
*/
@Override
public T addAsDirectory(final String path) throws IllegalArgumentException {
// Precondition check
Validate.notNullOrEmpty(path, "path must be specified");
// Delegate and return
return this.addAsDirectory(ArchivePaths.create(path));
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#addAsDirectories(org.jboss.shrinkwrap.api.ArchivePath[])
*/
@Override
public T addAsDirectories(final ArchivePath... paths) throws IllegalArgumentException {
// Precondition check
Validate.notNull(paths, "paths must be specified");
// Add
for (final ArchivePath path : paths) {
this.addAsDirectory(path);
}
// Return
return covariantReturn();
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#addAsDirectories(java.lang.String[])
*/
@Override
public T addAsDirectories(final String... paths) throws IllegalArgumentException {
// Precondition check
Validate.notNull(paths, "paths must be specified");
// Represent as array of Paths
final Collection pathsCollection = new ArrayList<>(paths.length);
for (final String path : paths) {
pathsCollection.add(ArchivePaths.create(path));
}
// Delegate and return
return this.addAsDirectories(pathsCollection.toArray(new ArchivePath[] {}));
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#getName()
*/
@Override
public final String getName() {
return name;
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#getId()
*/
@Override
public String getId() {
return this.id.toString();
}
@Override
public void setId(final String id) throws IllegalArgumentException {
Validate.notNullOrEmpty(id, "ID must be specified");
this.id = id;
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#merge(org.jboss.shrinkwrap.api.Archive)
*/
@Override
public T merge(final Archive> source) throws IllegalArgumentException {
return merge(source, new BasicPath());
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#merge(org.jboss.shrinkwrap.api.Archive, org.jboss.shrinkwrap.api.Filter)
*/
@Override
public T merge(Archive> source, Filter filter) throws IllegalArgumentException {
return merge(source, new BasicPath(), filter);
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#merge(org.jboss.shrinkwrap.api.Archive,
* org.jboss.shrinkwrap.api.ArchivePath)
*/
@Override
public T merge(final Archive> source, final ArchivePath path) throws IllegalArgumentException {
Validate.notNull(source, "No source archive was specified");
Validate.notNull(path, "No path was specified");
return merge(source, path, Filters.includeAll());
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#merge(org.jboss.shrinkwrap.api.Archive, java.lang.String,
* org.jboss.shrinkwrap.api.Filter)
*/
@Override
public T merge(final Archive> source, final String path, final Filter filter)
throws IllegalArgumentException {
Validate.notNull(path, "path must be specified");
return this.merge(source, ArchivePaths.create(path), filter);
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#merge(org.jboss.shrinkwrap.api.Archive, java.lang.String)
*/
@Override
public T merge(final Archive> source, final String path) throws IllegalArgumentException {
Validate.notNull(path, "path must be specified");
return this.merge(source, ArchivePaths.create(path));
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#shallowCopy()
*/
@Override
public final Archive shallowCopy() {
return this.shallowCopy(Filters.includeAll());
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#shallowCopy(Filter)
*/
@Override
public final Archive shallowCopy(Filter filter) {
Validate.notNull(filter, "Filter must be specified");
// Use existing configuration
final Configuration configuration = this.getConfiguration();
// Create a new archive to which we'll shallow copy all assets
final Archive from = this;
//TODO SHRINKWRAP-362
/*
* The design of this particular hack is particularly offensive:
*
* First off, we're binding this abstract parent to use a specific
* implementation in creating new instances.
*
* Second, there's the unchecked casting.
*
* Mostly, however, is that this "shallowCopy" issue
* denoted by SHRINKWRAP-353 calls into question
* the whole design of having ArchiveBase implement
* the full contract set defined by Archive. Archive is
* targeted to be an end-user view; and ArchiveBase is really
* a set of operations for the underlying storage mechanism. What
* would be much more appropriate is a contract set for Archive, and
* a separate contract for the underlying storage (ie. file system).
* To be addressed in SHRINKWRAP-362.
*
*/
@SuppressWarnings("unchecked")
final Archive to = Archive.class.cast(new MemoryMapArchiveImpl(configuration));
// Now loop through and add all content
for (final ArchivePath path : from.getContent().keySet()) {
Asset asset = from.get(path).getAsset();
if (!filter.include(path)) {
continue;
}
if (asset == null) {
to.addAsDirectory(path);
System.out.println("\tDIRECTORY: " + path);
} else {
System.out.println("\tASSET: " + path);
to.add(asset, path);
}
}
// Return
return to;
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#merge(org.jboss.shrinkwrap.api.Archive, org.jboss.shrinkwrap.api.ArchivePath,
* org.jboss.shrinkwrap.api.Filter)
*/
@Override
public T merge(Archive> source, ArchivePath path, Filter filter) throws IllegalArgumentException {
// Precondition checks
Validate.notNull(source, "No source archive was specified");
Validate.notNull(path, "No path was specified");
Validate.notNull(filter, "No filter was specified");
// Get existing contents from source archive
final Map sourceContent = source.getContent();
Validate.notNull(sourceContent, "Source archive content can not be null.");
// Add each asset from the source archive
for (final Entry contentEntry : sourceContent.entrySet()) {
final Node node = contentEntry.getValue();
ArchivePath nodePath = new BasicPath(path, contentEntry.getKey());
if (!filter.include(nodePath)) {
continue;
}
// Delegate
if (node.getAsset() == null) {
addAsDirectory(nodePath);
} else {
add(node.getAsset(), nodePath);
}
}
return covariantReturn();
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#move(org.jboss.shrinkwrap.api.ArchivePath, org.jboss.shrinkwrap.api.ArchivePath)
*/
@Override
public T move(ArchivePath source, ArchivePath target) throws IllegalArgumentException, IllegalArchivePathException {
Validate.notNull(source, "The source path was not specified");
Validate.notNull(target, "The target path was not specified");
final Node nodeToMove = get(source);
if (null == nodeToMove) {
throw new IllegalArchivePathException(source.get() + " doesn't specify any node in the archive to move");
}
final Asset asset = nodeToMove.getAsset();
// Directory?
if (asset == null) {
this.addAsDirectory(target);
} else {
add(asset, target);
}
// move children
// can't remove from collection inside the iteration
final Set nodeToMoveChildrenCopy = new HashSet<>(nodeToMove.getChildren());
for (final Node child : nodeToMoveChildrenCopy) {
final String childName = child.getPath().get().replaceFirst(child.getPath().getParent().get(), "");
final ArchivePath childTargetPath = ArchivePaths.create(target, childName);
move(child.getPath(), childTargetPath);
}
delete(source);
return covariantReturn();
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#move(java.lang.String, java.lang.String)
*/
@Override
public T move(String source, String target) throws IllegalArgumentException, IllegalArchivePathException {
Validate.notNullOrEmpty(source, "The source path was not specified");
Validate.notNullOrEmpty(target, "The target path was not specified");
final ArchivePath sourcePath = new BasicPath(source);
final ArchivePath targetPath = new BasicPath(target);
return move(sourcePath, targetPath);
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Assignable#as(java.lang.Class)
*/
@Override
public TYPE as(final Class clazz) {
Validate.notNull(clazz, "Class must be specified");
return this.configuration.getExtensionLoader().load(clazz, this);
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#toString()
*/
@Override
public String toString() {
return this.toString(Formatters.SIMPLE);
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#toString(boolean)
*/
@Override
public String toString(final boolean verbose) {
return verbose ? this.toString(Formatters.VERBOSE) : this.toString();
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.Archive#toString(org.jboss.shrinkwrap.api.formatter.Formatter)
*/
@Override
public String toString(final Formatter formatter) throws IllegalArgumentException {
// Precondition check
if (formatter == null) {
throw new IllegalArgumentException("Formatter must be specified");
}
// Delegate
return formatter.format(this);
}
/**
* {@inheritDoc}
*/
@Override
public void writeTo(final OutputStream outputStream, final Formatter formatter) throws IllegalArgumentException {
try {
IOUtil.bufferedWriteWithFlush(outputStream, toString(formatter).getBytes());
} catch (IOException ioe) {
throw new IllegalArgumentException("Could not write Archive contents to specified OutputStream", ioe);
}
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getContent() == null) ? 0 : getContent().hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof ArchiveBase)) {
return false;
}
ArchiveBase> other = (ArchiveBase>) obj;
if (getContent() == null) {
if (other.getContent() != null) {
return false;
}
} else if (!getContent().equals(other.getContent())) {
return false;
}
if (name == null) {
if (other.name != null) {
return false;
}
} else if (!name.equals(other.name)) {
return false;
}
return true;
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.spi.Configurable#getConfiguration()
*/
@Override
public Configuration getConfiguration() {
return configuration;
}
/**
* Exposes the actual class used in casting
*/
protected abstract Class getActualClass();
// -------------------------------------------------------------------------------------||
// Internal Helper Methods -------------------------------------------------------------||
// -------------------------------------------------------------------------------------||
/**
* Provides typesafe covariant return of this instance
*/
protected final T covariantReturn() {
try {
return this.getActualClass().cast(this);
} catch (final ClassCastException cce) {
log.log(Level.SEVERE,
"The class specified by getActualClass is not a valid assignment target for this instance;"
+ " developer error");
throw cce;
}
}
}