/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file 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.jbossallxml;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javax.xml.XMLConstants;
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.logging.ServerLogger;
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.module.ResourceRoot;
import org.jboss.staxmapper.XMLElementReader;
import org.jboss.staxmapper.XMLExtendedStreamReader;
import org.jboss.staxmapper.XMLMapper;
import org.jboss.vfs.VirtualFile;
import static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
import static org.jboss.as.controller.parsing.ParseUtils.unexpectedElement;
/**
* DUP that parses jboss-all.xml and attaches the results to the deployment
*
* @author Stuart Douglas
*/
public class JBossAllXMLParsingProcessor implements DeploymentUnitProcessor {
public static final String[] DEPLOYMENT_STRUCTURE_DESCRIPTOR_LOCATIONS = {
"WEB-INF/jboss-all.xml",
"META-INF/jboss-all.xml"};
private static final XMLInputFactory INPUT_FACTORY = XMLInputFactory.newInstance();
public static final String JBOSS = "jboss";
@Override
public void deploy(final DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
final ResourceRoot root = deploymentUnit.getAttachment(Attachments.DEPLOYMENT_ROOT);
VirtualFile descriptor = null;
for (final String loc : DEPLOYMENT_STRUCTURE_DESCRIPTOR_LOCATIONS) {
final VirtualFile file = root.getRoot().getChild(loc);
if (file.exists()) {
descriptor = file;
break;
}
}
if(descriptor == null) {
return;
}
final XMLMapper mapper = XMLMapper.Factory.create();
final Map<QName, AttachmentKey<?>> namespaceAttachments = new HashMap<QName, AttachmentKey<?>>();
for(final JBossAllXMLParserDescription<?> parser : deploymentUnit.getAttachmentList(JBossAllXMLParserDescription.ATTACHMENT_KEY)) {
namespaceAttachments.put(parser.getRootElement(), parser.getAttachmentKey());
mapper.registerRootElement(parser.getRootElement(), new JBossAllXMLElementReader(parser));
}
mapper.registerRootElement(new QName(Namespace.JBOSS_1_0.getUriString(), JBOSS), Parser.INSTANCE);
mapper.registerRootElement(new QName(Namespace.NONE.getUriString(), JBOSS), Parser.INSTANCE);
final JBossAllXmlParseContext context = new JBossAllXmlParseContext(deploymentUnit);
parse(descriptor, mapper, context);
//we use this map to detect the presence of two different but functionally equivalent namespaces
final Map<AttachmentKey<?>, QName> usedNamespaces = new HashMap<AttachmentKey<?>, QName>();
for(Map.Entry<QName, Object> entry : context.getParseResults().entrySet()) {
final AttachmentKey attachmentKey = namespaceAttachments.get(entry.getKey());
if(usedNamespaces.containsKey(attachmentKey)) {
throw ServerLogger.ROOT_LOGGER.equivalentNamespacesInJBossXml(entry.getKey(), usedNamespaces.get(attachmentKey));
}
usedNamespaces.put(attachmentKey, entry.getKey());
deploymentUnit.putAttachment(attachmentKey, entry.getValue());
}
}
@Override
public void undeploy(final DeploymentUnit context) {
for(JBossAllXMLParserDescription<?> parser : context.getAttachmentList(JBossAllXMLParserDescription.ATTACHMENT_KEY)) {
context.removeAttachment(parser.getAttachmentKey());
}
}
private void parse(final VirtualFile file,final XMLMapper mapper, final JBossAllXmlParseContext context) throws DeploymentUnitProcessingException {
final FileInputStream fis;
final File realFile;
try {
realFile = file.getPhysicalFile();
fis = new FileInputStream(realFile);
} catch (IOException e) {
//should never happen as we check for existence
throw new DeploymentUnitProcessingException(e);
}
try {
parse(fis, realFile, mapper, context);
} finally {
safeClose(fis);
}
}
private void setIfSupported(final XMLInputFactory inputFactory, final String property, final Object value) {
if (inputFactory.isPropertySupported(property)) {
inputFactory.setProperty(property, value);
}
}
private void parse(final InputStream source, final File file,final XMLMapper mapper, final JBossAllXmlParseContext context) 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 {
mapper.parseDocument(context, streamReader);
} finally {
safeClose(streamReader);
}
} catch (XMLStreamException e) {
throw ServerLogger.ROOT_LOGGER.errorLoadingJBossXmlFile(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
}
}
private static class Parser implements XMLElementReader<JBossAllXmlParseContext> {
public static final Parser INSTANCE = new Parser();
@Override
public void readElement(final XMLExtendedStreamReader reader, final JBossAllXmlParseContext context)
throws XMLStreamException {
if (Element.forName(reader.getLocalName()) != Element.JBOSS) {
throw unexpectedElement(reader);
}
Namespace readerNS = Namespace.forUri(reader.getNamespaceURI());
switch (readerNS) {
case NONE:
case JBOSS_1_0: {
parseJBossElement(reader, context);
break;
}
default: {
throw unexpectedElement(reader);
}
}
}
private void parseJBossElement(final XMLExtendedStreamReader reader, final JBossAllXmlParseContext context) throws XMLStreamException {
while (reader.hasNext() && reader.nextTag() != END_ELEMENT) {
reader.handleAny(context);
}
}
}
private static enum Element {
JBOSS(JBossAllXMLParsingProcessor.JBOSS),
UNKNOWN("unknown"),
;
private final String name;
Element(final String name) {
this.name = name;
}
/**
* Get the local name of this element.
*
* @return the local name
*/
public String getLocalName() {
return name;
}
private static final Map<String, Element> MAP;
static {
final Map<String, Element> map = new HashMap<String, Element>();
for (Element element : values()) {
final String name = element.getLocalName();
if (name != null) map.put(name, element);
}
MAP = map;
}
public static Element forName(String localName) {
final Element element = MAP.get(localName);
return element == null ? UNKNOWN : element;
}
}
private static enum Namespace {
// must be first
UNKNOWN(null), NONE(null),
// predefined standard
XML_SCHEMA_INSTANCE("http://www.w3.org/2001/XMLSchema-instance"),
// domain versions, oldest to newest
JBOSS_1_0("urn:jboss:1.0"),
;
/**
* The current namespace version.
*/
public static final Namespace CURRENT = JBOSS_1_0;
private final String name;
Namespace(final String name) {
this.name = name;
}
/**
* Get the URI of this namespace.
*
* @return the URI
*/
public String getUriString() {
return name;
}
private static final Map<String, Namespace> MAP;
static {
final Map<String, Namespace> map = new HashMap<String, Namespace>();
for (Namespace namespace : values()) {
final String name = namespace.getUriString();
if (name != null)
map.put(name, namespace);
}
MAP = map;
}
public static Namespace forUri(String uri) {
// FIXME when STXM-8 is done, remove the null check
if (uri == null || XMLConstants.NULL_NS_URI.equals(uri))
return NONE;
final Namespace element = MAP.get(uri);
return element == null ? UNKNOWN : element;
}
}
}