/*******************************************************************************
* Copyright (c) 2008, 2010 VMware Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VMware Inc. - initial contribution
*******************************************************************************/
package org.eclipse.virgo.kernel.install.artifact.internal;
import java.io.IOException;
import java.io.InputStream;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.virgo.nano.deployer.api.core.DeployerLogEvents;
import org.eclipse.virgo.nano.deployer.api.core.DeploymentException;
import org.eclipse.virgo.nano.deployer.api.core.FatalDeploymentException;
import org.eclipse.virgo.kernel.install.artifact.ScopeServiceRepository;
import org.eclipse.virgo.medic.eventlog.EventLogger;
import org.osgi.framework.Version;
import org.springframework.util.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Utility class for parsing Spring config files and populating a {@link StandardScopeServiceRepository}.
*
* <strong>Concurrent Semantics</strong><br />
*
* Threadsafe.
*
*/
final class SpringConfigServiceModelScanner {
private static final String ATTRIBUTE_REF = "ref";
private static final String ATTRIBUTE_INTERFACE = "interface";
private static final String ELEMENT_VALUE = "value";
private static final String ELEMENT_INTERFACES = "interfaces";
private static final String ATTRIBUTE_VALUE = ELEMENT_VALUE;
private static final String ATTRIBUTE_KEY = "key";
private static final String ELEMENT_ENTRY = "entry";
private static final String ELEMENT_SERVICE_PROPERTIES = "service-properties";
private static final String SPRING_DM_NAMESPACE = "http://www.springframework.org/schema/osgi";
private static final String SPRING_BEANS_NAMESPACE = "http://www.springframework.org/schema/beans";
private static final String ELEMENT_SERVICE = "service";
private static final String BEAN_NAME_PROPERTY = "org.eclipse.gemini.blueprint.bean.name";
private final EventLogger eventLogger;
private final ScopeServiceRepository repository;
private final DocumentBuilder documentBuilder;
private final String scopeName;
public SpringConfigServiceModelScanner(String scopeName, ScopeServiceRepository repository, EventLogger eventLogger) {
this.scopeName = scopeName;
this.repository = repository;
this.eventLogger = eventLogger;
this.documentBuilder = createDocumentBuilder();
}
public void scanConfigFile(String bundleSymbolicName, Version bundleVersion, String configFileName, InputStream stream) throws DeploymentException {
Document doc = parseConfigFile(bundleSymbolicName, bundleVersion, configFileName, stream);
doScopeServices(doc.getDocumentElement().getChildNodes());
}
private void doScopeServices(NodeList childNodes) {
for (int x = 0; x < childNodes.getLength(); x++) {
Node node = childNodes.item(x);
if (isServiceElement(node)) {
parseServiceElement((Element) node);
}
doScopeServices(node.getChildNodes());
}
}
private void parseServiceElement(Element elem) {
String[] types = extractInterfaces(elem);
Dictionary<String, Object> properties = extractServiceProperties(elem);
this.repository.recordService(this.scopeName, types, properties);
}
/**
* Extracts all the interfaces from the supplied <code>reference</code> or <code>service</code> {@link Element}.
*
* @param e the <code>Element</code> to parse.
* @return the interfaces.
*/
private String[] extractInterfaces(Element e) {
Set<String> exportedInterfaces = new HashSet<String>();
String iface = StringUtils.trimWhitespace(e.getAttribute(ATTRIBUTE_INTERFACE));
if (StringUtils.hasText(iface)) {
exportedInterfaces.add(iface);
} else {
NodeList children = e.getChildNodes();
for (int y = 0; y < children.getLength(); y++) {
Node child = children.item(y);
if (child instanceof Element) {
if (isInterfacesElement(child)) {
Element elem = (Element) child;
NodeList intChildren = elem.getChildNodes();
for (int i = 0; i < intChildren.getLength(); i++) {
Node intChild = intChildren.item(i);
if (isValueElement(intChild)) {
exportedInterfaces.add(StringUtils.trimWhitespace(intChild.getTextContent()));
}
}
}
}
}
}
return exportedInterfaces.toArray(new String[exportedInterfaces.size()]);
}
private Dictionary<String, Object> extractServiceProperties(Element elem) {
NodeList servicePropertiesElems = elem.getElementsByTagNameNS(SPRING_DM_NAMESPACE, ELEMENT_SERVICE_PROPERTIES);
Dictionary<String, Object> p = null;
if (servicePropertiesElems.getLength() > 0) {
p = new Hashtable<String, Object>();
Node item = servicePropertiesElems.item(0);
readServiceProperties((Element) item, p);
}
p = addStandardServiceProperties(elem, p);
return p;
}
private Dictionary<String, Object> addStandardServiceProperties(Element elem, Dictionary<String, Object> p) {
// The only standard service property in the Spring DM reference manual is "bean name".
String beanName = StringUtils.trimWhitespace(elem.getAttribute(ATTRIBUTE_REF));
if (StringUtils.hasText(beanName)) {
if (p == null) {
p = new Hashtable<String, Object>();
}
p.put(BEAN_NAME_PROPERTY, beanName);
}
return p;
}
private void readServiceProperties(Element servicePropertiesElement, Dictionary<String, Object> serviceProperties) {
NodeList childNodes = servicePropertiesElement.getChildNodes();
for (int y = 0; y < childNodes.getLength(); y++) {
Node child = childNodes.item(y);
if (isEntryElement(child)) {
Element entry = (Element) child;
serviceProperties.put(entry.getAttribute(ATTRIBUTE_KEY), entry.getAttribute(ATTRIBUTE_VALUE));
}
}
}
private Document parseConfigFile(String bundleSymbolicName, Version bundleVersion, String configFileName, InputStream stream) throws DeploymentException {
try {
return this.documentBuilder.parse(new InputSource(stream));
} catch (SAXException ex) {
this.eventLogger.log(DeployerLogEvents.CONFIG_FILE_ERROR, ex, configFileName, bundleSymbolicName, bundleVersion);
throw new DeploymentException("Error parsing configuration file '" + configFileName + "'.", ex);
} catch (IOException ex) {
throw new FatalDeploymentException("Error accessing configuration file '" + configFileName + "'.", ex);
}
}
/**
* Creates a {@link DocumentBuilder}.
*
* @return the <code>DocumentBuilder</code>.
*/
private DocumentBuilder createDocumentBuilder() {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
return dbf.newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new FatalDeploymentException("Unable to create DocumentBuilder - JAXP parser configuration error.", e);
}
}
private boolean isValueElement(Node node) {
return SPRING_BEANS_NAMESPACE.equals(node.getNamespaceURI()) && ELEMENT_VALUE.equals(node.getLocalName())
&& node.getNodeType() == Node.ELEMENT_NODE;
}
private boolean isInterfacesElement(Node node) {
return SPRING_DM_NAMESPACE.equals(node.getNamespaceURI()) && ELEMENT_INTERFACES.equals(node.getLocalName())
&& node.getNodeType() == Node.ELEMENT_NODE;
}
private boolean isServiceElement(Node node) {
return SPRING_DM_NAMESPACE.equals(node.getNamespaceURI()) && ELEMENT_SERVICE.equals(node.getLocalName())
&& node.getNodeType() == Node.ELEMENT_NODE;
}
private boolean isEntryElement(Node node) {
return SPRING_BEANS_NAMESPACE.equals(node.getNamespaceURI()) && ELEMENT_ENTRY.equals(node.getLocalName())
&& node.getNodeType() == Node.ELEMENT_NODE;
}
}