/*
* JBoss, Home of Professional Open Source
* Copyright 2013, Red Hat, Inc. and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.richfaces.deployment;
import java.io.File;
import java.io.FilenameFilter;
import java.util.Arrays;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.faces.webapp.FacesServlet;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.jboss.shrinkwrap.descriptor.api.Descriptors;
import org.jboss.shrinkwrap.descriptor.api.facesconfig20.FacesConfigVersionType;
import org.jboss.shrinkwrap.descriptor.api.facesconfig20.WebFacesConfigDescriptor;
import org.jboss.shrinkwrap.descriptor.api.webapp30.WebAppDescriptor;
import org.jboss.shrinkwrap.resolver.api.maven.Maven;
import org.jboss.shrinkwrap.resolver.api.maven.MavenResolverSystem;
import org.richfaces.arquillian.configuration.FundamentalTestConfiguration;
import org.richfaces.arquillian.configuration.FundamentalTestConfigurationContext;
import com.google.common.base.Function;
import com.google.common.collect.Sets;
/**
* Provides base for all test deployments
*
* @author Lukas Fryc
*/
public class BaseDeployment {
private final Logger log = Logger.getLogger(BaseDeployment.class.getName());
private final FundamentalTestConfiguration configuration = FundamentalTestConfigurationContext.getProxy();
private final File cacheDir = new File("target/shrinkwrap-resolver-cache/");
private WebArchive archive;
private WebFacesConfigDescriptor facesConfig;
private WebAppDescriptor webXml;
private final Set<String> mavenDependencies = Sets.newHashSet();
private final Set<String> excludedMavenDependencies = Sets.newHashSet();
/**
* Constructs base deployment with:
*
* <ul>
* <li>archive named by test case</li>
* <li>empty faces-config.xml</li>
* <li>web.xml with default FacesServlet mapping, welcome file index.xhtml and disabled some features which are not
* necessary by default and Development stage turned on</li>
* </ul>
*
* @param testClass
*/
protected BaseDeployment(Class<?> testClass) {
this(testClass == null ? null : testClass.getSimpleName());
}
protected BaseDeployment(String archiveName) {
if (archiveName != null && !archiveName.isEmpty()) {
this.archive = ShrinkWrap.create(WebArchive.class, archiveName + ".war");
} else {
this.archive = ShrinkWrap.create(WebArchive.class);
}
this.facesConfig = Descriptors.create(WebFacesConfigDescriptor.class).version(FacesConfigVersionType._2_0);
this.webXml = Descriptors.create(WebAppDescriptor.class)
.version("3.0")
.addNamespace("xmlns:web", "http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd")
.getOrCreateWelcomeFileList()
.welcomeFile("faces/index.xhtml")
.up()
.getOrCreateContextParam()
.paramName("javax.faces.PROJECT_STAGE")
.paramValue("Development")
.up()
.getOrCreateServlet()
.servletName(FacesServlet.class.getSimpleName())
.servletClass(FacesServlet.class.getName())
.loadOnStartup(1)
.up()
.getOrCreateServletMapping()
.servletName(FacesServlet.class.getSimpleName())
.urlPattern("*.jsf")
.up()
.getOrCreateServletMapping()
.servletName(FacesServlet.class.getSimpleName())
.urlPattern("/faces/*")
.up();
// Servlet container setup
if (configuration.servletContainerSetup()) {
log.info("Adding Servlet Container extensions for JSF");
withServletContainerSetup();
}
if (configuration.isCurrentRichFacesVersion()) {
addRequiredMavenDependencies();
} else {
log.log(Level.INFO, "Running test against RichFaces version: {0}", configuration.getRichFacesVersion());
}
if (configuration.getMavenSettings() != null && !configuration.getMavenSettings().isEmpty()) {
log.log(Level.INFO, "Use Maven Settings: {0}", configuration.getMavenSettings());
}
}
/**
* Provides {@link WebArchive} available for modifications
*/
public WebArchive archive() {
return archive;
}
/**
* Returns the final testable archive - packages all the resources which were configured separately, also adds empty
* beans.xml file
*/
public WebArchive getFinalArchive() {
WebArchive finalArchive = archive
.addAsWebInfResource(new StringAsset(facesConfig.exportAsString()), "faces-config.xml")
.addAsWebInfResource(new StringAsset(webXml.exportAsString()), "web.xml")
.addAsWebInfResource(new File("src/test/resources/beans.xml"));
// add library dependencies
exportMavenDependenciesToArchive(finalArchive);
return finalArchive;
}
/**
* Allows to modify contents of faces-config.xml.
*
* Takes function which transforms original faces-config.xml and returns modified one
*/
public void facesConfig(Function<WebFacesConfigDescriptor, WebFacesConfigDescriptor> transform) {
this.facesConfig = transform.apply(this.facesConfig);
}
/**
* Allows to modify contents of web.xml.
*
* Takes function which transforms original web.xml and returns modified one
*/
public void webXml(Function<WebAppDescriptor, WebAppDescriptor> transform) {
this.webXml = transform.apply(this.webXml);
}
/**
* Resolves maven dependencies, either by {@link MavenDependencyResolver} or from file cache
*/
private void exportMavenDependenciesToArchive(WebArchive finalArchive) {
Set<File> jarFiles = Sets.newHashSet();
for (String dependency : mavenDependencies) {
try {
File dependencyDir = new File(cacheDir, dependency);
if (!dependencyDir.exists()) {
resolveMavenDependency(dependency, dependencyDir);
} else if (dependencyDirIsStale(dependencyDir)) {
cleanDependencyDir(dependencyDir);
resolveMavenDependency(dependency, dependencyDir);
}
File[] listFiles = dependencyDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".jar");
}
});
jarFiles.addAll(Arrays.asList(listFiles));
} catch (Exception e) {
throw new IllegalStateException("Can't resolve maven dependency: " + dependency, e);
}
}
File[] files = jarFiles.toArray(new File[jarFiles.size()]);
finalArchive.addAsLibraries(files);
}
private boolean dependencyDirIsStale(File dir) {
long lastModified = dir.lastModified();
long expires = lastModified + 720000;
return System.currentTimeMillis() > expires;
}
private void cleanDependencyDir(File dir) {
for (File file : dir.listFiles()) {
file.delete();
}
dir.delete();
}
/**
* Adds maven artifact as library dependency
*/
public BaseDeployment addMavenDependency(String... dependencies) {
mavenDependencies.addAll(Arrays.asList(dependencies));
return this;
}
/**
* Adds patters for Maven library dependency exclusion
*/
public BaseDeployment excludeMavenDependency(String... dependencies) {
excludedMavenDependencies.addAll(Arrays.asList(dependencies));
return this;
}
/**
* Adds dependencies which are necessary to deploy onto Servlet containers (Tomcat, Jetty)
*/
private BaseDeployment withServletContainerSetup() {
addMavenDependency(configuration.getJsfImplementation());
addMavenDependency("org.jboss.weld.servlet:weld-servlet");
addMavenDependency("javax.annotation:jsr250-api:1.0");
addMavenDependency("javax.servlet:jstl:1.2");
webXml(new Function<WebAppDescriptor, WebAppDescriptor>() {
public WebAppDescriptor apply(WebAppDescriptor webXml) {
// setup Weld Servlet
webXml
.createListener()
.listenerClass("org.jboss.weld.environment.servlet.Listener");
return webXml;
}
});
return this;
}
/**
* Adds Hibernate validator dependency, but only when using Servlet container (Tomcat, Jetty).
*/
public BaseDeployment addHibernateValidatorWhenUsingServletContainer() {
if (configuration.servletContainerSetup()) {
return addMavenDependency("org.hibernate:hibernate-validator");
}
return this;
}
/**
* <p>
* Add basic dependencies which RichFaces depends on.</p>
*
* <p>
* This dependencies would be brought transitively by org.richfaces:richfaces:jar artifact, however in snapshot builds we
* don't rely on this dependency and we use target/richfaces.jar instead.</p>
*/
private void addRequiredMavenDependencies() {
addMavenDependency("com.google.guava:guava", "net.sourceforge.cssparser:cssparser");
}
/**
* Resolves Maven dependency and writes it to the cache, so it can be reused next run
*/
private void resolveMavenDependency(String missingDependency, File dir) {
MavenResolverSystem resolver = getMavenResolver();
JavaArchive[] dependencies;
if (missingDependency.matches("^[^:]+:[^:]+:[^:]+")) {
// resolution of the artifact without a version specified
dependencies = resolver.resolve(missingDependency).withClassPathResolution(false).withTransitivity()
.as(JavaArchive.class);
} else {
// resolution of the artifact without a version specified
dependencies = resolver.loadPomFromFile("pom.xml").resolve(missingDependency)
.withClassPathResolution(false).withTransitivity().as(JavaArchive.class);
}
for (JavaArchive archive : dependencies) {
dir.mkdirs();
if (mavenDependencyExcluded(archive.getName())) {
continue;
}
final File outputFile = new File(dir, archive.getName());
archive.as(ZipExporter.class).exportTo(outputFile, true);
}
}
private MavenResolverSystem getMavenResolver() {
String mavenSettings = configuration.getMavenSettings();
if (mavenSettings == null || mavenSettings.isEmpty()) {
return Maven.resolver();
} else {
return Maven.configureResolver().fromFile(mavenSettings);
}
}
private boolean mavenDependencyExcluded(String archiveName) {
for (String exclude : excludedMavenDependencies) {
if (archiveName.contains(exclude)) {
return true;
}
}
return false;
}
}