/*
* Copyright 2012 Harald Wellmann
*
* 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.ops4j.pax.exam.spi.war;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.ops4j.io.FileUtils;
import org.ops4j.io.StreamUtils;
import org.ops4j.io.ZipExploder;
import org.ops4j.pax.exam.TestContainerException;
import org.ops4j.pax.exam.options.WarProbeOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Builds a WAR according to a {@link WarProbeOption}.
*
* @author Harald Wellmann
*
*/
public class WarBuilder {
private static final Logger LOG = LoggerFactory.getLogger(WarBuilder.class);
/**
* Temporary directory for assembling the WAR probe.
*/
private File tempDir;
/**
* Option used to configure the WAR.
*/
private WarProbeOption option;
/**
* Composite regular expression for filtering classpath components.
*/
private Pattern filterPattern;
/**
* Constructs a WAR builder for the given option.
*
* @param tempDir
* temporary directory
* @param option
* WAR probe option
*/
public WarBuilder(File tempDir, WarProbeOption option) {
this.option = option;
this.tempDir = tempDir;
}
/**
* Builds a WAR from the given option.
*
* @return file URI referencing the WAR in a temporary directory
*/
public URI buildWar() {
if (option.getName() == null) {
option.name(UUID.randomUUID().toString());
}
processClassPath();
try {
File webResourceDir = getWebResourceDir();
File probeWar = new File(tempDir, option.getName() + ".war");
ZipBuilder builder = new ZipBuilder(probeWar);
for (String library : option.getLibraries()) {
File file = toLocalFile(library);
/*
* ScatteredArchive copies all directory class path items to WEB-INF/classes,
* so that separate CDI bean deployment archives get merged into one. To avoid
* that, we convert each directory class path to a JAR file.
*/
if (file.isDirectory()) {
file = toJar(file);
}
LOG.debug("including library {} = {}", library, file);
builder.addFile(file, "WEB-INF/lib/" + file.getName());
}
builder.addDirectory(webResourceDir, "");
builder.close();
URI warUri = probeWar.toURI();
LOG.info("WAR probe = {}", warUri);
return warUri;
}
catch (IOException exc) {
throw new TestContainerException(exc);
}
}
/**
* Creates a JAR file from the contents of the given root directory. The file is located in
* a temporary directory and is named <code>${artifactId}-${version}.jar</code> according to
* Maven conventions, if there is a {@code pom.properties} resource located anywhere under
* {@code META-INF/maven} defining the two propeties {@code artifactId} and {@code version}.
* <p>
* Otherwise the file is named <code>${uuid}.jar</code>, where {@code uuid} represents a random
* {@link UUID}.
*
* @param root root directory with archive contents
* @return archive file
* @throws IOException
*/
private File toJar(File root) throws IOException {
String artifactName = findArtifactName(root);
File jar = new File(tempDir, artifactName);
ZipBuilder builder = new ZipBuilder(jar);
builder.addDirectory(root, "");
builder.close();
return jar;
}
private String findArtifactName(File root) {
File pomProperties = FileFinder.findFile(root, "pom.properties");
if (pomProperties != null) {
Properties props = new Properties();
try {
InputStream is = new FileInputStream(pomProperties);
props.load(is);
String artifactId = props.getProperty("artifactId");
String version = props.getProperty("version");
is.close();
return String.format("%s-%s.jar", artifactId, version);
}
catch (IOException exc) {
// ignore
}
}
return UUID.randomUUID().toString() + ".jar";
}
private File toLocalFile(String anyUri) throws IOException {
URI uri = null;
try {
uri = new URI(anyUri);
File file = new File(uri);
return file;
}
catch (URISyntaxException exc) {
throw new TestContainerException(exc);
}
catch (IllegalArgumentException exc) {
InputStream is = uri.toURL().openStream();
File tempFile = File.createTempFile("paxexam", ".jar");
OutputStream os = new FileOutputStream(tempFile);
StreamUtils.copyStream(is, os, true);
return tempFile;
}
}
private void processClassPath() {
if (option.isClassPathEnabled()) {
buildClassPathPattern();
String classpath = System.getProperty("java.class.path");
String[] pathElems = classpath.split(File.pathSeparator);
for (String pathElem : pathElems) {
File file = new File(pathElem);
if (file.exists()) {
String path = file.getAbsolutePath();
Matcher matcher = filterPattern.matcher(path);
if (!matcher.find()) {
option.library(path);
}
}
}
}
}
private File getWebResourceDir() throws IOException {
File webResourceDir = new File(tempDir, "webapp");
LOG.debug("building webapp in {}", webResourceDir);
ZipExploder exploder = new ZipExploder();
webResourceDir.mkdir();
if (option.getOverlays().isEmpty()) {
option.overlay("src/main/webapp");
}
for (String overlay : option.getOverlays()) {
File file = toFile(overlay);
if (file.exists()) {
if (file.isDirectory()) {
copyDirectory(file, webResourceDir);
}
else {
exploder.processFile(file, webResourceDir);
}
}
}
File metaInfDir = new File(webResourceDir, "META-INF");
metaInfDir.mkdir();
for (String metaInfResource : option.getMetaInfResources()) {
File source = new File(metaInfResource);
if (source.isDirectory()) {
copyDirectory(source, metaInfDir);
}
else {
FileUtils.copyFile(source, new File(metaInfDir, source.getName()), null);
}
}
File webInfDir = new File(webResourceDir, "WEB-INF");
webInfDir.mkdir();
for (String webInfResource : option.getWebInfResources()) {
File source = new File(webInfResource);
if (source.isDirectory()) {
copyDirectory(source, webInfDir);
}
else {
FileUtils.copyFile(source, new File(webInfDir, source.getName()), null);
}
}
File resourceDir = new File(webResourceDir, "WEB-INF/classes");
resourceDir.mkdir();
for (Class<?> klass : option.getClasses()) {
addClass(klass, resourceDir);
}
for (String resource : option.getResources()) {
addResource(resource, resourceDir);
}
File beansXml = new File(webInfDir, "beans.xml");
if (!beansXml.exists()) {
beansXml.createNewFile();
}
return webResourceDir;
}
private void addClass(Class<?> klass, File resourceDir) throws IOException {
String resource = "/" + klass.getName().replaceAll("\\.", "/") + ".class";
addResource(resource, resourceDir);
for (Class<?> innerClass : klass.getClasses()) {
addClass(innerClass, resourceDir);
}
}
private void addResource(String resource, File resourceDir) throws IOException {
InputStream is = getClass().getResourceAsStream("/" + resource);
File target = new File(resourceDir, resource);
target.getParentFile().mkdirs();
FileOutputStream os = new FileOutputStream(target);
StreamUtils.copyStream(is, os, true);
}
private File toFile(String uriString) {
try {
URI uri = new URI(uriString);
try {
return new File(uri);
}
catch (IllegalArgumentException exc) {
InputStream is = uri.toURL().openStream();
File tempFile = File.createTempFile("pax-exam", ".tmp");
OutputStream os = new FileOutputStream(tempFile);
StreamUtils.copyStream(is, os, true);
return tempFile;
}
}
catch (URISyntaxException exc) {
throw new TestContainerException(exc);
}
catch (IOException exc) {
throw new TestContainerException(exc);
}
}
private void copyDirectory(File fromDir, File toDir) throws IOException {
for (File file : fromDir.listFiles()) {
if (file.isDirectory()) {
File targetDir = new File(toDir, file.getName());
targetDir.mkdir();
copyDirectory(file, targetDir);
}
else {
FileUtils.copyFile(file, new File(toDir, file.getName()), null);
}
}
}
private void buildClassPathPattern() {
List<String> classpathFilters = option.getClassPathFilters();
if (classpathFilters.isEmpty()) {
filterPattern = Pattern.compile("^$");
return;
}
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < classpathFilters.size(); i++) {
if (i > 0) {
buffer.append("|");
}
buffer.append("(");
buffer.append(classpathFilters.get(i));
buffer.append(")");
}
String disjunction = buffer.toString();
filterPattern = Pattern.compile(disjunction);
}
}