/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.felix.scr.impl.xml;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.felix.scr.impl.helper.Logger;
import org.apache.felix.scr.impl.metadata.ComponentMetadata;
import org.apache.felix.scr.impl.metadata.DSVersion;
import org.apache.felix.scr.impl.metadata.PropertyMetadata;
import org.apache.felix.scr.impl.metadata.ReferenceMetadata;
import org.apache.felix.scr.impl.metadata.ServiceMetadata;
import org.apache.felix.scr.impl.parser.KXml2SAXHandler;
import org.apache.felix.scr.impl.parser.KXml2SAXParser.Attributes;
import org.apache.felix.scr.impl.parser.ParseException;
import org.osgi.framework.Bundle;
import org.osgi.service.log.LogService;
/**
* XML Parser for the component XML
*/
public class XmlHandler implements KXml2SAXHandler
{
// the bundle containing the XML resource being parsed
private final Bundle m_bundle;
// logger for any messages
private final Logger m_logger;
private final boolean m_globalObsoleteFactoryComponentFactory;
private final boolean m_globalDelayedKeepInstances;
// A reference to the current component
private ComponentMetadata m_currentComponent;
// The current service
private ServiceMetadata m_currentService;
// A list of component descriptors contained in the file
private List<ComponentMetadata> m_components = new ArrayList<ComponentMetadata>();
// PropertyMetaData whose value attribute is missing, hence has element data
private PropertyMetadata m_pendingProperty;
// PropertyMetaData whose value attribute is missing, hence has element data
private PropertyMetadata m_pendingFactoryProperty;
/** Flag for detecting the first element. */
protected boolean firstElement = true;
/** Override namespace. */
protected String overrideNamespace;
/** Flag for elements inside a component element */
protected boolean isComponent = false;
// creates an instance with the bundle owning the component descriptor
// file parsed by this instance
public XmlHandler( Bundle bundle, Logger logger, boolean globalObsoleteFactoryComponentFactory, boolean globalDelayedKeepInstances )
{
m_bundle = bundle;
m_logger = logger;
m_globalObsoleteFactoryComponentFactory = globalObsoleteFactoryComponentFactory;
m_globalDelayedKeepInstances = globalDelayedKeepInstances;
}
/**
* Called to retrieve the service descriptors
*
* @return A list of service descriptors
*/
public List<ComponentMetadata> getComponentMetadataList()
{
return m_components;
}
/**
* Method called when a tag opens
*
* @param uri
* @param localName
* @param attributes
* @exception ParseException
**/
@Override
public void startElement( String uri, String localName, Attributes attributes ) throws ParseException
{
// according to the spec, the elements should have the namespace,
// except when the root element is the "component" element
// So we check this for the first element, we receive.
if ( firstElement )
{
firstElement = false;
if ( localName.equals( XmlConstants.EL_COMPONENT ) && XmlConstants.NAMESPACE_URI_EMPTY.equals( uri ) )
{
overrideNamespace = XmlConstants.NAMESPACE_URI;
}
}
if ( overrideNamespace != null && XmlConstants.NAMESPACE_URI_EMPTY.equals( uri ) )
{
uri = overrideNamespace;
}
// FELIX-695: however the spec also states that the inner elements
// of a component are unqualified, so they don't have
// the namespace - we allow both: with or without namespace!
if ( this.isComponent && XmlConstants.NAMESPACE_URI_EMPTY.equals(uri) )
{
uri = XmlConstants.NAMESPACE_URI;
}
// get the namespace code for the namespace uri
DSVersion namespaceCode = XmlConstants.NAMESPACE_CODE_MAP.get( uri );
// from now on uri points to the namespace
if ( namespaceCode != null )
{
try
{
// 112.4.3 Component Element
if ( localName.equals( XmlConstants.EL_COMPONENT ) )
{
this.isComponent = true;
// Create a new ComponentMetadata
m_currentComponent = new ComponentMetadata( namespaceCode );
// name attribute is optional (since DS 1.1)
if ( attributes.getAttribute( XmlConstants.ATTR_NAME ) != null )
{
m_currentComponent.setName( attributes.getAttribute( XmlConstants.ATTR_NAME ) );
}
// enabled attribute is optional
if ( attributes.getAttribute( "enabled" ) != null )
{
m_currentComponent.setEnabled( attributes.getAttribute( "enabled" ).equals( "true" ) );
}
// immediate attribute is optional
if ( attributes.getAttribute( "immediate" ) != null )
{
m_currentComponent.setImmediate( attributes.getAttribute( "immediate" ).equals( "true" ) );
}
// factory attribute is optional
if ( attributes.getAttribute( "factory" ) != null )
{
m_currentComponent.setFactoryIdentifier( attributes.getAttribute( "factory" ) );
}
// configuration-policy is optional (since DS 1.1)
if ( attributes.getAttribute( "configuration-policy" ) != null )
{
m_currentComponent.setConfigurationPolicy( attributes.getAttribute( "configuration-policy" ) );
}
// activate attribute is optional (since DS 1.1)
if ( attributes.getAttribute( "activate" ) != null )
{
m_currentComponent.setActivate( attributes.getAttribute( "activate" ) );
}
// deactivate attribute is optional (since DS 1.1)
if ( attributes.getAttribute( "deactivate" ) != null )
{
m_currentComponent.setDeactivate( attributes.getAttribute( "deactivate" ) );
}
// modified attribute is optional (since DS 1.1)
if ( attributes.getAttribute( "modified" ) != null )
{
m_currentComponent.setModified( attributes.getAttribute( "modified" ) );
}
// configuration-pid attribute is optional (since DS 1.2)
String configurationPidString = attributes.getAttribute( "configuration-pid" );
if (configurationPidString != null)
{
String[] configurationPid = configurationPidString.split( " " );
m_currentComponent.setConfigurationPid( configurationPid );
}
m_currentComponent.setConfigurableServiceProperties("true".equals(attributes.getAttribute(XmlConstants.NAMESPACE_URI_1_0_FELIX_EXTENSIONS, XmlConstants.ATTR_CONFIGURABLE_SERVICE_PROPERTIES)));
m_currentComponent.setPersistentFactoryComponent("true".equals(attributes.getAttribute(XmlConstants.NAMESPACE_URI_1_0_FELIX_EXTENSIONS, XmlConstants.ATTR_PERSISTENT_FACTORY_COMPONENT)));
m_currentComponent.setDeleteCallsModify("true".equals(attributes.getAttribute(XmlConstants.NAMESPACE_URI_1_0_FELIX_EXTENSIONS, XmlConstants.ATTR_DELETE_CALLS_MODIFY)));
if ( attributes.getAttribute(XmlConstants.NAMESPACE_URI_1_0_FELIX_EXTENSIONS, XmlConstants.ATTR_OBSOLETE_FACTORY_COMPONENT_FACTORY) != null)
{
m_currentComponent.setObsoleteFactoryComponentFactory("true".equals(attributes.getAttribute(XmlConstants.NAMESPACE_URI_1_0_FELIX_EXTENSIONS, XmlConstants.ATTR_OBSOLETE_FACTORY_COMPONENT_FACTORY)));
}
else if ( !namespaceCode.isDS13() )
{
m_currentComponent.setObsoleteFactoryComponentFactory(m_globalObsoleteFactoryComponentFactory);
}
m_currentComponent.setConfigureWithInterfaces("true".equals(attributes.getAttribute(XmlConstants.NAMESPACE_URI_1_0_FELIX_EXTENSIONS, XmlConstants.ATTR_CONFIGURE_WITH_INTERFACES)));
m_currentComponent.setDelayedKeepInstances(m_globalDelayedKeepInstances || "true".equals(attributes.getAttribute(XmlConstants.NAMESPACE_URI_1_0_FELIX_EXTENSIONS, XmlConstants.ATTR_DELAYED_KEEP_INSTANCES)));
// activation-fields is optional (since DS 1.4)
String activationFields = attributes.getAttribute( "activation-fields" );
if ( activationFields != null )
{
final String[] fields = activationFields.split(" ");
m_currentComponent.setActivationFields( fields );
}
// Add this component to the list
m_components.add( m_currentComponent );
}
// not inside a component element, ignore current element
else if ( !this.isComponent )
{
m_logger.log( LogService.LOG_DEBUG,
"Not currently parsing a component; ignoring element {0} (bundle {1})", new Object[]
{ localName, m_bundle.getLocation() }, null, null, null );
}
// 112.4.4 Implementation
else if ( localName.equals( XmlConstants.EL_IMPL ) )
{
// Set the implementation class name (mandatory)
m_currentComponent.setImplementationClassName( attributes.getAttribute( "class" ) );
}
// 112.4.5 [...] Property Elements
else if ( localName.equals( XmlConstants.EL_PROPERTY ) )
{
PropertyMetadata prop = new PropertyMetadata();
// name attribute is mandatory
prop.setName( attributes.getAttribute( XmlConstants.ATTR_NAME ) );
// type attribute is optional
if ( attributes.getAttribute( XmlConstants.ATTR_TYPE ) != null )
{
prop.setType( attributes.getAttribute( XmlConstants.ATTR_TYPE ) );
}
// 112.4.5: If the value attribute is specified, the body of the element is ignored.
if ( attributes.getAttribute( XmlConstants.ATTR_VALUE ) != null )
{
prop.setValue( attributes.getAttribute( XmlConstants.ATTR_VALUE ) );
m_currentComponent.addProperty( prop );
}
else
{
// hold the metadata pending
m_pendingProperty = prop;
}
}
// 112.4.5 Properties [...] Elements
else if ( localName.equals( XmlConstants.EL_PROPERTIES ) )
{
final Properties props = readPropertiesEntry( attributes.getAttribute( "entry" ) );
// create PropertyMetadata for the properties from the file
for ( Map.Entry<Object, Object> pEntry: props.entrySet() )
{
PropertyMetadata prop = new PropertyMetadata();
prop.setName( String.valueOf( pEntry.getKey() ) );
prop.setValue( String.valueOf( pEntry.getValue() ) );
m_currentComponent.addProperty( prop );
}
}
// 112.4.9 [...] Factory Property Element
else if ( localName.equals( XmlConstants.EL_FACTORY_PROPERTY ) )
{
PropertyMetadata prop = new PropertyMetadata();
// name attribute is mandatory
prop.setName( attributes.getAttribute( XmlConstants.ATTR_NAME ) );
// type attribute is optional
if ( attributes.getAttribute( XmlConstants.ATTR_TYPE ) != null )
{
prop.setType( attributes.getAttribute( XmlConstants.ATTR_TYPE ) );
}
// 112.4.5: If the value attribute is specified, the body of the element is ignored.
if ( attributes.getAttribute( XmlConstants.ATTR_VALUE ) != null )
{
prop.setValue( attributes.getAttribute( XmlConstants.ATTR_VALUE ) );
m_currentComponent.addFactoryProperty( prop );
}
else
{
// hold the metadata pending
m_pendingFactoryProperty = prop;
}
}
// 112.4.9 [...] Factory Properties Element
else if ( localName.equals( XmlConstants.EL_FACTORY_PROPERTIES ) )
{
final Properties props = readPropertiesEntry( attributes.getAttribute( "entry" ) );
// create PropertyMetadata for the properties from the file
for ( Map.Entry<Object, Object> pEntry: props.entrySet() )
{
PropertyMetadata prop = new PropertyMetadata();
prop.setName( String.valueOf( pEntry.getKey() ) );
prop.setValue( String.valueOf( pEntry.getValue() ) );
m_currentComponent.addFactoryProperty( prop );
}
}
// 112.4.6 Service Element
else if ( localName.equals( XmlConstants.EL_SERVICE ) )
{
m_currentService = new ServiceMetadata();
// servicefactory attribute is optional
if ( attributes.getAttribute( "servicefactory" ) != null )
{
m_currentService.setServiceFactory( attributes.getAttribute( "servicefactory" ).equals( "true" ) );
}
if ( attributes.getAttribute( "scope" ) != null )
{
m_currentService.setScope( attributes.getAttribute( "scope" ) );
}
m_currentComponent.setService( m_currentService );
}
else if ( localName.equals( XmlConstants.EL_PROVIDE ) )
{
m_currentService.addProvide( attributes.getAttribute( XmlConstants.ATTR_INTERFACE ) );
}
// 112.4.7 Reference element
else if ( localName.equals( XmlConstants.EL_REF ) )
{
ReferenceMetadata ref = new ReferenceMetadata();
// name attribute is optional (since DS 1.1)
if ( attributes.getAttribute( XmlConstants.ATTR_NAME ) != null )
{
ref.setName( attributes.getAttribute( XmlConstants.ATTR_NAME ) );
}
ref.setInterface( attributes.getAttribute( XmlConstants.ATTR_INTERFACE ) );
// Cardinality
if ( attributes.getAttribute( "cardinality" ) != null )
{
ref.setCardinality( attributes.getAttribute( "cardinality" ) );
}
if ( attributes.getAttribute( "policy" ) != null )
{
ref.setPolicy( attributes.getAttribute( "policy" ) );
}
if ( attributes.getAttribute( "policy-option" ) != null )
{
ref.setPolicyOption( attributes.getAttribute( "policy-option" ) );
}
if ( attributes.getAttribute( "scope" ) != null )
{
ref.setScope( attributes.getAttribute( "scope" ) );
}
if ( attributes.getAttribute( "target" ) != null)
{
ref.setTarget( attributes.getAttribute( "target" ) );
PropertyMetadata prop = new PropertyMetadata();
prop.setName( (ref.getName() == null? ref.getInterface(): ref.getName()) + ".target");
prop.setValue( attributes.getAttribute( "target" ) );
m_currentComponent.addProperty( prop );
}
// method reference
ref.setBind( attributes.getAttribute( "bind" ) );
ref.setUpdated( attributes.getAttribute( "updated" ) );
ref.setUnbind( attributes.getAttribute( "unbind" ) );
// field reference
ref.setField( attributes.getAttribute( "field" ) );
ref.setFieldOption( attributes.getAttribute( "field-option" ) );
ref.setFieldCollectionType( attributes.getAttribute( "field-collection-type" ) );
// DS 1.4 : references as parameter of the activator (method or constructor)
if ( attributes.getAttribute( "parameter" ) != null)
{
ref.setParameter( attributes.getAttribute( "parameter" ) );
}
m_currentComponent.addDependency( ref );
}
// unexpected element (except the root element "components"
// used by the Maven SCR Plugin, which is just silently ignored)
else if ( !localName.equals( XmlConstants.EL_COMPONENTS ) )
{
m_logger.log( LogService.LOG_DEBUG, "Ignoring unsupported element {0} (bundle {1})", new Object[]
{ localName, m_bundle.getLocation() }, null, null, null );
}
}
catch ( Exception ex )
{
throw new ParseException( "Exception during parsing", ex );
}
}
// unexpected namespace (except the root element "components"
// used by the Maven SCR Plugin, which is just silently ignored)
else if ( !localName.equals( XmlConstants.EL_COMPONENTS ) )
{
m_logger.log( LogService.LOG_DEBUG, "Ignoring unsupported element '{'{0}'}'{1} (bundle {2})", new Object[]
{ uri, localName, m_bundle.getLocation() }, null, null, null );
}
}
/**
* Method called when a tag closes
*
* @param uri
* @param localName
*/
@Override
public void endElement( String uri, String localName )
{
if ( overrideNamespace != null && XmlConstants.NAMESPACE_URI_EMPTY.equals( uri ) )
{
uri = overrideNamespace;
}
if ( this.isComponent && XmlConstants.NAMESPACE_URI_EMPTY.equals(uri) )
{
uri = XmlConstants.NAMESPACE_URI;
}
if ( XmlConstants.NAMESPACE_URI.equals( uri ) )
{
if ( localName.equals( XmlConstants.EL_COMPONENT ) )
{
this.isComponent = false;
}
else if ( localName.equals( XmlConstants.EL_PROPERTY ) && m_pendingProperty != null )
{
// 112.4.5 body expected to contain property value
// if so, the m_pendingProperty field would be null
// currently, we just ignore this situation
m_pendingProperty = null;
}
else if ( localName.equals( XmlConstants.EL_FACTORY_PROPERTY ) && m_pendingFactoryProperty != null )
{
// 112.4.5 body expected to contain property value
// if so, the m_pendingFactoryProperty field would be null
// currently, we just ignore this situation
m_pendingFactoryProperty = null;
}
}
}
/**
* @see org.apache.felix.scr.impl.parser.KXml2SAXHandler#characters(java.lang.String)
*/
@Override
public void characters( String text )
{
// 112.4.5 If the value attribute is not specified, the body must contain one or more values
if ( m_pendingProperty != null )
{
m_pendingProperty.setValues( text );
m_currentComponent.addProperty( m_pendingProperty );
m_pendingProperty = null;
}
if ( m_pendingFactoryProperty != null )
{
m_pendingFactoryProperty.setValues( text );
m_currentComponent.addFactoryProperty( m_pendingFactoryProperty );
m_pendingFactoryProperty = null;
}
}
/**
* @see org.apache.felix.scr.impl.parser.KXml2SAXHandler#processingInstruction(java.lang.String, java.lang.String)
*/
@Override
public void processingInstruction( String target, String data )
{
// Not used
}
/**
* @see org.apache.felix.scr.impl.parser.KXml2SAXHandler#setLineNumber(int)
*/
@Override
public void setLineNumber( int lineNumber )
{
// Not used
}
/**
* @see org.apache.felix.scr.impl.parser.KXml2SAXHandler#setColumnNumber(int)
*/
@Override
public void setColumnNumber( int columnNumber )
{
// Not used
}
/**
* Reads the name property file from the bundle owning this descriptor. All
* properties read from the properties file are added to the current
* component's property meta data list.
*
* @param entryName The name of the bundle entry containing the propertes
* to be added. This must not be <code>null</code>.
*
* @throws ParseException If the entry name is <code>null</code> or no
* entry with the given name exists in the bundle or an error occurrs
* reading the properties file.
*/
private Properties readPropertiesEntry( String entryName ) throws ParseException
{
if ( entryName == null )
{
throw new ParseException( "Missing entry attribute of properties element", null );
}
URL entryURL = m_bundle.getEntry( entryName );
if ( entryURL == null )
{
throw new ParseException( "Missing bundle entry " + entryName, null );
}
Properties props = new Properties();
InputStream entryStream = null;
try
{
entryStream = entryURL.openStream();
props.load( entryStream );
}
catch ( IOException ioe )
{
throw new ParseException( "Failed to read properties entry " + entryName, ioe );
}
finally
{
if ( entryStream != null )
{
try
{
entryStream.close();
}
catch ( IOException ignore )
{
// don't care
}
}
}
return props;
}
}