blob: fbe6ec235936840ce8ec88ee1013c7e7c59997c7 [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.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.felix.scr.impl.config.ScrConfigurationImpl;
import org.apache.felix.scr.impl.inject.internal.ClassUtils;
import org.apache.felix.scr.impl.logger.ScrLogger;
import org.apache.felix.scr.impl.manager.ComponentHolder;
import org.apache.felix.scr.impl.metadata.ComponentMetadata;
import org.apache.felix.scr.impl.metadata.MetadataStoreHelper.MetaDataReader;
import org.apache.felix.scr.impl.metadata.MetadataStoreHelper.MetaDataWriter;
import org.apache.felix.scr.impl.runtime.ServiceComponentRuntimeImpl;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.BundleWire;
import org.osgi.framework.wiring.BundleWiring;
import org.osgi.namespace.extender.ExtenderNamespace;
import org.osgi.service.component.ComponentConstants;
import org.osgi.service.component.runtime.ServiceComponentRuntime;
import org.osgi.service.log.LogService;
/**
* This activator is used to cover requirement described in section 112.8.1 @@ -27,14
* 37,202 @@ in active bundles.
*
*/
public class Activator extends AbstractExtender
{
// Our configuration from bundle context properties and Config Admin
private final ScrConfigurationImpl m_configuration;
private BundleContext m_context;
//Either this bundle's context or the framework bundle context, depending on the globalExtender setting.
private BundleContext m_globalContext;
// this bundle
private Bundle m_bundle;
// the log service to log messages to
private volatile ScrLogger logger;
// map of BundleComponentActivator instances per Bundle indexed by Bundle id
private Map<Long, BundleComponentActivator> m_componentBundles;
// registry of managed component
private ComponentRegistry m_componentRegistry;
// thread acting upon configurations
private ComponentActorThread m_componentActor;
private ServiceRegistration<ServiceComponentRuntime> m_runtime_reg;
private ComponentCommands m_componentCommands;
private ConcurrentMap<Long, List<ComponentMetadata>> m_componentMetadataStore;
public Activator()
{
m_configuration = new ScrConfigurationImpl( this );
}
/**
* Registers this instance as a (synchronous) bundle listener and loads the
* components of already registered bundles.
*
* @param context The <code>BundleContext</code> of the SCR implementation
* bundle.
*/
@Override
public void start(final BundleContext context) throws Exception
{
m_context = context;
m_bundle = context.getBundle();
// require the log service
logger = new ScrLogger(m_configuration, m_context);
// set bundle context for PackageAdmin tracker
ClassUtils.setBundleContext( context );
// get the configuration
m_configuration.start( m_context ); //this will call restart, which calls super.start.
}
public void restart(boolean globalExtender)
{
m_componentMetadataStore = load(m_context, logger,
m_configuration.cacheMetadata());
BundleContext context = m_globalContext;
if ( globalExtender )
{
m_globalContext = m_context.getBundle( Constants.SYSTEM_BUNDLE_LOCATION ).getBundleContext();
}
else
{
m_globalContext = m_context;
}
if ( ClassUtils.m_packageAdmin != null )
{
logger.log(LogService.LOG_INFO, "Stopping to restart with new globalExtender setting: {0}", null, globalExtender);
//this really is a restart, not the initial start
// the initial start where m_globalContext is null should skip this as m_packageAdmin should not yet be set.
try
{
super.stop( context );
}
catch ( final Exception e )
{
// logger might be null
if ( logger != null )
{
logger.log(LogService.LOG_ERROR, "Exception stopping during restart", e);
}
}
// reinstantiate logger
logger = new ScrLogger(m_configuration, m_context);
}
try
{
logger.log(LogService.LOG_INFO, "Starting with globalExtender setting: {0}", null, globalExtender);
super.start( m_globalContext );
}
catch ( final Exception e )
{
logger.log(LogService.LOG_ERROR, "Exception starting during restart", e);
}
}
@Override
protected void doStart() throws Exception
{
// prepare component registry
m_componentBundles = new HashMap<>();
m_componentRegistry = new ComponentRegistry( this.m_configuration, this.logger );
final ServiceComponentRuntimeImpl runtime = new ServiceComponentRuntimeImpl( m_globalContext, m_componentRegistry );
m_runtime_reg = m_context.registerService( ServiceComponentRuntime.class,
runtime,
m_componentRegistry.getServiceRegistrationProperties() );
m_componentRegistry.setRegistration(m_runtime_reg);
// log SCR startup
logger.log( LogService.LOG_INFO, " Version = {0}",
null, m_bundle.getVersion().toString() );
// create and start the component actor
m_componentActor = new ComponentActorThread( this.logger );
Thread t = new Thread( m_componentActor, "SCR Component Actor" );
t.setDaemon( true );
t.start();
super.doStart();
m_componentCommands = new ComponentCommands(m_context, runtime, m_configuration);
m_componentCommands.register();
m_componentCommands.updateProvideScrInfoService(m_configuration.infoAsService());
m_configuration.setScrCommand(m_componentCommands);
}
@Override
public void stop(BundleContext context) throws Exception
{
super.stop( context );
m_configuration.stop();
store(m_componentMetadataStore, context, logger, m_configuration.cacheMetadata());
}
@Override
public void bundleChanged(BundleEvent event)
{
super.bundleChanged(event);
if (event.getType() == BundleEvent.UPDATED
|| event.getType() == BundleEvent.UNINSTALLED)
{
m_componentMetadataStore.remove(event.getBundle().getBundleId());
}
}
private static ConcurrentMap<Long, List<ComponentMetadata>> load(
BundleContext context,
ScrLogger logger, boolean loadFromCache)
{
try
{
ConcurrentMap<Long, List<ComponentMetadata>> result = new ConcurrentHashMap<>();
if (!loadFromCache)
{
return result;
}
BundleContext systemContext = context.getBundle(
Constants.SYSTEM_BUNDLE_LOCATION).getBundleContext();
File store = context.getDataFile("componentMetadataStore");
if (store.isFile())
{
try (DataInputStream in = new DataInputStream(
new BufferedInputStream(new FileInputStream(store))))
{
MetaDataReader metaDataReader = new MetaDataReader();
if (!metaDataReader.isVersionSupported(in))
{
// the stored version is not compatible
return result;
}
int numStrings = in.readInt();
for (int i = 0; i < numStrings; i++)
{
metaDataReader.readIndexedString(in);
}
int numBundles = in.readInt();
for (int i = 0; i < numBundles; i++)
{
// Read all the components for the ID even if the bundle does not exist;
long bundleId = in.readLong();
int numComponents = in.readInt();
long lastModified = in.readLong();
List<ComponentMetadata> components = new ArrayList<>(
numComponents);
for (int j = 0; j < numComponents; j++)
{
components.add(ComponentMetadata.load(in, metaDataReader));
}
// Check with system context by ID to avoid hooks hiding;
Bundle b = systemContext.getBundle(bundleId);
if (b != null)
{
if (lastModified == b.getLastModified())
{
result.put(bundleId, components);
}
}
}
}
catch (IOException e)
{
logger.log(LogService.LOG_WARNING,
"Error loading component metadata cache.", e);
}
}
return result;
}
catch (RuntimeException re)
{
// avoid failing all of SCR start on cache load bug
logger.log(LogService.LOG_ERROR,
"Error loading component metadata cache.", re);
return new ConcurrentHashMap<>();
}
}
private static void store(Map<Long, List<ComponentMetadata>> componentsMap,
BundleContext context, ScrLogger logger, boolean storeCache)
{
if (!storeCache)
{
return;
}
BundleContext systemContext = context.getBundle(
Constants.SYSTEM_BUNDLE_LOCATION).getBundleContext();
File store = context.getDataFile("componentMetadataStore");
try (DataOutputStream out = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(store))))
{
MetaDataWriter metaDataWriter = new MetaDataWriter();
metaDataWriter.writeVersion(out);
Set<String> allStrings = new HashSet<>();
for (List<ComponentMetadata> components : componentsMap.values())
{
for (ComponentMetadata component : components)
{
component.collectStrings(allStrings);
}
}
// remove possible null
allStrings.remove(null);
out.writeInt(allStrings.size());
for (String s : allStrings)
{
metaDataWriter.writeIndexedString(s, out);
}
out.writeInt(componentsMap.size());
for (Entry<Long, List<ComponentMetadata>> entry : componentsMap.entrySet())
{
out.writeLong(entry.getKey());
out.writeInt(entry.getValue().size());
Bundle b = systemContext.getBundle(entry.getKey());
out.writeLong(b == null ? -1 : b.getLastModified());
for (ComponentMetadata component : entry.getValue())
{
component.store(out, metaDataWriter);
}
}
}
catch (IOException e)
{
logger.log(LogService.LOG_WARNING, "Error storing component metadata cache.",
e);
}
}
/**
* Unregisters this instance as a bundle listener and unloads all components
* which have been registered during the active life time of the SCR
* implementation bundle.
*/
@Override
public void doStop() throws Exception
{
// stop tracking
super.doStop();
if ( m_componentCommands != null )
{
m_componentCommands.unregister();
}
if ( m_runtime_reg != null )
{
m_runtime_reg.unregister();
m_runtime_reg = null;
}
// dispose component registry
if ( m_componentRegistry != null )
{
m_componentRegistry = null;
}
// terminate the actor thread
if ( m_componentActor != null )
{
m_componentActor.terminate();
m_componentActor = null;
}
// close the LogService tracker now
if ( logger != null )
{
logger.close();
logger = null;
}
ClassUtils.close();
}
//---------- Component Management -----------------------------------------
@Override
protected ScrExtension doCreateExtension(final Bundle bundle) throws Exception
{
return new ScrExtension( bundle );
}
protected class ScrExtension
{
private final Bundle bundle;
private final Lock stateLock = new ReentrantLock();
public ScrExtension(Bundle bundle)
{
this.bundle = bundle;
}
public void start()
{
boolean acquired = false;
try
{
try
{
acquired = stateLock.tryLock( m_configuration.stopTimeout(), TimeUnit.MILLISECONDS );
}
catch ( final InterruptedException e )
{
Thread.currentThread().interrupt();
logger.log(LogService.LOG_WARNING, "The wait for {0} being destroyed before destruction has been interrupted.", e,
bundle );
}
loadComponents( ScrExtension.this.bundle );
}
finally
{
if ( acquired )
{
stateLock.unlock();
}
}
}
public void destroy()
{
boolean acquired = false;
try
{
try
{
acquired = stateLock.tryLock( m_configuration.stopTimeout(), TimeUnit.MILLISECONDS );
}
catch ( final InterruptedException e )
{
Thread.currentThread().interrupt();
logger.log(LogService.LOG_WARNING, "The wait for {0} being started before destruction has been interrupted.", e,
bundle );
}
disposeComponents( bundle );
}
finally
{
if ( acquired )
{
stateLock.unlock();
}
}
}
}
/**
* Loads the components of the given bundle. If the bundle has no
* <i>Service-Component</i> header, this method has no effect. The
* fragments of a bundle are not checked for the header (112.4.1).
* <p>
* This method calls the {@link Bundle#getBundleContext()} method to find
* the <code>BundleContext</code> of the bundle. If the context cannot be
* found, this method does not load components for the bundle.
*/
private void loadComponents(Bundle bundle)
{
final Long bundleId = bundle.getBundleId();
List<ComponentMetadata> cached = m_componentMetadataStore.get(bundleId);
if (cached != null && cached.isEmpty())
{
// Cached that there are no components for this bundle.
return;
}
if (cached == null
&& bundle.getHeaders("").get(ComponentConstants.SERVICE_COMPONENT) == null)
{
// Cache that there are no components
m_componentMetadataStore.put(bundleId,
Collections.<ComponentMetadata> emptyList());
// no components in the bundle, abandon
return;
}
// there should be components, load them with a bundle context
BundleContext context = bundle.getBundleContext();
if ( context == null )
{
logger.log(LogService.LOG_DEBUG, "Cannot get BundleContext of {0}.", null, bundle);
return;
}
//Examine bundle for extender requirement; if present check if bundle is wired to us.
BundleWiring wiring = bundle.adapt( BundleWiring.class );
List<BundleWire> extenderWires = wiring.getRequiredWires( ExtenderNamespace.EXTENDER_NAMESPACE );
for ( BundleWire wire : extenderWires )
{
if ( ComponentConstants.COMPONENT_CAPABILITY_NAME.equals(
wire.getCapability().getAttributes().get( ExtenderNamespace.EXTENDER_NAMESPACE ) ) )
{
if ( !m_bundle.adapt( BundleRevision.class ).equals( wire.getProvider() ) )
{
logger.log(LogService.LOG_DEBUG, "{0} wired to a different extender: {1}.", null,
bundle, wire.getProvider().getBundle());
return;
}
break;
}
}
// FELIX-1666 method is called for the LAZY_ACTIVATION event and
// the started event. Both events cause this method to be called;
// so we have to make sure to not load components twice
// FELIX-2231 Mark bundle loaded early to prevent concurrent loading
// if LAZY_ACTIVATION and STARTED event are fired at the same time
final boolean loaded;
synchronized ( m_componentBundles )
{
if ( m_componentBundles.containsKey( bundleId ) )
{
loaded = true;
}
else
{
m_componentBundles.put( bundleId, null );
loaded = false;
}
}
// terminate if already loaded (or currently being loaded)
if ( loaded )
{
logger.log(LogService.LOG_DEBUG, "Components for {0} already loaded. Nothing to do.", null,
bundle );
return;
}
try
{
BundleComponentActivator ga = new BundleComponentActivator( this.logger, m_componentRegistry, m_componentActor,
context, m_configuration, cached);
ga.initialEnable();
if (cached == null)
{
List<ComponentHolder<?>> components = ga.getSelectedComponents(null);
List<ComponentMetadata> metadatas = new ArrayList<>(components.size());
for (ComponentHolder<?> holder : ga.getSelectedComponents(null))
{
metadatas.add(holder.getComponentMetadata());
}
m_componentMetadataStore.put(bundleId, metadatas);
}
// replace bundle activator in the map
synchronized ( m_componentBundles )
{
m_componentBundles.put( bundleId, ga );
}
}
catch ( Exception e )
{
// remove the bundle id from the bundles map to ensure it is
// not marked as being loaded
synchronized ( m_componentBundles )
{
m_componentBundles.remove( bundleId );
}
if ( e instanceof IllegalStateException && bundle.getState() != Bundle.ACTIVE )
{
logger.log(LogService.LOG_DEBUG, "{0} has been stopped while trying to activate its components. Trying again when the bundles gets started again.", e,
bundle );
}
else
{
logger.log(LogService.LOG_ERROR, "Error while loading components of {0}", e, bundle);
}
}
}
/**
* Unloads components of the given bundle. If no components have been loaded
* for the bundle, this method has no effect.
*/
private void disposeComponents(Bundle bundle)
{
final BundleComponentActivator ga;
synchronized ( m_componentBundles )
{
ga = m_componentBundles.remove( bundle.getBundleId() );
}
if ( ga != null )
{
try
{
int reason = isStopping()? ComponentConstants.DEACTIVATION_REASON_DISPOSED
: ComponentConstants.DEACTIVATION_REASON_BUNDLE_STOPPED;
ga.dispose( reason );
}
catch ( Exception e )
{
logger.log(LogService.LOG_ERROR, "Error while disposing components of {0}", e, bundle);
}
}
}
@Override
protected void debug(final Bundle bundle, final String msg)
{
if ( logger.isLogEnabled(LogService.LOG_DEBUG) )
{
if ( bundle != null )
{
logger.log( LogService.LOG_DEBUG, "{0} : " + msg, null, bundle );
}
else
{
logger.log( LogService.LOG_DEBUG, msg, null );
}
}
}
@Override
protected void warn(final Bundle bundle, final String msg, final Throwable t)
{
if ( logger.isLogEnabled(LogService.LOG_WARNING) )
{
if ( bundle != null )
{
logger.log( LogService.LOG_WARNING, "{0} : " + msg, t, bundle );
}
else
{
logger.log( LogService.LOG_WARNING, msg, t );
}
}
}
}