blob: 556bf5ee91404fc4d44ae0300d1d4915bdea23cb [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.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.Constants;
import org.osgi.framework.SynchronousBundleListener;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;
/**
* This activator is used to cover requirement described in section 112.8.1 @@ -27,14
* 37,202 @@ in active bundles.
*
*/
public class Activator implements BundleActivator, SynchronousBundleListener
{
// name of the LogService class (this is a string to not create a reference to the class)
static final String LOGSERVICE_CLASS = "org.osgi.service.log.LogService";
// Flag that sets error messages
private static int m_logLevel = LogService.LOG_ERROR;
// this bundle's context
private BundleContext m_context;
// the log service to log messages to
private static ServiceTracker m_logService;
// map of BundleComponentActivator instances per Bundle indexed by Bundle symbolic
// name
private Map m_componentBundles;
// registry of managed component
private ComponentRegistry m_componentRegistry;
// thread acting upon configurations
private ComponentActorThread m_componentActor;
/**
* 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.
*/
public void start( BundleContext context ) throws Exception
{
m_context = context;
m_componentBundles = new HashMap();
m_componentRegistry = new ComponentRegistry( m_context );
// require the log service
m_logService = new ServiceTracker( context, LOGSERVICE_CLASS, null );
m_logService.open();
// configure logging from context properties
m_logLevel = getLogLevel( context );
if ( "true".equalsIgnoreCase( context.getProperty( "ds.showversion" ) ) )
{
log( LogService.LOG_INFO, context.getBundle(), " Version = "
+ context.getBundle().getHeaders().get( Constants.BUNDLE_VERSION ), null );
}
// create and start the component actor
m_componentActor = new ComponentActorThread();
m_componentActor.start();
// register for bundle updates
context.addBundleListener( this );
// 112.8.2 load all components of active bundles
loadAllComponents( context );
// We dynamically import the impl service API, so it
// might not actually be available, so be ready to catch
// the exception when we try to register the command service.
try
{
// Register "scr" impl command service as a
// wrapper for the bundle repository service.
context.registerService( org.apache.felix.shell.Command.class.getName(), new ScrCommand( m_context,
m_componentRegistry ), null );
}
catch ( Throwable th )
{
// Ignore.
}
}
/**
* 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.
*
* @param context The <code>BundleContext</code> of the SCR implementation
* bundle.
*/
public void stop( BundleContext context ) throws Exception
{
// unregister as bundle listener
context.removeBundleListener( this );
// 112.8.2 dispose off all active components
disposeAllComponents();
// dispose off the component registry
m_componentRegistry.dispose();
// terminate the actor thread and wait for it for a limited time
if ( m_componentActor != null )
{
// terminate asynchrounous updates
m_componentActor.terminate();
// wait for all updates to terminate
try
{
m_componentActor.join( 5000 );
}
catch ( InterruptedException ie )
{
// don't really care
}
}
}
// ---------- BundleListener Interface -------------------------------------
/**
* Loads and unloads any components provided by the bundle whose state
* changed. If the bundle has been started, the components are loaded. If
* the bundle is about to stop, the components are unloaded.
*
* @param event The <code>BundleEvent</code> representing the bundle state
* change.
*/
public void bundleChanged( BundleEvent event )
{
if ( event.getType() == BundleEvent.STARTED )
{
loadComponents( event.getBundle() );
}
else if ( event.getType() == BundleEvent.STOPPING )
{
disposeComponents( event.getBundle() );
}
}
//---------- Component Management -----------------------------------------
// Loads the components of all bundles currently active.
private void loadAllComponents( BundleContext context )
{
Bundle[] bundles = context.getBundles();
for ( int i = 0; i < bundles.length; i++ )
{
Bundle bundle = bundles[i];
if ( bundle.getState() == Bundle.ACTIVE )
{
loadComponents( bundle );
}
}
}
/**
* 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 #getBundleContext(Bundle)} 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 )
{
if ( bundle.getHeaders().get( "Service-Component" ) == null )
{
// no components in the bundle, abandon
return;
}
// there should be components, load them with a bundle context
BundleContext context = getBundleContext( bundle );
if ( context == null )
{
log( LogService.LOG_ERROR, m_context.getBundle(), "Cannot get BundleContext of bundle "
+ bundle.getSymbolicName(), null );
return;
}
try
{
BundleComponentActivator ga = new BundleComponentActivator( m_componentRegistry, m_componentActor, context,
m_logLevel );
m_componentBundles.put( bundle.getSymbolicName(), ga );
}
catch ( Exception e )
{
if ( e instanceof IllegalStateException && bundle.getState() != Bundle.ACTIVE )
{
log(
LogService.LOG_INFO,
m_context.getBundle(),
"Bundle "
+ bundle.getSymbolicName()
+ " has been stopped while trying to activate its components. Trying again when the bundles gets startet again.",
e );
}
else
{
log( LogService.LOG_ERROR, m_context.getBundle(), "Error while loading components of bundle "
+ bundle.getSymbolicName(), e );
}
}
}
/**
* 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 )
{
String name = bundle.getSymbolicName();
BundleComponentActivator ga = ( BundleComponentActivator ) m_componentBundles.remove( name );
if ( ga != null )
{
try
{
ga.dispose();
}
catch ( Exception e )
{
log( LogService.LOG_ERROR, m_context.getBundle(), "Error while disposing components of bundle " + name,
e );
}
}
}
// Unloads all components registered with the SCR
private void disposeAllComponents()
{
for ( Iterator it = m_componentBundles.values().iterator(); it.hasNext(); )
{
BundleComponentActivator ga = ( BundleComponentActivator ) it.next();
try
{
ga.dispose();
}
catch ( Exception e )
{
log( LogService.LOG_ERROR, m_context.getBundle(), "Error while disposing components of bundle "
+ ga.getBundleContext().getBundle().getSymbolicName(), e );
}
it.remove();
}
}
/**
* Returns the <code>BundleContext</code> of the bundle.
* <p>
* This method assumes a <code>getContext</code> method returning a
* <code>BundleContext</code> instance to be present in the class of the
* bundle or any of its parent classes.
*
* @param bundle The <code>Bundle</code> whose context is to be returned.
*
* @return The <code>BundleContext</code> of the bundle or
* <code>null</code> if no <code>getContext</code> method
* returning a <code>BundleContext</code> can be found.
*/
private BundleContext getBundleContext( Bundle bundle )
{
try
{
return bundle.getBundleContext();
}
catch ( SecurityException se )
{
// assume we do not have the correct AdminPermission[*,CONTEXT]
// to call this, so we have to forward this exception
throw se;
}
catch ( Throwable t )
{
// ignore any other Throwable, most prominently NoSuchMethodError
// which is called in a pre-OSGI 4.1 environment
}
BundleContext context = null;
for ( Class clazz = bundle.getClass(); context == null && clazz != null; clazz = clazz.getSuperclass() )
{
try
{
context = getBundleContext( clazz, bundle, "getBundleContext" );
if ( context == null )
{
context = getBundleContext( clazz, bundle, "getContext" );
}
}
catch ( NoSuchMethodException nsme )
{
// don't actually care, just try super class
}
catch ( Throwable t )
{
log( LogService.LOG_ERROR, m_context.getBundle(), "Cannot get BundleContext for "
+ bundle.getSymbolicName(), t );
}
}
// return what we found
return context;
}
private BundleContext getBundleContext( Class clazz, Bundle bundle, String methodName )
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException
{
Method m = clazz.getDeclaredMethod( methodName, null );
if ( m.getReturnType().equals( BundleContext.class ) )
{
m.setAccessible( true );
return ( BundleContext ) m.invoke( bundle, null );
}
// method exists but has wrong return type
return null;
}
private static int getLogLevel( BundleContext bundleContext )
{
String levelString = bundleContext.getProperty( "ds.loglevel" );
if ( levelString != null )
{
try
{
return Integer.parseInt( levelString );
}
catch ( NumberFormatException nfe )
{
// might be a descriptive name
}
if ( "debug".equalsIgnoreCase( levelString ) )
{
return LogService.LOG_DEBUG;
}
else if ( "info".equalsIgnoreCase( levelString ) )
{
return LogService.LOG_INFO;
}
else if ( "warn".equalsIgnoreCase( levelString ) )
{
return LogService.LOG_WARNING;
}
else if ( "error".equalsIgnoreCase( levelString ) )
{
return LogService.LOG_ERROR;
}
}
// check ds.showtrace property
levelString = bundleContext.getProperty( "ds.trace" );
if ( "true".equalsIgnoreCase( bundleContext.getProperty( "ds.showtrace" ) ) )
{
return LogService.LOG_DEBUG;
}
// next check ds.showerrors property
if ( "false".equalsIgnoreCase( bundleContext.getProperty( "ds.showerrors" ) ) )
{
return -1; // no logging at all !!
}
// default log level (errors only)
return LogService.LOG_ERROR;
}
/**
* Method to actually emit the log message. If the LogService is available,
* the message will be logged through the LogService. Otherwise the message
* is logged to stdout (or stderr in case of LOG_ERROR level messages),
*
* @param level The log level to log the message at
* @param message The message to log
* @param ex An optional <code>Throwable</code> whose stack trace is written,
* or <code>null</code> to not log a stack trace.
*/
static void log( int level, Bundle bundle, String message, Throwable ex )
{
if ( m_logLevel >= level )
{
Object logger = ( m_logService != null ) ? m_logService.getService() : null;
if ( logger == null )
{
// output depending on level
PrintStream out = ( level == LogService.LOG_ERROR ) ? System.err : System.out;
// level as a string
StringBuffer buf = new StringBuffer();
switch ( level )
{
case ( LogService.LOG_DEBUG ):
buf.append( "DEBUG: " );
break;
case ( LogService.LOG_INFO ):
buf.append( "INFO : " );
break;
case ( LogService.LOG_WARNING ):
buf.append( "WARN : " );
break;
case ( LogService.LOG_ERROR ):
buf.append( "ERROR: " );
break;
default:
buf.append( "UNK : " );
break;
}
// bundle information
if ( bundle != null )
{
buf.append( bundle.getSymbolicName() );
buf.append( " (" );
buf.append( bundle.getBundleId() );
buf.append( "): " );
}
// the message
buf.append( message );
// keep the message and the stacktrace together
synchronized ( out)
{
out.println( buf );
if ( ex != null )
{
ex.printStackTrace( out );
}
}
}
else
{
( ( LogService ) logger ).log( level, message, ex );
}
}
}
}