/* * JBoss, Home of Professional Open Source * Copyright 2011, Red Hat, Inc. and/or its affiliates, and individual * contributors by the @authors tag. See the copyright.txt in the * distribution for a full listing of individual contributors. * * 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.jboss.solder.config.xml.model; import java.lang.annotation.Annotation; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.enterprise.inject.Produces; import javax.enterprise.inject.Stereotype; import javax.enterprise.inject.spi.BeanManager; import javax.inject.Qualifier; import javax.interceptor.InterceptorBinding; import org.jboss.solder.logging.Logger; import org.jboss.solder.config.xml.core.BeanResult; import org.jboss.solder.config.xml.core.BeanResultType; import org.jboss.solder.config.xml.core.XmlResult; import org.jboss.solder.config.xml.parser.SaxNode; import org.jboss.solder.config.xml.parser.namespace.CompositeNamespaceElementResolver; import org.jboss.solder.config.xml.parser.namespace.NamespaceElementResolver; import org.jboss.solder.config.xml.parser.namespace.RootNamespaceElementResolver; import org.jboss.solder.config.xml.util.TypeOccuranceInformation; import org.jboss.solder.config.xml.util.XmlConfigurationException; /** * Builds an XML result from sax nodes * * @author stuart */ public class ModelBuilder { static final String ROOT_NAMESPACE = "urn:java:ee"; static final String BEANS_ROOT_NAMESPACE = "http://java.sun.com/xml/ns/javaee"; static final Logger log = Logger.getLogger(ModelBuilder.class); private final XmlResult ret; public ModelBuilder(String fileUrl) { ret = new XmlResult(fileUrl); } /** * builds an XML result from a parsed xml document */ public XmlResult build(SaxNode root, BeanManager manager) { Map<String, NamespaceElementResolver> resolvers = new HashMap<String, NamespaceElementResolver>(); if (!root.getName().equals("beans")) { throw new XmlConfigurationException("Wrong root element for XML config file, expected:<beans> found:" + root.getName(), root.getDocument(), root.getLineNo()); } String namespaceUri = root.getNamespaceUri(); if (!(ROOT_NAMESPACE.equals(namespaceUri) || BEANS_ROOT_NAMESPACE.equals(namespaceUri) || namespaceUri == null || namespaceUri.isEmpty())) { throw new XmlConfigurationException("Wrong root namespace for XML config file, expected:" + ROOT_NAMESPACE + ", " + BEANS_ROOT_NAMESPACE + " or no namespace, found:" + namespaceUri, root.getDocument(), root.getLineNo()); } resolvers.put(ROOT_NAMESPACE, new RootNamespaceElementResolver()); List<SaxNode> children = root.getChildren(); for (SaxNode node : children) { try { // nodes with a null namespace are whitespace nodes etc if (node.getNamespaceUri() != null) { // ignore <alternatives> <interceptors> etc if (node.getNamespaceUri().equals(BEANS_ROOT_NAMESPACE) || node.getNamespaceUri().isEmpty()) { continue; } XmlItem rb = resolveNode(node, null, resolvers, manager); if (rb != null) { addNodeToResult(rb, manager); } } } catch (Exception e) { ret.addProblem(e.getMessage()); e.printStackTrace(); } } return ret; } @SuppressWarnings("unchecked") public void addNodeToResult(XmlItem xmlItem, BeanManager manager) { validateXmlItem(xmlItem); if (xmlItem.getType() == XmlItemType.CLASS || xmlItem.getType() == XmlItemType.ANNOTATION) { ResultType resultType = getItemType(xmlItem); // if we are configuring a bean if (resultType == ResultType.BEAN) { ClassXmlItem cxml = (ClassXmlItem) xmlItem; // get the AnnotatedType information BeanResult<?> beanResult = cxml.createBeanResult(manager); ret.addBean(beanResult); // <override> or <speciailizes> need to veto the bean if (beanResult.getBeanType() != BeanResultType.ADD) { ret.addVeto(beanResult.getType()); } } else if (resultType == ResultType.VIRTUAL_PRODUCER) { ClassXmlItem cxml = (ClassXmlItem) xmlItem; // get the AnnotatedType information BeanResult<?> beanResult = cxml.createVirtualFieldBeanResult(manager); ret.addBean(beanResult); // <override> or <speciailizes> need to veto the bean if (beanResult.getBeanType() != BeanResultType.ADD) { ret.addVeto(beanResult.getType()); } } else if (resultType == ResultType.QUALIFIER) { ret.addQualifier((Class) xmlItem.getJavaClass()); } else if (resultType == ResultType.INTERCEPTOR_BINDING) { ret.addInterceptorBinding((Class) xmlItem.getJavaClass()); } else if (resultType == ResultType.STEREOTYPE) { addStereotypeToResult(ret, xmlItem); } } } /** * resolves the appropriate java elements from the xml */ protected XmlItem resolveNode(SaxNode node, XmlItem parent, Map<String, NamespaceElementResolver> resolvers, BeanManager manager) { NamespaceElementResolver resolver = resolveNamepsace(node.getNamespaceUri(), resolvers); if (resolver == null) { log.warnf("Solder Config could not resolve XML namspace for: {}", node.getNamespaceUri()); return null; } XmlItem ret = resolver.getItemForNamespace(node, parent); if (ret == null) { throw new XmlConfigurationException("Could not resolve node " + node.getName() + " in namespace " + node.getNamespaceUri(), node.getDocument(), node.getLineNo()); } List<SaxNode> children = node.getChildren(); for (SaxNode n : children) { if (n.getNamespaceUri() != null) { XmlItem rb = resolveNode(n, ret, resolvers, manager); ret.addChild(rb); } } ret.resolveChildren(manager); return ret; } protected NamespaceElementResolver resolveNamepsace(String namespaceURI, Map<String, NamespaceElementResolver> resolvers) { if (resolvers.containsKey(namespaceURI)) { return resolvers.get(namespaceURI); } if (!namespaceURI.startsWith("urn:java:")) { return null; } String ns = namespaceURI.replaceFirst("urn:java:", ""); CompositeNamespaceElementResolver res = new CompositeNamespaceElementResolver(ns.split(":")); resolvers.put(namespaceURI, res); return res; } /** * Determines the type of an element by examining its child nodes */ protected ResultType getItemType(XmlItem item) { ResultType ret = null; for (AnnotationXmlItem it : item.getChildrenOfType(AnnotationXmlItem.class)) { if (it.getJavaClass() == InterceptorBinding.class) { if (ret != null) { throw new XmlConfigurationException("Element cannot be both an INTERCEPTOR_BINDING and a " + ret.toString(), item.getDocument(), item.getLineno()); } else { ret = ResultType.INTERCEPTOR_BINDING; } } else if (it.getJavaClass() == Qualifier.class) { if (ret != null) { throw new XmlConfigurationException("Element cannot be both an QUALIFIER and a " + ret.toString(), item.getDocument(), item.getLineno()); } else { ret = ResultType.QUALIFIER; } } else if (it.getJavaClass() == Produces.class) { if (ret != null) { throw new XmlConfigurationException("Element cannot be both an virtual producer field and a " + ret.toString(), item.getDocument(), item.getLineno()); } else { ret = ResultType.VIRTUAL_PRODUCER; } } else if (it.getJavaClass() == Stereotype.class) { if (ret != null) { throw new XmlConfigurationException("Element cannot be both an STEREOTYPE and a " + ret.toString(), item.getDocument(), item.getLineno()); } else { ret = ResultType.STEREOTYPE; } } } if (ret == null) { ret = ResultType.BEAN; } return ret; } @SuppressWarnings("unchecked") void addStereotypeToResult(XmlResult ret, XmlItem rb) { Annotation[] values = new Annotation[rb.getChildren().size()]; int count = 0; for (XmlItem item : rb.getChildren()) { if (item.getType() == XmlItemType.ANNOTATION) { Annotation a = AnnotationUtils.createAnnotation((AnnotationXmlItem) item); values[count] = a; } else { throw new XmlConfigurationException("Setereotype " + rb.getJavaClass() + " has an item that does not represent an annotation in its XML configurations", rb.getDocument(), rb.getLineno()); } count++; } ret.addStereotype((Class) rb.getJavaClass(), values); } public void validateXmlItem(XmlItem item) { Set<TypeOccuranceInformation> allowed = item.getAllowedItem(); Map<XmlItemType, Integer> counts = new HashMap<XmlItemType, Integer>(); for (XmlItem i : item.getChildren()) { boolean found = false; for (TypeOccuranceInformation type : allowed) { if (type.getType() == i.getType()) { found = true; if (counts.containsKey(i.getType())) { counts.put(i.getType(), counts.get(i.getType()) + 1); } else { counts.put(i.getType(), 1); } } } if (!found) { throw new XmlConfigurationException("Item " + item.getType() + " is not allowed to contain " + i.getType(), item.getDocument(), item.getLineno()); } validateXmlItem(i); } for (TypeOccuranceInformation type : allowed) { Integer count = counts.get(type.getType()); if (type.getMaxOccurances() != null) { if (count != null) { if (count > type.getMaxOccurances()) { throw new XmlConfigurationException("Item " + item.getType() + " has " + count + " children of type " + type.getType() + " when it should have at most " + type.getMaxOccurances(), item.getDocument(), item.getLineno()); } } } if (type.getMinOccurances() != null) { if (count == null || count < type.getMinOccurances()) { throw new XmlConfigurationException("Item " + item.getType() + " has " + count + " children of type " + type.getType() + " when it should have at least " + type.getMinOccurances(), item.getDocument(), item.getLineno()); } } } } }