/* * Copyright 2008 Tim Peierls * * Licensed 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.directwebremoting.guice.util; import com.google.inject.Key; import com.google.inject.Provider; import com.google.inject.util.ToStringBuilder; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import static java.util.Collections.synchronizedList; import static org.directwebremoting.guice.util.AbstractContextScope.State.CLOSED; import static org.directwebremoting.guice.util.AbstractContextScope.State.OPEN; /** * Partial implementation of {@link ContextScope}. Concrete implementations * must pass the context identifier type to the super constructor and define * {@code get()} to return the current context identifier (and to return null * or throw an exception if there is no current context). They must also implement * the {@link ContextRegistry} interface. * @author Tim Peierls [tim at peierls dot net] */ public abstract class AbstractContextScope<C, R> implements ContextScope<C>, ContextRegistry<C, R> { protected AbstractContextScope(Class<C> type, String scopeName) { this.type = type; this.scopeName = scopeName; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return scopeName; } /* (non-Javadoc) * @see org.directwebremoting.guice.ContextScope#getKeysInScope() */ public List<Key<?>> getKeysInScope() { synchronized (scopedKeys) { return new ArrayList<Key<?>>(scopedKeys); } } /* (non-Javadoc) * @see org.directwebremoting.guice.ContextScope#scope(com.google.inject.Key, com.google.inject.Provider) */ public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) { scopedKeys.add(key); final String name = key.toString(); return new Provider<T>() { public T get() { C context = getContext(key); R registry = registryFor(context); InstanceProvider<T> future = AbstractContextScope.this.get(registry, key, name); if (future == null) { InstanceProvider<T> futureTask = new FutureTaskProvider<T>(creator); future = putIfAbsent(registry, key, name, futureTask); if (future == null) { future = futureTask; futureTask.run(); if (Thread.currentThread().isInterrupted()) { remove(registry, key, name, futureTask); } } } return future.get(); } @Override public String toString() { return new ToStringBuilder(this.getClass()) .add("scopeName", scopeName) .add("type", type) .add("key", key) .add("creator", creator) .toString(); } }; } public abstract C get(); /* (non-Javadoc) * @see org.directwebremoting.guice.ContextScope#type() */ public Class<C> type() { return type; } /* (non-Javadoc) * @see org.directwebremoting.guice.ContextScope#getOpenContexts() */ public Collection<C> getOpenContexts() { Collection<C> openContexts = new ArrayList<C>(); for (C context : contexts.keySet()) { if (contexts.get(context) == OPEN) { openContexts.add(context); } } return openContexts; } /* (non-Javadoc) * @see org.directwebremoting.guice.ContextScope#close(java.lang.Object, org.directwebremoting.guice.ContextCloseHandler<?>[]) */ public void close(C context, ContextCloseHandler<?>... closeHandlers) { if (!contexts.replace(context, OPEN, CLOSED)) { // Context hadn't been opened or was already closed. return; } for (InstanceProvider<?> provider : registeredProviders(registryFor(context))) { Object value = null; try { value = provider.get(); } catch (RuntimeException e) { // Ignore runtime exceptions: they were thrown when // attempting creation and mean that no object was // created. } if (value == null) { // No instance was created by this provider, so we ignore. continue; } for (ContextCloseHandler<?> closeHandler : closeHandlers) { handleClose(closeHandler, value); } } } /* (non-Javadoc) * @see org.directwebremoting.guice.ContextScope#closeAll(org.directwebremoting.guice.ContextCloseHandler<?>[]) */ public void closeAll(ContextCloseHandler<?>... closeHandlers) { for (C context : getOpenContexts()) { close(context, closeHandlers); } } private <T> void handleClose(ContextCloseHandler<T> closeHandler, Object value) { Class<T> closeType = closeHandler.type(); if (closeType.isInstance(value)) { try { closeHandler.close(closeType.cast(value)); } catch (Exception e) { // Ignore exceptions when closing, // the closeHandler should have taken // appropriate action before re-throwing. } } } protected C getContext(Key<?> key) { C context = null; RuntimeException caught = null; try { context = get(); if (contexts.putIfAbsent(context, OPEN) == CLOSED) { // Context is closed. context = null; } } catch (RuntimeException ex) { caught = ex; } if (context == null) { throw new OutOfScopeException(this, key, caught); } return context; } private Collection<InstanceProvider<?>> registeredProviders(R registry) { List<InstanceProvider<?>> providers = new ArrayList<InstanceProvider<?>>(); for (Key<?> key : getKeysInScope()) { InstanceProvider<?> provider = get(registry, key, key.toString()); if (provider != null) { providers.add(provider); } } return providers; } enum State { OPEN, CLOSED } protected final Class<C> type; protected final String scopeName; /* @GuardedBy("self") */ private final List<Key<?>> scopedKeys = synchronizedList(new ArrayList<Key<?>>()); private final ConcurrentMap<C, State> contexts = new ConcurrentHashMap<C, State>(); }