blob: a57b5f27ccc603be2829535292c88f7009d797b0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2013 Sonatype, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Stuart McCulloch (Sonatype, Inc.) - initial API and implementation
*******************************************************************************/
package org.eclipse.sisu.launch;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import org.eclipse.sisu.inject.BindingPublisher;
import org.eclipse.sisu.inject.MutableBeanLocator;
import org.eclipse.sisu.inject.Weak;
import org.eclipse.sisu.space.BundleClassSpace;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkUtil;
import org.osgi.util.tracker.BundleTracker;
import com.google.inject.Guice;
import com.google.inject.Injector;
/**
* OSGi {@link BundleTracker} that tracks component bundles and uses {@link BundlePlan}s to publish them.
*/
public class SisuTracker
extends BundleTracker
{
// ----------------------------------------------------------------------
// Constants
// ----------------------------------------------------------------------
private static final Object PLACEHOLDER = new Object();
private static final Set<String> SUPPORT_BUNDLE_NAMES = new HashSet<String>();
static
{
final Class<?>[] supportTypes = { Inject.class, Guice.class, SisuExtender.class };
for ( final Class<?> type : supportTypes )
{
SUPPORT_BUNDLE_NAMES.add( FrameworkUtil.getBundle( type ).getSymbolicName() );
}
}
// ----------------------------------------------------------------------
// Implementation fields
// ----------------------------------------------------------------------
// attempt to track publishers across extender restarts
private static final Map<Long, Object> bundlePublishers =
Collections.synchronizedMap( Weak.<Long, Object> values() );
/**
* Mask of bundle states being tracked.
*/
protected final int stateMask;
/**
* Shared locator of bound components.
*/
protected final MutableBeanLocator locator;
/**
* Custom plans; contributed by attaching fragments to the extender bundle.
*/
protected final List<BundlePlan> plans;
// ----------------------------------------------------------------------
// Constructors
// ----------------------------------------------------------------------
public SisuTracker( final BundleContext context, final int stateMask, final MutableBeanLocator locator )
{
super( context, stateMask, null );
this.stateMask = stateMask;
this.locator = locator;
plans = discoverPlans();
}
// ----------------------------------------------------------------------
// Public methods
// ----------------------------------------------------------------------
@Override
public final void open()
{
super.open();
purgeBundles(); // catch-up with any events we've missed
}
@Override
public final Object addingBundle( final Bundle bundle, final BundleEvent event )
{
@SuppressWarnings( "boxing" )
final Long bundleId = bundle.getBundleId();
if ( !bundlePublishers.containsKey( bundleId ) )
{
final BundlePlan plan = selectPlan( bundle );
if ( null != plan )
{
// protect against repeated activation calls
bundlePublishers.put( bundleId, PLACEHOLDER );
final BindingPublisher publisher = plan.publish( bundle );
locator.add( publisher, publisher.maxBindingRank() );
bundlePublishers.put( bundleId, publisher );
}
}
return bundle;
}
@Override
public final void removedBundle( final Bundle bundle, final BundleEvent event, final Object object )
{
if ( evictBundle( bundle ) )
{
@SuppressWarnings( "boxing" )
final Object publisher = bundlePublishers.remove( bundle.getBundleId() );
if ( publisher instanceof BindingPublisher )
{
locator.remove( (BindingPublisher) publisher );
}
}
}
/**
* Purges any bundles that are no longer valid.
*/
public final void purgeBundles()
{
for ( final long bundleId : new ArrayList<Long>( bundlePublishers.keySet() ) )
{
final Bundle bundle = context.getBundle( bundleId );
if ( null == bundle || evictBundle( bundle ) )
{
@SuppressWarnings( "boxing" )
final Object publisher = bundlePublishers.remove( bundleId );
if ( publisher instanceof BindingPublisher )
{
locator.remove( (BindingPublisher) publisher );
}
}
}
}
// ----------------------------------------------------------------------
// Customizable methods
// ----------------------------------------------------------------------
/**
* Discovers plans listed locally under {@code META-INF/services/org.eclipse.sisu.launch.BundlePlan} ;
* implementations must have a public no-arg constructor or one that accepts a {@link MutableBeanLocator}.
*
* @return List of plans
*/
protected List<BundlePlan> discoverPlans()
{
final SisuExtensions extensions = SisuExtensions.local( new BundleClassSpace( context.getBundle() ) );
final List<BundlePlan> localPlans = extensions.create( BundlePlan.class, MutableBeanLocator.class, locator );
Collections.reverse( localPlans ); // TODO: support prioritized list?
localPlans.add( new SisuBundlePlan( locator ) );
return localPlans;
}
/**
* Selects the appropriate plan for the given bundle.
*
* @param bundle The bundle
* @return The chosen plan
*/
protected BundlePlan selectPlan( final Bundle bundle )
{
final String symbolicName = bundle.getSymbolicName();
if ( SUPPORT_BUNDLE_NAMES.contains( symbolicName ) )
{
return null; // ignore our main support bundles
}
if ( null != bundle.getHeaders().get( Constants.FRAGMENT_HOST ) )
{
return null; // fragment, we'll scan it when we process the host
}
for ( final BundlePlan plan : plans )
{
if ( plan.appliesTo( bundle ) )
{
return plan;
}
}
return null; // nothing to do
}
/**
* Determines whether we should destroy the {@link Injector} of the given bundle.
*
* @param bundle The bundle
* @return {@code true} if the injector should be destroyed; otherwise {@code false}
*/
protected boolean evictBundle( final Bundle bundle )
{
return ( bundle.getState() & stateMask ) == 0;
}
}