Skip to content
Ruedi Steinmann edited this page Mar 28, 2015 · 3 revisions

Custom Scopes

Creating custom scopes is a multistep process:

  1. Define a scoping annotation
  2. Implementing the Scope interface
  3. Attaching the scope annotation to the implementation
  4. Triggering scope entry and exit

Defining a scoping annotation

The scoping annotation identifies your scope. You'll use it to annotate Salta-constructed types, @Provides methods, and in the in() clause of a bind statement. Copy-and-customize this code to define your scoping annotation:

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
import javax.inject.Scope;

@Target({ TYPE, METHOD }) @Retention(RUNTIME) @Scope
public @interface BatchScoped {}

Implementing Scope

To avoid the need to write bytecode generation code, we will implement a ScopeHandler and wrap it in a ScopeImpl to get a Scope. SimpleScopeHandler is a decent starting point for a per-thread scope implementation. Copy this class into your project and tweak it to suit your needs.

import static com.google.common.base.Preconditions.checkState;
import java.util.Map;
import java.util.function.Supplier;
import com.github.ruediste.salta.core.Binding;
import com.github.ruediste.salta.core.CoreDependencyKey;
import com.github.ruediste.salta.standard.ScopeImpl.ScopeHandler;
import com.google.common.collect.Maps;

/**
 * Scopes a single execution of a block of code. Apply this scope with a
 * try/finally block:
 * 
 * <pre>
 * <code>
 * 
 *   scopeHandler.enter();
 *   try {
 *     // create and access scoped objects
 *   } finally {
 *     scopeHandler.exit();
 *   }
 * </code>
 * </pre>
 *
 * Register it with
 * 
 * <pre>
 * <code>
 * bindScope(MyCustomScopeAnnotation.class, new ScopeImpl(scopeHandler));
 * </code>
 * </pre>
 * 
 * @author Jesse Wilson
 * @author Fedor Karpelevitch
 * @author Ruedi Steinmann
 */
public class SimpleScopeHandler implements ScopeHandler {

  private final ThreadLocal<Map<Binding, Object>> values = new ThreadLocal<>();

  public void enter() {
    checkState(values.get() == null,
        "A scoping block is already in progress");
    values.set(Maps.newHashMap());
  }

  public void exit() {
    checkState(values.get() != null, "No scoping block in progress");
    values.remove();
  }

  @Override
  public Supplier<Object> scope(Supplier<Object> supplier, Binding binding,
      CoreDependencyKey<?> requestedKey) {

    return () -> {
      Map<Binding, Object> scopedObjects = getScopedObjectMap(requestedKey);
      if (!scopedObjects.containsKey(binding)) {
        Object current = supplier.get();
        scopedObjects.put(binding, current);
        return current;
      } else
        return scopedObjects.get(binding);

    };
  }

  private Map<Binding, Object> getScopedObjectMap(
      CoreDependencyKey<?> requestedKey) {
    Map<Binding, Object> scopedObjects = values.get();
    if (scopedObjects == null) {
      throw new RuntimeException("Cannot access " + requestedKey
          + " outside of a scoping block");
    }
    return scopedObjects;
  }

}

Binding the annotation to the implementation

You must attach your scoping annotation to the corresponding scope implementation. Just like bindings, you can configure this in your module's configure() method. Usually you'll also bind the scope itself, so interceptors or filters can use it.

public class BatchScopeModule extends AbstractModule {
  public void configure() {
    SimpleScopeHandler handler = new SimpleScopeHandler();

    // tell Salta about the scope handler
    bindScope(BatchScoped.class, new ScopeImpl(handler));

    // make our scope instance injectable
    bind(SimpleScopeHandler.class)
      .named("batchScopeHandler")
      .toInstance(handler);
  }
}

Triggering the Scope

SimpleScopeHandler requires that you manually enter and exit the scope. Usually this lives in some low-level infrastructure code, like a filter or interceptor. Be sure to call exit() in a finally clause, otherwise the scope will be left open when an exception is thrown.

  @Inject @Named("batchScopeHandler") SimpleScopeHandler scopeHandler;

  /**
   * Runs {@code runnable} in batch scope.
   */
  public void scopeRunnable(Runnable runnable) {
    scopeHandler.enter();
    try {
      // create and access scoped objects
      runnable.run();

    } finally {
      scopeHandler.exit();
    }
  }

Clone this wiki locally