blob: 7cb5bceffef3cd6518c058846ae1e09dea976d4b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010-present 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.bean;
import java.util.ArrayList;
import com.google.inject.Binder;
import com.google.inject.Module;
import com.google.inject.Scopes;
import com.google.inject.matcher.Matcher;
import com.google.inject.matcher.Matchers;
import com.google.inject.spi.BindingScopingVisitor;
import com.google.inject.spi.DefaultBindingScopingVisitor;
/**
* Schedules safe activation of beans even when cyclic dependencies are involved.<br>
* Takes advantage of the new Guice ProvisionListener SPI, if available at runtime.
*/
public abstract class BeanScheduler
{
// ----------------------------------------------------------------------
// Static initialization
// ----------------------------------------------------------------------
static
{
Object cycleActivator;
Object candidateCycle = new Object();
Object cycleConfirmed = new Object();
try
{
// extra check in case we have both old and new versions of guice overlapping on the runtime classpath
Binder.class.getMethod( "bindListener", Matcher.class, com.google.inject.spi.ProvisionListener[].class );
// allow cycle detection to be turned off completely
final String detectCycles = System.getProperty( "sisu.detect.cycles" );
if ( "false".equalsIgnoreCase( detectCycles ) )
{
cycleActivator = null;
}
else
{
cycleActivator = new CycleActivator();
}
// support use of the old 'pessimistic' approach
if ( "pessimistic".equalsIgnoreCase( detectCycles ) )
{
candidateCycle = cycleConfirmed;
}
}
catch ( final Exception e )
{
cycleActivator = null;
}
catch ( final LinkageError e )
{
cycleActivator = null;
}
CYCLE_ACTIVATOR = cycleActivator;
CANDIDATE_CYCLE = candidateCycle;
CYCLE_CONFIRMED = cycleConfirmed;
}
// ----------------------------------------------------------------------
// Constants
// ----------------------------------------------------------------------
static final Object CYCLE_ACTIVATOR;
static final Object CANDIDATE_CYCLE;
static final Object CYCLE_CONFIRMED;
/**
* Enables deferred activation of component cycles, only needed in legacy systems like Plexus.
*/
public static final Module MODULE = new Module()
{
public void configure( final Binder binder )
{
if ( null != CYCLE_ACTIVATOR )
{
binder.bindListener( Matchers.any(), (com.google.inject.spi.ProvisionListener) CYCLE_ACTIVATOR );
}
}
};
// ----------------------------------------------------------------------
// Implementation fields
// ----------------------------------------------------------------------
static final ThreadLocal<Object[]> pendingHolder = new ThreadLocal<Object[]>();
// ----------------------------------------------------------------------
// Public methods
// ----------------------------------------------------------------------
/**
* Detects if a dependency cycle exists and activation needs to be deferred.
*/
public static void detectCycle( final Object value )
{
if ( null != CYCLE_ACTIVATOR && Scopes.isCircularProxy( value ) )
{
final Object[] holder = pendingHolder.get();
if ( null != holder )
{
final Object pending = holder[0];
if ( CANDIDATE_CYCLE.equals( pending ) )
{
holder[0] = CYCLE_CONFIRMED;
}
}
}
}
/**
* Schedules activation of the given bean at the next safe activation point.
*
* @param bean The managed bean
*/
public final void schedule( final Object bean )
{
if ( null != CYCLE_ACTIVATOR )
{
final Object[] holder = pendingHolder.get();
if ( null != holder )
{
final Object pending = holder[0];
if ( CYCLE_CONFIRMED.equals( pending ) )
{
holder[0] = new Pending( bean );
return; // will be activated later
}
else if ( pending instanceof Pending )
{
( (Pending) pending ).add( bean );
return; // will be activated later
}
}
}
activate( bean ); // no ProvisionListener, so activate immediately
}
// ----------------------------------------------------------------------
// Customizable methods
// ----------------------------------------------------------------------
/**
* Customized activation of the given bean.
*
* @param bean The bean to activate
*/
protected abstract void activate( final Object bean );
// ----------------------------------------------------------------------
// Implementation types
// ----------------------------------------------------------------------
/**
* Collects pending beans waiting for activation.
*/
@SuppressWarnings( "serial" )
private final class Pending
extends ArrayList<Object>
{
Pending( final Object bean )
{
add( bean );
}
/**
* Activates pending beans in order of registration, that is in the order they finished injection.
*/
public void activate()
{
for ( int i = 0, size = size(); i < size; i++ )
{
BeanScheduler.this.activate( get( i ) );
}
}
}
/**
* Listens to provisioning events in order to determine safe activation points.
*/
static final class CycleActivator
implements com.google.inject.spi.ProvisionListener
{
private static final BindingScopingVisitor<Boolean> IS_SCOPED = new DefaultBindingScopingVisitor<Boolean>()
{
@Override
public Boolean visitNoScoping()
{
return Boolean.FALSE;
}
@Override
protected Boolean visitOther()
{
return Boolean.TRUE;
}
};
public <T> void onProvision( final ProvisionInvocation<T> pi )
{
// Only scoped dependencies like singletons are candidates for dependency cycles
if ( Boolean.TRUE.equals( pi.getBinding().acceptScopingVisitor( IS_SCOPED ) ) )
{
Object[] holder = pendingHolder.get();
if ( null == holder )
{
pendingHolder.set( holder = new Object[1] );
}
if ( null == holder[0] )
{
final Object pending;
holder[0] = CANDIDATE_CYCLE;
try
{
pi.provision(); // may involve nested calls/cycles
}
finally
{
pending = holder[0];
holder[0] = null;
}
if ( pending instanceof Pending )
{
( (Pending) pending ).activate();
}
}
}
}
}
}