/*
* Copyright 2011 the original author or authors.
*
* 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.gradle.plugins.ear.descriptor.internal;
import groovy.lang.Closure;
import groovy.util.Node;
import groovy.util.XmlParser;
import groovy.xml.QName;
import org.apache.commons.io.IOUtils;
import org.gradle.api.Action;
import org.gradle.api.UncheckedIOException;
import org.gradle.api.XmlProvider;
import org.gradle.api.internal.DomNode;
import org.gradle.internal.Cast;
import org.gradle.internal.UncheckedException;
import org.gradle.internal.file.PathToFileResolver;
import org.gradle.internal.reflect.Instantiator;
import org.gradle.internal.xml.XmlTransformer;
import org.gradle.plugins.ear.descriptor.DeploymentDescriptor;
import org.gradle.plugins.ear.descriptor.EarModule;
import org.gradle.plugins.ear.descriptor.EarSecurityRole;
import org.gradle.plugins.ear.descriptor.EarWebModule;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import javax.inject.Inject;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class DefaultDeploymentDescriptor implements DeploymentDescriptor {
private static final String ACCESS_EXTERNAL_DTD = "http://javax.xml.XMLConstants/property/accessExternalDTD";
private static final String ALLOW_ANY_EXTERNAL_DTD = "all";
private final XmlTransformer transformer = new XmlTransformer();
private final PathToFileResolver fileResolver;
private final Instantiator instantiator;
private String fileName = "application.xml";
private String version = "6";
private String applicationName;
private Boolean initializeInOrder = Boolean.FALSE;
private String description;
private String displayName;
private String libraryDirectory;
private Set<EarModule> modules = new LinkedHashSet<EarModule>();
private Set<EarSecurityRole> securityRoles = new LinkedHashSet<EarSecurityRole>();
private Map<String, String> moduleTypeMappings = new LinkedHashMap<String, String>();
@Inject
public DefaultDeploymentDescriptor(PathToFileResolver fileResolver, Instantiator instantiator) {
this.fileResolver = fileResolver;
this.instantiator = instantiator;
}
@Override
public String getFileName() {
return fileName;
}
@Override
public void setFileName(String fileName) {
this.fileName = fileName;
readFrom(new File("META-INF", fileName));
}
@Override
public String getVersion() {
return version;
}
@Override
public void setVersion(String version) {
this.version = version;
}
@Override
public String getApplicationName() {
return applicationName;
}
@Override
public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}
@Override
public Boolean getInitializeInOrder() {
return initializeInOrder;
}
@Override
public void setInitializeInOrder(Boolean initializeInOrder) {
this.initializeInOrder = initializeInOrder;
}
@Override
public String getDescription() {
return description;
}
@Override
public void setDescription(String description) {
this.description = description;
}
@Override
public String getDisplayName() {
return displayName;
}
@Override
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
@Override
public String getLibraryDirectory() {
return libraryDirectory;
}
@Override
public void setLibraryDirectory(String libraryDirectory) {
this.libraryDirectory = libraryDirectory;
}
@Override
public Set<EarModule> getModules() {
return modules;
}
@Override
public void setModules(Set<EarModule> modules) {
this.modules = modules;
}
@Override
public Set<EarSecurityRole> getSecurityRoles() {
return securityRoles;
}
@Override
public void setSecurityRoles(Set<EarSecurityRole> securityRoles) {
this.securityRoles = securityRoles;
}
@Override
public Map<String, String> getModuleTypeMappings() {
return moduleTypeMappings;
}
@Override
public void setModuleTypeMappings(Map<String, String> moduleTypeMappings) {
this.moduleTypeMappings = moduleTypeMappings;
}
@Override
public DefaultDeploymentDescriptor module(EarModule module, String type) {
modules.add(module);
moduleTypeMappings.put(module.getPath(), type);
return this;
}
@Override
public DefaultDeploymentDescriptor module(String path, String type) {
return module(new DefaultEarModule(path), type);
}
@Override
public DefaultDeploymentDescriptor webModule(String path, String contextRoot) {
modules.add(new DefaultEarWebModule(path, contextRoot));
moduleTypeMappings.put(path, "web");
return this;
}
@Override
public DefaultDeploymentDescriptor securityRole(EarSecurityRole role) {
securityRoles.add(role);
return this;
}
@Override
public DeploymentDescriptor securityRole(String role) {
securityRoles.add(new DefaultEarSecurityRole(role));
return this;
}
@Override
public DeploymentDescriptor securityRole(Action<? super EarSecurityRole> action) {
EarSecurityRole role = instantiator.newInstance(DefaultEarSecurityRole.class);
action.execute(role);
securityRoles.add(role);
return this;
}
@Override
public DeploymentDescriptor withXml(Closure closure) {
transformer.addAction(closure);
return this;
}
@Override
public DeploymentDescriptor withXml(Action<? super XmlProvider> action) {
transformer.addAction(action);
return this;
}
@Override
public boolean readFrom(Object path) {
if (fileResolver == null) {
return false;
}
File descriptorFile = fileResolver.resolve(path);
if (descriptorFile == null || !descriptorFile.exists()) {
return false;
}
try {
FileReader reader = new FileReader(descriptorFile);
readFrom(reader);
return true;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private static XmlParser createParser() {
try {
XmlParser parser = new XmlParser(false, true, true);
try {
// If not set for >= JAXP 1.5 / Java8 won't allow referencing DTDs, e.g.
// using http URLs, because Groovy's XmlParser requests FEATURE_SECURE_PROCESSING
parser.setProperty(ACCESS_EXTERNAL_DTD, ALLOW_ANY_EXTERNAL_DTD);
} catch (SAXNotRecognizedException ignore) {
// property requires >= JAXP 1.5 / Java8
}
return parser;
} catch (Exception ex) {
throw new UncheckedException(ex);
}
}
@Override
public DeploymentDescriptor readFrom(Reader reader) {
try {
Node appNode = createParser().parse(reader);
version = (String) appNode.attribute("version");
for (final Node child : Cast.<List<Node>>uncheckedCast(appNode.children())) {
String childLocalName = localNameOf(child);
if (childLocalName.equals("application-name")) {
applicationName = child.text();
} else if (childLocalName.equals("initialize-in-order")) {
initializeInOrder = Boolean.valueOf(child.text());
} else if (childLocalName.equals("description")) {
description = child.text();
} else if (childLocalName.equals("display-name")) {
displayName = child.text();
} else if (childLocalName.equals("library-directory")) {
libraryDirectory = child.text();
} else if (childLocalName.equals("module")) {
EarModule module = null;
for (Node moduleNode : Cast.<List<Node>>uncheckedCast(child.children())) {
String moduleNodeLocalName = localNameOf(moduleNode);
if (moduleNodeLocalName.equals("web")) {
String webUri = childNodeText(moduleNode, "web-uri");
String contextRoot = childNodeText(moduleNode, "context-root");
module = new DefaultEarWebModule(webUri, contextRoot);
modules.add(module);
moduleTypeMappings.put(module.getPath(), "web");
} else if (moduleNodeLocalName.equals("alt-dd")) {
assert module != null;
module.setAltDeployDescriptor(moduleNode.text());
} else {
module = new DefaultEarModule(moduleNode.text());
modules.add(module);
moduleTypeMappings.put(module.getPath(), moduleNodeLocalName);
}
}
} else if (childLocalName.equals("security-role")) {
String roleName = childNodeText(child, "role-name");
String description = childNodeText(child, "description");
securityRoles.add(new DefaultEarSecurityRole(roleName, description));
} else {
withXml(new Action<XmlProvider>() {
@Override
public void execute(XmlProvider xmlProvider) {
xmlProvider.asNode().append(child);
}
});
}
}
} catch (IOException ex) {
throw new UncheckedIOException(ex);
} catch (SAXException ex) {
throw new UncheckedException(ex);
} finally {
IOUtils.closeQuietly(reader);
}
return this;
}
private static String childNodeText(Node root, String name) {
for (Node child : Cast.<List<Node>>uncheckedCast(root.children())) {
if (localNameOf(child).equals(name)) {
return child.text();
}
}
return null;
}
private static String localNameOf(Node node) {
return node.name() instanceof QName ? ((QName) node.name()).getLocalPart() : String.valueOf(node.name());
}
@Override
public DefaultDeploymentDescriptor writeTo(Object path) {
transformer.transform(toXmlNode(), fileResolver.resolve(path));
return this;
}
@Override
public DefaultDeploymentDescriptor writeTo(Writer writer) {
transformer.transform(toXmlNode(), writer);
return this;
}
private DomNode toXmlNode() {
DomNode root = new DomNode(nodeNameFor("application"));
root.attributes().put("version", version);
if (!"1.3".equals(version)) {
root.attributes().put("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
}
if ("1.3".equals(version)) {
root.setPublicId("-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN");
root.setSystemId("http://java.sun.com/dtd/application_1_3.dtd");
} else if ("1.4".equals(version)) {
root.attributes().put("xsi:schemaLocation", "http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/application_1_4.xsd");
} else if ("5".equals(version) || "6".equals(version)) {
root.attributes().put("xsi:schemaLocation", "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_" + version + ".xsd");
} else if ("7".equals(version)) {
root.attributes().put("xsi:schemaLocation", "http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/application_" + version + ".xsd");
}
if (applicationName != null) {
new Node(root, nodeNameFor("application-name"), applicationName);
}
if (description != null) {
new Node(root, nodeNameFor("description"), description);
}
if (displayName != null) {
new Node(root, nodeNameFor("display-name"), displayName);
}
if (initializeInOrder != null && initializeInOrder) {
new Node(root, nodeNameFor("initialize-in-order"), initializeInOrder);
}
for (EarModule module : modules) {
Node moduleNode = new Node(root, nodeNameFor("module"));
module.toXmlNode(moduleNode, moduleNameFor(module));
}
if (securityRoles != null) {
for (EarSecurityRole role : securityRoles) {
Node roleNode = new Node(root, nodeNameFor("security-role"));
if (role.getDescription() != null) {
new Node(roleNode, nodeNameFor("description"), role.getDescription());
}
new Node(roleNode, nodeNameFor("role-name"), role.getRoleName());
}
}
if (libraryDirectory != null) {
new Node(root, nodeNameFor("library-directory"), libraryDirectory);
}
return root;
}
private Object moduleNameFor(EarModule module) {
String name = moduleTypeMappings.get(module.getPath());
if (name == null) {
if (module instanceof EarWebModule) {
name = "web";
} else {
// assume EJB is the most common kind of EAR deployment
name = "ejb";
}
}
return nodeNameFor(name);
}
private Object nodeNameFor(String name) {
if ("1.3".equals(version)) {
return name;
} else if ("1.4".equals(version)) {
return new QName("http://java.sun.com/xml/ns/j2ee", name);
} else if ("5".equals(version) || "6".equals(version)) {
return new QName("http://java.sun.com/xml/ns/javaee", name);
} else if ("7".equals(version)) {
return new QName("http://xmlns.jcp.org/xml/ns/javaee", name);
} else {
return new QName(name);
}
}
// For tests
XmlTransformer getTransformer() {
return transformer;
}
}