/*
* JBoss, Home of Professional Open Source
* Copyright 2010, Red Hat Inc., and individual contributors as indicated
* 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.jboss.as.server.deployment.module.descriptor;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import org.jboss.as.server.DeployerChainAddHandler;
import org.jboss.as.server.logging.ServerLogger;
import org.jboss.as.server.ServerService;
import org.jboss.as.server.deployment.AttachmentKey;
import org.jboss.as.server.deployment.Attachments;
import org.jboss.as.server.deployment.DeploymentPhaseContext;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
import org.jboss.as.server.deployment.DeploymentUnitProcessor;
import org.jboss.as.server.deployment.DeploymentUtils;
import org.jboss.as.server.deployment.Phase;
import org.jboss.as.server.deployment.SubDeploymentMarker;
import org.jboss.as.server.deployment.annotation.ResourceRootIndexer;
import org.jboss.as.server.deployment.jbossallxml.JBossAllXmlParserRegisteringProcessor;
import org.jboss.as.server.deployment.module.AdditionalModuleSpecification;
import org.jboss.as.server.deployment.module.ModuleRootMarker;
import org.jboss.as.server.deployment.module.ModuleSpecification;
import org.jboss.as.server.deployment.module.ResourceRoot;
import org.jboss.as.server.moduleservice.ServiceModuleLoader;
import org.jboss.modules.ModuleIdentifier;
import org.jboss.modules.ModuleLoader;
import org.jboss.staxmapper.XMLMapper;
import org.jboss.vfs.VirtualFile;
/**
* Parses <code>jboss-deployment-structure.xml</code>, and merges the result with the deployment.
* <p/>
* <code>jboss-deployment-structure.xml</code> is only parsed for top level deployments. It allows configuration of the following for
* deployments and sub deployments:
* <ul>
* <li>Additional dependencies</li>
* <li>Additional resource roots</li>
* <li>{@link java.lang.instrument.ClassFileTransformer}s that will be applied at classloading</li>
* <li>Child first behaviour</li>
* </ul>
* <p/>
* It also allows for the use to add additional modules, using a syntax similar to that used in module xml files.
*
* @author Stuart Douglas
* @author Marius Bogoevici
*/
public class DeploymentStructureDescriptorParser implements DeploymentUnitProcessor {
public static final String[] DEPLOYMENT_STRUCTURE_DESCRIPTOR_LOCATIONS = {
"META-INF/jboss-deployment-structure.xml",
"WEB-INF/jboss-deployment-structure.xml"};
private static final AttachmentKey<ParseResult> RESULT_ATTACHMENT_KEY = AttachmentKey.create(ParseResult.class);
public static void registerJBossXMLParsers() {
DeployerChainAddHandler.addDeploymentProcessor(ServerService.SERVER_NAME, Phase.STRUCTURE, Phase.STRUCTURE_REGISTER_JBOSS_ALL_STRUCTURE_1_0, new JBossAllXmlParserRegisteringProcessor<ParseResult>(ROOT_1_0, RESULT_ATTACHMENT_KEY, JBossDeploymentStructureParser10.JBOSS_ALL_XML_PARSER));
DeployerChainAddHandler.addDeploymentProcessor(ServerService.SERVER_NAME, Phase.STRUCTURE, Phase.STRUCTURE_REGISTER_JBOSS_ALL_STRUCTURE_1_1, new JBossAllXmlParserRegisteringProcessor<ParseResult>(ROOT_1_1, RESULT_ATTACHMENT_KEY, JBossDeploymentStructureParser11.JBOSS_ALL_XML_PARSER));
DeployerChainAddHandler.addDeploymentProcessor(ServerService.SERVER_NAME, Phase.STRUCTURE, Phase.STRUCTURE_REGISTER_JBOSS_ALL_STRUCTURE_1_2, new JBossAllXmlParserRegisteringProcessor<ParseResult>(ROOT_1_2, RESULT_ATTACHMENT_KEY, JBossDeploymentStructureParser12.JBOSS_ALL_XML_PARSER));
DeployerChainAddHandler.addDeploymentProcessor(ServerService.SERVER_NAME, Phase.STRUCTURE, Phase.STRUCTURE_REGISTER_JBOSS_ALL_STRUCTURE_1_3, new JBossAllXmlParserRegisteringProcessor<ParseResult>(ROOT_1_3, RESULT_ATTACHMENT_KEY, JBossDeploymentStructureParser13.JBOSS_ALL_XML_PARSER));
}
private static final QName ROOT_1_0 = new QName(JBossDeploymentStructureParser10.NAMESPACE_1_0, "jboss-deployment-structure");
private static final QName ROOT_1_1 = new QName(JBossDeploymentStructureParser11.NAMESPACE_1_1, "jboss-deployment-structure");
private static final QName ROOT_1_2 = new QName(JBossDeploymentStructureParser12.NAMESPACE_1_2, "jboss-deployment-structure");
private static final QName ROOT_1_3 = new QName(JBossDeploymentStructureParser13.NAMESPACE_1_3, "jboss-deployment-structure");
private static final QName ROOT_NO_NAMESPACE = new QName("jboss-deployment-structure");
private static final XMLInputFactory INPUT_FACTORY = XMLInputFactory.newInstance();
private final AttachmentKey<ModuleStructureSpec> SUB_DEPLOYMENT_STRUCTURE = AttachmentKey.create(ModuleStructureSpec.class);
private final XMLMapper mapper;
public DeploymentStructureDescriptorParser() {
mapper = XMLMapper.Factory.create();
mapper.registerRootElement(ROOT_1_0, JBossDeploymentStructureParser10.INSTANCE);
mapper.registerRootElement(ROOT_1_1, JBossDeploymentStructureParser11.INSTANCE);
mapper.registerRootElement(ROOT_1_2, JBossDeploymentStructureParser12.INSTANCE);
mapper.registerRootElement(ROOT_1_3, JBossDeploymentStructureParser13.INSTANCE);
mapper.registerRootElement(ROOT_NO_NAMESPACE, JBossDeploymentStructureParser13.INSTANCE);
}
@Override
public void deploy(final DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
final ResourceRoot deploymentRoot = deploymentUnit.getAttachment(Attachments.DEPLOYMENT_ROOT);
final ServiceModuleLoader moduleLoader = deploymentUnit.getAttachment(Attachments.SERVICE_MODULE_LOADER);
if (deploymentUnit.getParent() != null) {
//if the parent has already attached parsed data for this sub deployment we need to process it
if (deploymentRoot.hasAttachment(SUB_DEPLOYMENT_STRUCTURE)) {
final ModuleSpecification subModuleSpec = deploymentUnit.getAttachment(Attachments.MODULE_SPECIFICATION);
handleDeployment(phaseContext, deploymentUnit, subModuleSpec, deploymentRoot.getAttachment(SUB_DEPLOYMENT_STRUCTURE));
}
}
VirtualFile deploymentFile = null;
for (final String loc : DEPLOYMENT_STRUCTURE_DESCRIPTOR_LOCATIONS) {
final VirtualFile file = deploymentRoot.getRoot().getChild(loc);
if (file.exists()) {
deploymentFile = file;
break;
}
}
ParseResult result = deploymentUnit.getAttachment(RESULT_ATTACHMENT_KEY);
if (deploymentFile == null && result == null) {
return;
}
if (deploymentUnit.getParent() != null) {
if(deploymentFile != null) {
ServerLogger.DEPLOYMENT_LOGGER.jbossDeploymentStructureIgnored(deploymentFile.getPathName());
}
if(result != null) {
ServerLogger.DEPLOYMENT_LOGGER.jbossDeploymentStructureNamespaceIgnored(deploymentUnit.getName());
}
return;
}
try {
if(deploymentFile != null) {
result = parse(deploymentFile.getPhysicalFile(), deploymentUnit, moduleLoader);
}
final ModuleSpecification moduleSpec = deploymentUnit.getAttachment(Attachments.MODULE_SPECIFICATION);
if (result.getEarSubDeploymentsIsolated() != null) {
// set the ear subdeployment isolation value overridden via the jboss-deployment-structure.xml
moduleSpec.setSubDeploymentModulesIsolated(result.getEarSubDeploymentsIsolated());
}
if(result.getEarExclusionsCascadedToSubDeployments() != null) {
// set the ear cascade exclusions to sub-deployments flag as configured in jboss-deployment-structure.xml
moduleSpec.setExclusionsCascadedToSubDeployments(result.getEarExclusionsCascadedToSubDeployments());
}
// handle the the root deployment
final ModuleStructureSpec rootDeploymentSpecification = result.getRootDeploymentSpecification();
if (rootDeploymentSpecification != null) {
handleDeployment(phaseContext, deploymentUnit, moduleSpec, rootDeploymentSpecification);
}
// handle sub deployments
final Map<String, ResourceRoot> subDeploymentMap = new HashMap<String, ResourceRoot>();
final List<ResourceRoot> resourceRoots = deploymentUnit.getAttachmentList(Attachments.RESOURCE_ROOTS);
for (final ResourceRoot root : resourceRoots) {
if (SubDeploymentMarker.isSubDeployment(root)) {
subDeploymentMap.put(root.getRoot().getPathNameRelativeTo(deploymentRoot.getRoot()), root);
}
}
for (final Entry<String, ModuleStructureSpec> entry : result.getSubDeploymentSpecifications().entrySet()) {
final String path = entry.getKey();
final ModuleStructureSpec spec = entry.getValue();
if (!subDeploymentMap.containsKey(path)) {
throw subDeploymentNotFound(path, subDeploymentMap.keySet());
}
final ResourceRoot subDeployment = subDeploymentMap.get(path);
subDeployment.putAttachment(SUB_DEPLOYMENT_STRUCTURE, spec);
// cascade the exclusions if configured
if(moduleSpec.isExclusionsCascadedToSubDeployments() && rootDeploymentSpecification != null) {
for(ModuleIdentifier exclusion : rootDeploymentSpecification.getExclusions()) {
spec.getExclusions().add(exclusion);
}
}
}
// handle additional modules
for (final ModuleStructureSpec additionalModule : result.getAdditionalModules()) {
for (final ModuleIdentifier identifier : additionalModule.getAnnotationModules()) {
//additional modules don't support annotation imports
ServerLogger.DEPLOYMENT_LOGGER.annotationImportIgnored(identifier, additionalModule.getModuleIdentifier());
}
//log a warning if the resource root is wrong
final List<ResourceRoot> additionalModuleResourceRoots = new ArrayList<ResourceRoot>(additionalModule.getResourceRoots());
final ListIterator<ResourceRoot> itr = additionalModuleResourceRoots.listIterator();
while (itr.hasNext()) {
final ResourceRoot resourceRoot = itr.next();
if(!resourceRoot.getRoot().exists()) {
ServerLogger.DEPLOYMENT_LOGGER.additionalResourceRootDoesNotExist(resourceRoot.getRoot().getPathName());
itr.remove();
}
}
final AdditionalModuleSpecification additional = new AdditionalModuleSpecification(additionalModule.getModuleIdentifier(), additionalModuleResourceRoots);
additional.addAliases(additionalModule.getAliases());
additional.addSystemDependencies(additionalModule.getModuleDependencies());
deploymentUnit.addToAttachmentList(Attachments.ADDITIONAL_MODULES, additional);
for (final ResourceRoot root : additionalModuleResourceRoots) {
ResourceRootIndexer.indexResourceRoot(root);
}
}
} catch (IOException e) {
throw new DeploymentUnitProcessingException(e);
}
}
private void handleDeployment(final DeploymentPhaseContext phaseContext, final DeploymentUnit deploymentUnit, final ModuleSpecification moduleSpec, final ModuleStructureSpec rootDeploymentSpecification) throws DeploymentUnitProcessingException {
final Map<VirtualFile, ResourceRoot> resourceRoots = resourceRoots(deploymentUnit);
moduleSpec.addUserDependencies(rootDeploymentSpecification.getModuleDependencies());
moduleSpec.addExclusions(rootDeploymentSpecification.getExclusions());
moduleSpec.addAliases(rootDeploymentSpecification.getAliases());
moduleSpec.addModuleSystemDependencies(rootDeploymentSpecification.getSystemDependencies());
for (final ResourceRoot additionalResourceRoot : rootDeploymentSpecification.getResourceRoots()) {
final ResourceRoot existingRoot = resourceRoots.get(additionalResourceRoot.getRoot());
if (existingRoot != null) {
//we already have to the resource root
//so now we want to merge it
existingRoot.merge(additionalResourceRoot);
} else if (!additionalResourceRoot.getRoot().exists()) {
ServerLogger.DEPLOYMENT_LOGGER.additionalResourceRootDoesNotExist(additionalResourceRoot.getRoot().getPathName());
} else {
deploymentUnit.addToAttachmentList(Attachments.RESOURCE_ROOTS, additionalResourceRoot);
//compute the annotation index for the root
ResourceRootIndexer.indexResourceRoot(additionalResourceRoot);
ModuleRootMarker.mark(additionalResourceRoot);
}
}
for (final String classFileTransformer : rootDeploymentSpecification.getClassFileTransformers()) {
moduleSpec.addClassFileTransformer(classFileTransformer);
}
//handle annotations
for (final ModuleIdentifier dependency : rootDeploymentSpecification.getAnnotationModules()) {
deploymentUnit.addToAttachmentList(Attachments.ADDITIONAL_ANNOTATION_INDEXES, dependency);
if(dependency.getName().startsWith(ServiceModuleLoader.MODULE_PREFIX)) {
phaseContext.addToAttachmentList(Attachments.NEXT_PHASE_DEPS, ServiceModuleLoader.moduleServiceName(dependency));
}
}
moduleSpec.setLocalLast(rootDeploymentSpecification.isLocalLast());
if(rootDeploymentSpecification.getExcludedSubsystems() != null) {
deploymentUnit.putAttachment(Attachments.EXCLUDED_SUBSYSTEMS, rootDeploymentSpecification.getExcludedSubsystems());
}
}
private Map<VirtualFile, ResourceRoot> resourceRoots(final DeploymentUnit deploymentUnit) {
final Map<VirtualFile, ResourceRoot> resourceRoots = new HashMap<VirtualFile, ResourceRoot>();
for (final ResourceRoot root : DeploymentUtils.allResourceRoots(deploymentUnit)) {
resourceRoots.put(root.getRoot(), root);
}
return resourceRoots;
}
private DeploymentUnitProcessingException subDeploymentNotFound(final String path, final Collection<String> subDeployments) {
final StringBuilder builder = new StringBuilder();
boolean first = true;
for (final String dep : subDeployments) {
if (!first) {
builder.append(", ");
} else {
first = false;
}
builder.append(dep);
}
return ServerLogger.ROOT_LOGGER.subdeploymentNotFound(path, builder);
}
@Override
public void undeploy(final DeploymentUnit context) {
//any processors from these subsystems that run before this DUP
//will have run, so we need to clear this to make sure their undeploy
//will be called
context.removeAttachment(Attachments.EXCLUDED_SUBSYSTEMS);
}
private ParseResult parse(final File file, final DeploymentUnit deploymentUnit, final ModuleLoader moduleLoader) throws DeploymentUnitProcessingException {
final FileInputStream fis;
try {
fis = new FileInputStream(file);
} catch (FileNotFoundException e) {
throw ServerLogger.ROOT_LOGGER.deploymentStructureFileNotFound(file);
}
try {
return parse(fis, file, deploymentUnit, moduleLoader);
} finally {
safeClose(fis);
}
}
private void setIfSupported(final XMLInputFactory inputFactory, final String property, final Object value) {
if (inputFactory.isPropertySupported(property)) {
inputFactory.setProperty(property, value);
}
}
private ParseResult parse(final InputStream source, final File file, final DeploymentUnit deploymentUnit, final ModuleLoader moduleLoader)
throws DeploymentUnitProcessingException {
try {
final XMLInputFactory inputFactory = INPUT_FACTORY;
setIfSupported(inputFactory, XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
setIfSupported(inputFactory, XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
final XMLStreamReader streamReader = inputFactory.createXMLStreamReader(source);
try {
final ParseResult result = new ParseResult(moduleLoader, deploymentUnit);
mapper.parseDocument(result, streamReader);
return result;
} finally {
safeClose(streamReader);
}
} catch (XMLStreamException e) {
throw ServerLogger.ROOT_LOGGER.errorLoadingDeploymentStructureFile(file.getPath(), e);
}
}
private static void safeClose(final Closeable closeable) {
if (closeable != null)
try {
closeable.close();
} catch (IOException e) {
// ignore
}
}
private static void safeClose(final XMLStreamReader streamReader) {
if (streamReader != null)
try {
streamReader.close();
} catch (XMLStreamException e) {
// ignore
}
}
}