| /******************************************************************************* |
| * 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(); |
| } |
| } |
| } |
| } |
| } |
| } |