| /* |
| * 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.InternalLogger.Level; |
| import org.apache.felix.scr.impl.logger.ScrLogManager; |
| 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; |
| |
| /** |
| * 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(); |
| // 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(Level.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(Level.ERROR, "Exception stopping during restart", e); |
| } |
| } |
| } |
| try |
| { |
| logger.log(Level.INFO, "Starting with globalExtender setting: {0}", null, |
| globalExtender); |
| |
| super.start( m_globalContext ); |
| } |
| catch ( final Exception e ) |
| { |
| logger.log(Level.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(Level.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()); |
| logger.close(); |
| } |
| |
| @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(Level.WARN, |
| "Error loading component metadata cache.", e); |
| } |
| } |
| return result; |
| } |
| catch (RuntimeException re) |
| { |
| // avoid failing all of SCR start on cache load bug |
| logger.log(Level.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(Level.WARN, "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; |
| } |
| 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(Level.WARN, |
| "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(Level.WARN, |
| "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(Level.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(Level.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(Level.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(Level.DEBUG, |
| "{0} has been stopped while trying to activate its components. Trying again when the bundles gets started again.", |
| e, |
| bundle ); |
| } |
| else |
| { |
| logger.log(Level.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(Level.ERROR, "Error while disposing components of {0}", e, |
| bundle); |
| } |
| } |
| } |
| |
| @Override |
| protected void debug(final Bundle bundle, final String msg) |
| { |
| if (logger.isLogEnabled(Level.DEBUG)) |
| { |
| if ( bundle != null ) |
| { |
| logger.log(Level.DEBUG, "{0} : " + msg, null, bundle); |
| } |
| else |
| { |
| logger.log(Level.DEBUG, msg, null); |
| } |
| } |
| } |
| |
| @Override |
| protected void warn(final Bundle bundle, final String msg, final Throwable t) |
| { |
| if (logger.isLogEnabled(Level.WARN)) |
| { |
| if ( bundle != null ) |
| { |
| logger.log(Level.WARN, "{0} : " + msg, t, bundle); |
| } |
| else |
| { |
| logger.log(Level.WARN, msg, t); |
| } |
| } |
| } |
| |
| public void setLogger() |
| { |
| // TODO we only set the logger once |
| // If the need arises to be able to dynamically set the logger type |
| // then more work is needed to do that switch |
| // for now we only can configure ds.log.extension with context properties |
| if (logger == null) |
| { |
| logger = ScrLogManager.scr(m_context, m_configuration); |
| } |
| |
| } |
| } |