blob: 0b2469ddc55c65bbe00203dfcee3b1cf46eb62c6 [file] [log] [blame]
/*
* 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;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.felix.scr.impl.helper.ConfigAdminTracker;
import org.apache.felix.scr.impl.logger.BundleLogger;
import org.apache.felix.scr.impl.logger.ComponentLogger;
import org.apache.felix.scr.impl.logger.ScrLogger;
import org.apache.felix.scr.impl.manager.AbstractComponentManager;
import org.apache.felix.scr.impl.manager.ComponentActivator;
import org.apache.felix.scr.impl.manager.ComponentHolder;
import org.apache.felix.scr.impl.manager.DependencyManager;
import org.apache.felix.scr.impl.manager.ExtendedServiceEvent;
import org.apache.felix.scr.impl.manager.ExtendedServiceListener;
import org.apache.felix.scr.impl.manager.RegionConfigurationSupport;
import org.apache.felix.scr.impl.manager.ScrConfiguration;
import org.apache.felix.scr.impl.metadata.ComponentMetadata;
import org.apache.felix.scr.impl.xml.XmlHandler;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentException;
import org.osgi.service.log.LogService;
/**
* The BundleComponentActivator is helper class to load and unload Components of
* a single bundle. It will read information from the metadata.xml file
* descriptors and create the corresponding managers.
*/
public class BundleComponentActivator implements ComponentActivator
{
// global component registration
private final ComponentRegistry m_componentRegistry;
// The bundle owning the registered component
private final Bundle m_bundle;
// The bundle context owning the registered component
private final BundleContext m_context;
// This is a list of component holders that belong to a particular bundle
private final List<ComponentHolder<?>> m_holders = new ArrayList<>();
// thread acting upon configurations
private final ComponentActorThread m_componentActor;
// true as long as the dispose method is not called
private final AtomicBoolean m_active = new AtomicBoolean( true );
private final CountDownLatch m_closeLatch = new CountDownLatch( 1 );
// the configuration
private final ScrConfiguration m_configuration;
private final ConfigAdminTracker configAdminTracker;
private final Map<String, ListenerInfo> listenerMap = new HashMap<>();
private final BundleLogger logger;
private static class ListenerInfo implements ServiceListener
{
private Map<Filter, List<ExtendedServiceListener<ExtendedServiceEvent>>> filterMap = new HashMap<>();
@Override
public void serviceChanged(ServiceEvent event)
{
ServiceReference<?> ref = event.getServiceReference();
ExtendedServiceEvent extEvent = null;
ExtendedServiceEvent endMatchEvent = null;
Map<Filter, List<ExtendedServiceListener<ExtendedServiceEvent>>> filterMap;
synchronized ( this )
{
filterMap = this.filterMap;
}
for ( Map.Entry<Filter, List<ExtendedServiceListener<ExtendedServiceEvent>>> entry : filterMap.entrySet() )
{
Filter filter = entry.getKey();
if ( filter == null || filter.match( ref ) )
{
if ( extEvent == null )
{
extEvent = new ExtendedServiceEvent( event );
}
for ( ExtendedServiceListener<ExtendedServiceEvent> forwardTo : entry.getValue() )
{
forwardTo.serviceChanged( extEvent );
}
}
else if ( event.getType() == ServiceEvent.MODIFIED )
{
if ( endMatchEvent == null )
{
endMatchEvent = new ExtendedServiceEvent( ServiceEvent.MODIFIED_ENDMATCH, ref );
}
for ( ExtendedServiceListener<ExtendedServiceEvent> forwardTo : entry.getValue() )
{
forwardTo.serviceChanged( endMatchEvent );
}
}
}
if ( extEvent != null )
{
extEvent.activateManagers();
}
if ( endMatchEvent != null )
{
endMatchEvent.activateManagers();
}
}
public synchronized void add(Filter filter, ExtendedServiceListener<ExtendedServiceEvent> listener)
{
filterMap = new HashMap<>( filterMap );
List<ExtendedServiceListener<ExtendedServiceEvent>> listeners = filterMap.get( filter );
if ( listeners == null )
{
listeners = Collections.<ExtendedServiceListener<ExtendedServiceEvent>> singletonList( listener );
}
else
{
listeners = new ArrayList<>( listeners );
listeners.add( listener );
}
filterMap.put( filter, listeners );
}
public synchronized boolean remove(Filter filter, ExtendedServiceListener<ExtendedServiceEvent> listener)
{
List<ExtendedServiceListener<ExtendedServiceEvent>> listeners = filterMap.get( filter );
if ( listeners != null )
{
filterMap = new HashMap<>( filterMap );
listeners = new ArrayList<>( listeners );
listeners.remove( listener );
if ( listeners.isEmpty() )
{
filterMap.remove( filter );
}
else
{
filterMap.put( filter, listeners );
}
}
return filterMap.isEmpty();
}
}
@Override
public void addServiceListener(String classNameFilter, Filter eventFilter,
ExtendedServiceListener<ExtendedServiceEvent> listener)
{
ListenerInfo listenerInfo;
synchronized ( listenerMap )
{
logger.log( LogService.LOG_DEBUG, "classNameFilter: " + classNameFilter + " event filter: " + eventFilter, null);
listenerInfo = listenerMap.get( classNameFilter );
if ( listenerInfo == null )
{
listenerInfo = new ListenerInfo();
listenerMap.put( classNameFilter, listenerInfo );
try
{
m_context.addServiceListener( listenerInfo, classNameFilter );
}
catch ( InvalidSyntaxException e )
{
throw (IllegalArgumentException) new IllegalArgumentException(
"invalid class name filter" ).initCause( e );
}
}
}
listenerInfo.add( eventFilter, listener );
}
@Override
public void removeServiceListener(String className, Filter filter,
ExtendedServiceListener<ExtendedServiceEvent> listener)
{
synchronized ( listenerMap )
{
ListenerInfo listenerInfo = listenerMap.get( className );
if ( listenerInfo != null )
{
if ( listenerInfo.remove( filter, listener ) )
{
listenerMap.remove( className );
m_context.removeServiceListener( listenerInfo );
}
}
}
}
/**
* Called upon starting of the bundle. This method invokes initialize() which
* parses the metadata and creates the holders
*
* @param componentRegistry The <code>ComponentRegistry</code> used to
* register components with to ensure uniqueness of component names
* and to ensure configuration updates.
* @param context The bundle context owning the components
*
* @throws ComponentException if any error occurrs initializing this class
*/
public BundleComponentActivator(final ScrLogger scrLogger,
final ComponentRegistry componentRegistry,
final ComponentActorThread componentActor,
final BundleContext context,
final ScrConfiguration configuration,
final List<ComponentMetadata> cachedComponentMetadata)
throws ComponentException
{
// create a logger on behalf of the bundle
this.logger = new BundleLogger(context, scrLogger);
// keep the parameters for later
m_componentRegistry = componentRegistry;
m_componentActor = componentActor;
m_context = context;
m_bundle = context.getBundle();
m_configuration = configuration;
logger.log( LogService.LOG_DEBUG, "BundleComponentActivator : Bundle active", null);
initialize(cachedComponentMetadata);
ConfigAdminTracker tracker = null;
for ( ComponentHolder<?> holder : m_holders )
{
if ( !holder.getComponentMetadata().isConfigurationIgnored() )
{
tracker = new ConfigAdminTracker( this );
break;
}
}
configAdminTracker = tracker;
}
/**
* Gets the MetaData location, parses the meta data and requests the processing
* of binder instances
* @param cachedComponentMetadata
*
* @throws IllegalStateException If the bundle has already been uninstalled.
*/
protected void initialize(List<ComponentMetadata> cachedComponentMetadata)
{
if (cachedComponentMetadata != null)
{
for (ComponentMetadata metadata : cachedComponentMetadata)
{
validateAndRegister(metadata);
}
}
else
{
// Get the Metadata-Location value from the manifest
String descriptorLocations = m_bundle.getHeaders("").get("Service-Component");
if (descriptorLocations == null)
{
throw new ComponentException(
"Service-Component entry not found in the manifest");
}
logger.log(LogService.LOG_DEBUG,
"BundleComponentActivator : Descriptor locations {0}", null,
descriptorLocations);
// 112.4.1: The value of the the header is a comma separated list of XML entries within the Bundle
StringTokenizer st = new StringTokenizer(descriptorLocations, ", ");
while (st.hasMoreTokens())
{
String descriptorLocation = st.nextToken();
URL[] descriptorURLs = findDescriptors(m_bundle, descriptorLocation);
if (descriptorURLs.length == 0)
{
// 112.4.1 If an XML document specified by the header cannot be located in the bundle and its attached
// fragments, SCR must log an error message with the Log Service, if present, and continue.
logger.log(LogService.LOG_ERROR,
"Component descriptor entry ''{0}'' not found", null,
descriptorLocation);
continue;
}
// load from the descriptors
for (URL descriptorURL : descriptorURLs)
{
loadDescriptor(descriptorURL);
}
}
}
}
/**
* Called outside the constructor so that the m_managers field is completely initialized.
* A component might possibly start a thread to enable other components, which could access m_managers
*/
void initialEnable()
{
//enable all the enabled components
for ( ComponentHolder<?> componentHolder : m_holders )
{
logger.log( LogService.LOG_DEBUG, "BundleComponentActivator : May enable component holder {0}", null,
componentHolder.getComponentMetadata().getName() );
if ( componentHolder.getComponentMetadata().isEnabled() )
{
logger.log( LogService.LOG_DEBUG, "BundleComponentActivator :Enabling component holder {0}", null,
componentHolder.getComponentMetadata().getName() );
try
{
componentHolder.enableComponents( false );
}
catch ( Throwable t )
{
// caught on unhandled RuntimeException or Error
// (e.g. ClassDefNotFoundError)
// make sure the component is properly disabled, just in case
try
{
componentHolder.disableComponents( false );
}
catch ( Throwable ignore )
{
}
logger.log( LogService.LOG_ERROR,
"BundleComponentActivator : Unexpected failure enabling component holder {0}", t,
componentHolder.getComponentMetadata().getName() );
}
}
else
{
logger.log( LogService.LOG_DEBUG,
"BundleComponentActivator : Will not enable component holder {0}", null,
componentHolder.getComponentMetadata().getName() );
}
}
}
/**
* Finds component descriptors based on descriptor location.
*
* @param bundle bundle to search for descriptor files
* @param descriptorLocation descriptor location
* @return array of descriptors or empty array if none found
*/
static URL[] findDescriptors(final Bundle bundle, final String descriptorLocation)
{
if ( bundle == null || descriptorLocation == null || descriptorLocation.trim().length() == 0 )
{
return new URL[0];
}
// split pattern and path
final int lios = descriptorLocation.lastIndexOf( "/" );
final String path;
final String filePattern;
if ( lios > 0 )
{
path = descriptorLocation.substring( 0, lios );
filePattern = descriptorLocation.substring( lios + 1 );
}
else
{
path = "/";
filePattern = descriptorLocation;
}
// find the entries
final Enumeration<URL> entries = bundle.findEntries( path, filePattern, false );
if ( entries == null || !entries.hasMoreElements() )
{
return new URL[0];
}
// create the result list
List<URL> urls = new ArrayList<>();
while ( entries.hasMoreElements() )
{
urls.add( entries.nextElement() );
}
return urls.toArray( new URL[urls.size()] );
}
private void loadDescriptor(final URL descriptorURL)
{
// simple path for log messages
final String descriptorLocation = descriptorURL.getPath();
InputStream stream = null;
try
{
stream = descriptorURL.openStream();
XmlHandler handler = new XmlHandler( m_bundle, this.logger, getConfiguration().isFactoryEnabled(),
getConfiguration().keepInstances() );
final SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
final SAXParser parser = factory.newSAXParser();
parser.parse( stream, handler );
// 112.4.2 Component descriptors may contain a single, root component element
// or one or more component elements embedded in a larger document
for ( ComponentMetadata metadata : handler.getComponentMetadataList() )
{
validateAndRegister(metadata);
}
}
catch ( IOException ex )
{
// 112.4.1 If an XML document specified by the header cannot be located in the bundle and its attached
// fragments, SCR must log an error message with the Log Service, if present, and continue.
logger.log( LogService.LOG_ERROR, "Problem reading descriptor entry ''{0}''", ex, descriptorLocation );
}
catch ( Exception ex )
{
logger.log( LogService.LOG_ERROR, "General problem with descriptor entry ''{0}''", ex, descriptorLocation );
}
finally
{
if ( stream != null )
{
try
{
stream.close();
}
catch ( IOException ignore )
{
}
}
}
}
void validateAndRegister(ComponentMetadata metadata)
{
final ComponentLogger componentLogger = new ComponentLogger(metadata, logger);
ComponentRegistryKey key = null;
try
{
// validate the component metadata
metadata.validate();
// check and reserve the component name (validate ensures it's never null)
key = m_componentRegistry.checkComponentName(m_bundle, metadata.getName());
// Request creation of the component manager
ComponentHolder<?> holder = m_componentRegistry.createComponentHolder(this,
metadata, componentLogger);
// register the component after validation
m_componentRegistry.registerComponentHolder(key, holder);
m_holders.add(holder);
componentLogger.log(LogService.LOG_DEBUG,
"BundleComponentActivator : ComponentHolder created.", null);
}
catch (Throwable t)
{
// There is a problem with this particular component, we'll log the error
// and proceed to the next one
componentLogger.log(LogService.LOG_ERROR, "Cannot register component", t);
// make sure the name is not reserved any more
if (key != null)
{
m_componentRegistry.unregisterComponentHolder(key);
}
}
}
/**
* Dispose of this component activator instance and all the component
* managers.
*/
void dispose(int reason)
{
if ( m_active.compareAndSet( true, false ) )
{
logger.log( LogService.LOG_DEBUG, "BundleComponentActivator : Will destroy {0} instances",
null, m_holders.size() );
for ( ComponentHolder<?> holder : m_holders )
{
try
{
holder.disposeComponents( reason );
}
catch ( Exception e )
{
logger.log( LogService.LOG_ERROR, "BundleComponentActivator : Exception invalidating", e,
holder.getComponentMetadata() );
}
finally
{
m_componentRegistry.unregisterComponentHolder( m_bundle, holder.getComponentMetadata().getName() );
}
}
if ( configAdminTracker != null )
{
configAdminTracker.dispose();
}
logger.log( LogService.LOG_DEBUG, "BundleComponentActivator : Bundle STOPPED",
null );
logger.close();
m_closeLatch.countDown();
}
else
{
try
{
m_closeLatch.await( m_configuration.lockTimeout(), TimeUnit.MILLISECONDS );
}
catch ( InterruptedException e )
{
//ignore interruption during concurrent shutdown.
Thread.currentThread().interrupt();
}
}
}
/**
* Returns <true> if this instance is active, that is if components
* may be activated for this component. The active flag is set early
* in the constructor indicating the activator is basically active
* (not fully setup, though) and reset early in the process of
* {@link #dispose(int) disposing} this instance.
*/
@Override
public boolean isActive()
{
return m_active.get();
}
/**
* Returns the BundleContext
*
* @return the BundleContext
*/
@Override
public BundleContext getBundleContext()
{
return m_context;
}
@Override
public ScrConfiguration getConfiguration()
{
return m_configuration;
}
/**
* Implements the <code>ComponentContext.enableComponent(String)</code>
* method by first finding the component(s) for the <code>name</code> and
* enabling them. The enable method will schedule activation.
* <p>
*
* @param name The name of the component to enable or <code>null</code> to
* enable all components.
*/
@Override
public void enableComponent(final String name)
{
final List<ComponentHolder<?>> holder = getSelectedComponents( name );
for ( ComponentHolder<?> aHolder : holder )
{
try
{
// TODO use component logger
logger.log( LogService.LOG_DEBUG, "Enabling Component {0}", null, aHolder.getComponentMetadata().getName() );
aHolder.enableComponents( true );
}
catch ( Throwable t )
{
// TODO use component logger
logger.log( LogService.LOG_ERROR, "Cannot enable component {0}", t, aHolder.getComponentMetadata().getName() );
}
}
}
/**
* Implements the <code>ComponentContext.disableComponent(String)</code>
* method by first finding the component(s) for the <code>name</code> and
* disabling them. The disable method will schedule deactivation
* <p>
*
* @param name The name of the component to disable or <code>null</code> to
* disable all components.
*/
@Override
public void disableComponent(final String name)
{
final List<ComponentHolder<?>> holder = getSelectedComponents( name );
for ( ComponentHolder<?> aHolder : holder )
{
try
{
// TODO use component logger
logger.log( LogService.LOG_DEBUG, "Disabling Component {0}", null, aHolder.getComponentMetadata().getName() );
aHolder.disableComponents( true );
}
catch ( Throwable t )
{
// TODO use component logger
logger.log( LogService.LOG_ERROR, "Cannot disable component {0}", t, aHolder.getComponentMetadata().getName() );
}
}
}
/**
* Returns an array of {@link ComponentHolder} instances which match the
* <code>name</code>. If the <code>name</code> is <code>null</code> an
* array of all currently known component managers is returned. Otherwise
* an array containing a single component manager matching the name is
* returned if one is registered. Finally, if no component manager with the
* given name is registered, <code>null</code> is returned.
*
* @param name The name of the component manager to return or
* <code>null</code> to return an array of all component managers.
*
* @return An array containing one or more component managers according
* to the <code>name</code> parameter or <code>null</code> if no
* component manager with the given name is currently registered.
*/
List<ComponentHolder<?>> getSelectedComponents(String name)
{
// if all components are selected
if ( name == null )
{
return m_holders;
}
ComponentHolder<?> componentHolder = m_componentRegistry.getComponentHolder( m_bundle, name );
if ( componentHolder != null )
{
return Collections.<ComponentHolder<?>> singletonList( componentHolder );
}
// if the component is not known
return Collections.emptyList();
}
//---------- Component ID support
@Override
public long registerComponentId(AbstractComponentManager<?> componentManager)
{
return m_componentRegistry.registerComponentId( componentManager );
}
@Override
public void unregisterComponentId(AbstractComponentManager<?> componentManager)
{
m_componentRegistry.unregisterComponentId( componentManager.getId() );
}
//---------- Asynchronous Component Handling ------------------------------
/**
* Schedules the given <code>task</code> for asynchrounous execution or
* synchronously runs the task if the thread is not running. If this instance
* is {@link #isActive() not active}, the task is not executed.
*
* @param task The component task to execute
*/
@Override
public void schedule(Runnable task)
{
if ( isActive() )
{
ComponentActorThread cat = m_componentActor;
if ( cat != null )
{
cat.schedule( task );
}
else
{
logger.log( LogService.LOG_DEBUG, "Component Actor Thread not running, calling synchronously", null );
try
{
synchronized ( this )
{
task.run();
}
}
catch ( Throwable t )
{
logger.log( LogService.LOG_WARNING, "Unexpected problem executing task", t );
}
}
}
else
{
logger.log( LogService.LOG_WARNING, "BundleComponentActivator is not active; not scheduling {0}",
null, task );
}
}
@Override
public BundleLogger getLogger() {
return logger;
}
@Override
public <T> boolean enterCreate(ServiceReference<T> serviceReference)
{
return m_componentRegistry.enterCreate( serviceReference );
}
@Override
public <T> void leaveCreate(ServiceReference<T> serviceReference)
{
m_componentRegistry.leaveCreate( serviceReference );
}
@Override
public <T> void missingServicePresent(ServiceReference<T> serviceReference)
{
m_componentRegistry.missingServicePresent( serviceReference, m_componentActor );
}
@Override
public <S, T> void registerMissingDependency(DependencyManager<S, T> dependencyManager,
ServiceReference<T> serviceReference, int trackingCount)
{
m_componentRegistry.registerMissingDependency( dependencyManager, serviceReference, trackingCount );
}
@Override
public RegionConfigurationSupport setRegionConfigurationSupport(ServiceReference<ConfigurationAdmin> reference)
{
RegionConfigurationSupport rcs = m_componentRegistry.registerRegionConfigurationSupport( reference );
if (rcs != null) {
for ( ComponentHolder<?> holder : m_holders )
{
rcs.configureComponentHolder( holder );
}
}
return rcs;
}
@Override
public void unsetRegionConfigurationSupport(RegionConfigurationSupport rcs)
{
m_componentRegistry.unregisterRegionConfigurationSupport( rcs );
// TODO anything needed?
}
@Override
public void updateChangeCount() {
this.m_componentRegistry.updateChangeCount();
}
}