com.sun.faces.mock.MockApplication Maven / Gradle / Ivy
/*
* $Id: MockApplication.java,v 1.1 2005/10/18 17:47:51 edburns Exp $
*/
/*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* https://javaserverfaces.dev.java.net/CDDL.html or
* legal/CDDLv1.0.txt.
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at legal/CDDLv1.0.txt.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* [Name of File] [ver.__] [Date]
*
* Copyright 2005 Sun Microsystems Inc. All Rights Reserved
*/
package com.sun.faces.mock;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.el.ELContextListener;
import javax.el.ELException;
import javax.el.ELResolver;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.application.*;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.el.MethodBinding;
import javax.faces.el.PropertyResolver;
import javax.faces.el.ValueBinding;
import javax.faces.el.VariableResolver;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;
import javax.faces.event.SystemEvent;
import javax.faces.event.SystemEventListener;
import javax.faces.event.SystemEventListenerHolder;
import javax.faces.validator.Validator;
import javax.servlet.ServletContext;
import com.sun.el.ExpressionFactoryImpl;
public class MockApplication extends Application {
private static final Logger LOGGER = Logger.getLogger("MockApplication");
private final SystemEventHelper systemEventHelper = new SystemEventHelper();
private final ComponentSystemEventHelper compSysEventHelper = new ComponentSystemEventHelper();
public MockApplication() {
addComponent("TestNamingContainer",
"javax.faces.webapp.TestNamingContainer");
addComponent("TestComponent", "javax.faces.webapp.TestComponent");
addComponent("TestInput", "javax.faces.component.UIInput");
addComponent("TestOutput", "javax.faces.component.UIOutput");
addConverter("Integer", "javax.faces.convert.IntegerConverter");
addConverter("javax.faces.Number",
"javax.faces.convert.NumberConverter");
addConverter("javax.faces.Long",
"javax.faces.convert.LongConverter");
addValidator("Length", "javax.faces.validator.LengthValidator");
servletContext = new MockServletContext();
}
private ServletContext servletContext = null;
private ActionListener actionListener = null;
private static boolean processActionCalled = false;
public ActionListener getActionListener() {
if (null == actionListener) {
actionListener = new ActionListener() {
public void processAction(ActionEvent e) {
processActionCalled = true;
}
// see if the other object is the same as our
// anonymous inner class implementation.
public boolean equals(Object otherObj) {
if (!(otherObj instanceof ActionListener)) {
return false;
}
ActionListener other = (ActionListener) otherObj;
processActionCalled = false;
other.processAction(null);
boolean result = processActionCalled;
processActionCalled = false;
return result;
}
};
}
return (this.actionListener);
}
public void setActionListener(ActionListener actionListener) {
this.actionListener = actionListener;
}
private NavigationHandler navigationHandler = null;
public NavigationHandler getNavigationHandler() {
return (this.navigationHandler);
}
public void setNavigationHandler(NavigationHandler navigationHandler) {
this.navigationHandler = navigationHandler;
}
private ResourceHandler resourceHandler = new MockResourceHandler();
@Override
public ResourceHandler getResourceHandler() {
return resourceHandler;
}
@Override
public void setResourceHandler(ResourceHandler resourceHandler) {
this.resourceHandler = resourceHandler;
}
private PropertyResolver propertyResolver = null;
public PropertyResolver getPropertyResolver() {
if (propertyResolver == null) {
propertyResolver = new MockPropertyResolver();
}
return (this.propertyResolver);
}
public void setPropertyResolver(PropertyResolver propertyResolver) {
this.propertyResolver = propertyResolver;
}
public MethodBinding createMethodBinding(String ref, Class params[]) {
if (ref == null) {
throw new NullPointerException();
} else {
return (new MockMethodBinding(this, ref, params));
}
}
public ValueBinding createValueBinding(String ref) {
if (ref == null) {
throw new NullPointerException();
} else {
return (new MockValueBinding(this, ref));
}
}
// PENDING(edburns): implement
public void addELResolver(ELResolver resolver) {
}
// PENDING(edburns): implement
public ELResolver getELResolver() {
return null;
}
private ExpressionFactory expressionFactory = null;
public ExpressionFactory getExpressionFactory() {
if (null == expressionFactory) {
expressionFactory = new ExpressionFactoryImpl();
}
return expressionFactory;
}
public Object evaluateExpressionGet(FacesContext context,
String expression,
Class expectedType) throws ELException{
ValueExpression ve = getExpressionFactory().createValueExpression(context.getELContext(),expression, expectedType);
return ve.getValue(context.getELContext());
}
private VariableResolver variableResolver = null;
public VariableResolver getVariableResolver() {
if (variableResolver == null) {
variableResolver = new MockVariableResolver();
}
return (this.variableResolver);
}
public void setVariableResolver(VariableResolver variableResolver) {
this.variableResolver = variableResolver;
}
private ViewHandler viewHandler = null;
public ViewHandler getViewHandler() {
if (null == viewHandler) {
viewHandler = new MockViewHandler();
}
return (this.viewHandler);
}
public void setViewHandler(ViewHandler viewHandler) {
this.viewHandler = viewHandler;
}
private StateManager stateManager = null;
public StateManager getStateManager() {
if (null == stateManager) {
stateManager = new MockStateManager();
}
return (this.stateManager);
}
public void setStateManager(StateManager stateManager) {
this.stateManager = stateManager;
}
private Map components = new HashMap();
public void addComponent(String componentType, String componentClass) {
components.put(componentType, componentClass);
}
public UIComponent createComponent(String componentType) {
String componentClass = (String) components.get(componentType);
try {
Class clazz = Class.forName(componentClass);
return ((UIComponent) clazz.newInstance());
} catch (Exception e) {
throw new FacesException(e);
}
}
public UIComponent createComponent(ValueBinding componentBinding,
FacesContext context,
String componentType)
throws FacesException {
throw new FacesException(new UnsupportedOperationException());
}
public UIComponent createComponent(ValueExpression componentExpression,
FacesContext context,
String componentType)
throws FacesException {
throw new FacesException(new UnsupportedOperationException());
}
public Iterator getComponentTypes() {
return (components.keySet().iterator());
}
private Map converters = new HashMap();
public void addConverter(String converterId, String converterClass) {
converters.put(converterId, converterClass);
}
public void addConverter(Class targetClass, String converterClass) {
throw new UnsupportedOperationException();
}
public Converter createConverter(String converterId) {
String converterClass = (String) converters.get(converterId);
try {
Class clazz = Class.forName(converterClass);
return ((Converter) clazz.newInstance());
} catch (Exception e) {
throw new FacesException(e);
}
}
public Converter createConverter(Class targetClass) {
throw new UnsupportedOperationException();
}
public Iterator getConverterIds() {
return (converters.keySet().iterator());
}
public Iterator getConverterTypes() {
throw new UnsupportedOperationException();
}
private String messageBundle = null;
public void setMessageBundle(String messageBundle) {
this.messageBundle = messageBundle;
}
public String getMessageBundle() {
return messageBundle;
}
private Map validators = new HashMap();
public void addValidator(String validatorId, String validatorClass) {
validators.put(validatorId, validatorClass);
}
public Validator createValidator(String validatorId) {
String validatorClass = (String) validators.get(validatorId);
try {
Class clazz = Class.forName(validatorClass);
return ((Validator) clazz.newInstance());
} catch (Exception e) {
throw new FacesException(e);
}
}
public Iterator getValidatorIds() {
return (validators.keySet().iterator());
}
public Iterator getSupportedLocales() {
return Collections.EMPTY_LIST.iterator();
}
public void setSupportedLocales(Collection newLocales) {
}
public void addELContextListener(ELContextListener listener) {
// PENDING(edburns): maybe implement
}
public void removeELContextListener(ELContextListener listener) {
// PENDING(edburns): maybe implement
}
public ELContextListener [] getELContextListeners() {
// PENDING(edburns): maybe implement
return (ELContextListener []) java.lang.reflect.Array.newInstance(ELContextListener.class,
0);
}
public Locale getDefaultLocale(){
return Locale.getDefault();
}
public void setDefaultLocale(Locale newLocale) {
}
public String getDefaultRenderKitId() {
return null;
}
public void setDefaultRenderKitId(String renderKitId) {
}
public ResourceBundle getResourceBundle(FacesContext ctx, String name) {
return null;
}
/**
* If there are one or more listeners
* for events of the type represented by
* systemEventClass
, call those listeners, passing
* source
as the source of the event. The
* implementation should be as fast as possible in determining
* whether or not a listener for the given
* systemEventClass
and source
has been
* installed, and should return immediately once such a
* determination has been made. The implementation of
* publishEvent
must honor the requirements stated in
* {@link #subscribeToEvent} regarding the storage and retrieval of
* listener instances.
*
*
*
* The default implementation must implement an algorithm
* semantically equivalent to the following to locate listener
* instances and to invoke them.
*
*
*
* If the source
argument implements {@link
* javax.faces.event.SystemEventListenerHolder}, call {@link
* javax.faces.event.SystemEventListenerHolder#getListenersForEventClass}
* on it, passing the systemEventClass
argument. If
* the list is not empty, perform algorithm
* traverseListenerList on the list.
*
* If any Application
level listeners have
* been installed by previous calls to {@link
* #subscribeToEvent(Class, Class,
* javax.faces.event.SystemEventListener)}, perform algorithm
* traverseListenerList on the list.
*
* If any Application
level listeners have
* been installed by previous calls to {@link
* #subscribeToEvent(Class, javax.faces.event.SystemEventListener)},
* perform algorithm traverseListenerList on the
* list.
*
*
*
* If the act of invoking the processListener
method
* causes an {@link javax.faces.event.AbortProcessingException} to
* be thrown, processing of the listeners must be aborted.
*
* RELEASE_PENDING (edburns,rogerk) it may be prudent to specify how the
* abortprocessingexception should be handled. Logged or thrown?
*
* Algorithm traverseListenerList: For each listener in
* the list,
*
*
*
* Call {@link
* javax.faces.event.SystemEventListener#isListenerForSource}, passing the
* source
argument. If this returns
* false
, take no action on the listener.
*
* Otherwise, if the event to be passed to the listener
* instances has not yet been constructed, construct the event,
* passing source
as the argument to the
* one-argument constructor that takes an Object
.
* This same event instance must be passed to all listener
* instances.
*
* Call {@link javax.faces.event.SystemEvent#isAppropriateListener},
* passing the listener instance as the argument. If this
* returns false
, take no action on the
* listener.
*
* Call {@link javax.faces.event.SystemEvent#processListener},
* passing the listener instance.
*
*
*
*
* @param systemEventClass The Class
of event that is
* being published.
* @param source The source for the event of type
* systemEventClass
.
*
* @throws NullPointerException if either systemEventClass
or
* source
is null
*
* @since 2.0
*/
public void publishEvent(FacesContext context,
Class extends SystemEvent> systemEventClass,
Object source) {
if (systemEventClass == null) {
throw new NullPointerException("systemEventClass");
}
if (source == null) {
throw new NullPointerException("source");
}
try {
// The side-effect of calling invokeListenersFor
// will create a SystemEvent object appropriate to event/source
// combination. This event will be passed on subsequent invocations
// of invokeListenersFor
SystemEvent event;
// Look for and invoke any listeners stored on the source instance.
event = invokeComponentListenersFor(systemEventClass, source);
// look for and invoke any listeners stored on the application
// using source type.
event = invokeListenersFor(systemEventClass, event, source, true);
// look for and invoke any listeners not specific to the source class
invokeListenersFor(systemEventClass, event, source, false);
} catch (AbortProcessingException ape) {
if (LOGGER.isLoggable(Level.SEVERE)) {
LOGGER.log(Level.SEVERE,
ape.getMessage(),
ape);
}
}
}
/**
* Install the listener instance
* referenced by argument listener
into the
* application as a listener for events of type
* systemEventClass
that originate from objects of type
* sourceClass
.
*
*
*
* If argument sourceClass
is non-null
,
* sourceClass
and systemEventClass
must be
* used to store the argument listener
in the application in
* such a way that the listener
can be quickly looked
* up by the implementation of {@link javax.faces.application.Application#publishEvent} given
* systemEventClass
and an instance of the
* Class
referenced by sourceClass
. If
* argument sourceClass
is null
, the
* listener
must be discoverable by the implementation
* of {@link javax.faces.application.Application#publishEvent} given only systemEventClass
.
*
*
*
*
* @param systemEventClass the Class
of event for which
* listener
must be fired.
*
* @param sourceClass the Class
of the instance which
* causes events of type systemEventClass
to be fired.
* May be null
.
*
* @param listener the implementation of {@link
* javax.faces.event.SystemEventListener} whose {@link
* javax.faces.event.SystemEventListener#processEvent} method must be called when
* events of type systemEventClass
are fired.
*
* @throws NullPointerException
if any combination of
* systemEventClass
, or listener
are
* null
.
*
* @since 2.0
*/
public void subscribeToEvent(Class extends SystemEvent> systemEventClass,
Class> sourceClass,
SystemEventListener listener) {
if (systemEventClass == null) {
throw new NullPointerException("systemEventClass");
}
if (listener == null) {
throw new NullPointerException("listener");
}
Set listeners =
getListeners(systemEventClass, sourceClass);
listeners.add(listener);
}
/**
* Install the listener instance
* referenced by argument listener
into application
* as a listener for events of type
* systemEventClass
. The default implementation simply calls
* through to {@link #subscribeToEvent(Class, Class, javax.faces.event.SystemEventListener)} passing null
as the sourceClass
argument
*
* @param systemEventClass the Class
of event for which
* listener
must be fired.
*
* @param listener the implementation of {@link
* javax.faces.event.SystemEventListener} whose {@link
* javax.faces.event.SystemEventListener#processEvent} method must be called when
* events of type systemEventClass
are fired.
*
* @throws NullPointerException
if any combination of
* systemEventClass
, or listener
are
* null
.
*
* @since 2.0
*/
public void subscribeToEvent(Class extends SystemEvent> systemEventClass,
SystemEventListener listener) {
subscribeToEvent(systemEventClass, null, listener);
}
/**
* Remove the listener instance
* referenced by argument listener
from the application
* as a listener for events of type
* systemEventClass
that originate from objects of type
* sourceClass
. See {@link
* #subscribeToEvent(Class, Class,
* javax.faces.event.SystemEventListener)} for the specification
* of how the listener is stored, and therefore, how it must be
* removed.
*
* @param systemEventClass the Class
of event for which
* listener
must be fired.
*
* @param sourceClass the Class
of the instance which
* causes events of type systemEventClass
to be fired.
* May be null
.
*
* @param listener the implementation of {@link
* javax.faces.event.SystemEventListener} to remove from the internal data
* structure.
*
* @throws NullPointerException
if any combination of
* context
,
* systemEventClass
, or listener
are
* null
.
*/
public void unsubscribeFromEvent(Class extends SystemEvent> systemEventClass,
Class> sourceClass,
SystemEventListener listener) {
if (systemEventClass == null) {
throw new NullPointerException("systemEventClass");
}
if (listener == null) {
throw new NullPointerException("listener");
}
Set listeners =
getListeners(systemEventClass, sourceClass);
if (listeners != null) {
listeners.remove(listener);
}
}
/**
* Remove the listener instance
* referenced by argument listener
from the application
* as a listener for events of type systemEventClass
. The
* default implementation simply calls through to {@link #unsubscribeFromEvent(Class, javax.faces.event.SystemEventListener)} passing null
as the sourceClass
argument
*
* @param systemEventClass the Class
of event for which
* listener
must be fired.
*
* @param listener the implementation of {@link
* javax.faces.event.SystemEventListener} to remove from the internal data
* structure.
*
* @throws NullPointerException
if any combination of
* context
, systemEventClass
, or
* listener
are
* null
.
*/
public void unsubscribeFromEvent(Class extends SystemEvent> systemEventClass,
SystemEventListener listener) {
unsubscribeFromEvent(systemEventClass, null, listener);
}
/**
* @return the SystemEventListeners that should be used for the
* provided combination of SystemEvent and source.
*/
private Set getListeners(Class extends SystemEvent> systemEvent,
Class> sourceClass) {
Set listeners = null;
EventInfo sourceInfo =
systemEventHelper.getEventInfo(systemEvent, sourceClass);
if (sourceInfo != null) {
listeners = sourceInfo.getListeners();
}
return listeners;
}
/**
* @return process any listeners for the specified SystemEventListenerHolder
* and return any SystemEvent that may have been created as a side-effect
* of processing the listeners.
*/
private SystemEvent invokeComponentListenersFor(Class extends SystemEvent> systemEventClass,
Object source) {
if (source instanceof SystemEventListenerHolder) {
EventInfo eventInfo =
compSysEventHelper.getEventInfo(systemEventClass,
source.getClass());
return processListeners(((SystemEventListenerHolder) source).getListenersForEventClass(systemEventClass),
null,
source,
eventInfo);
}
return null;
}
/**
* Traverse the List
of listeners and invoke any that are relevent
* for the specified source.
*
* @throws javax.faces.event.AbortProcessingException propagated from the listener invocation
*/
private SystemEvent invokeListenersFor(Class extends SystemEvent> systemEventClass,
SystemEvent event,
Object source,
boolean useSourceLookup)
throws AbortProcessingException {
EventInfo eventInfo = systemEventHelper.getEventInfo(systemEventClass,
source,
useSourceLookup);
if (eventInfo != null) {
Set listeners = eventInfo.getListeners();
event = processListeners(listeners, event, source, eventInfo);
}
return event;
}
/**
* Iterate through and invoke the listeners. If the passed event was
* null
, create the event, and return it.
*/
private SystemEvent processListeners(Collection listeners,
SystemEvent event,
Object source,
EventInfo eventInfo) {
if (listeners != null && !listeners.isEmpty()) {
for (SystemEventListener curListener : listeners) {
if (curListener.isListenerForSource(source)) {
if (event == null) {
event = eventInfo.createSystemEvent(source);
}
assert (event != null);
if (event.isAppropriateListener(curListener)) {
event.processListener(curListener);
}
}
}
}
return event;
}
/**
* Utility class for dealing with application events.
*/
private static class SystemEventHelper {
private final Cache, SystemEventInfo> systemEventInfoCache;
// -------------------------------------------------------- Constructors
public SystemEventHelper() {
systemEventInfoCache =
new Cache, SystemEventInfo>(
new Factory, SystemEventInfo>() {
public SystemEventInfo newInstance(final Class extends SystemEvent> arg)
throws InterruptedException {
return new SystemEventInfo(arg);
}
}
);
}
// ------------------------------------------------------ Public Methods
public EventInfo getEventInfo(Class extends SystemEvent> systemEventClass,
Class> sourceClass) {
EventInfo info = null;
SystemEventInfo systemEventInfo = systemEventInfoCache.get(systemEventClass);
if (systemEventInfo != null) {
info = systemEventInfo.getEventInfo(sourceClass);
}
return info;
}
public EventInfo getEventInfo(Class extends SystemEvent> systemEventClass,
Object source,
boolean useSourceForLookup) {
Class> sourceClass =
((useSourceForLookup) ? source.getClass() : Void.class);
return getEventInfo(systemEventClass, sourceClass);
}
} // END SystemEventHelper
/**
* Utility class for dealing with {@link javax.faces.component.UIComponent} events.
*/
private static class ComponentSystemEventHelper {
private Cache,Cache,EventInfo>> sourceCache;
// -------------------------------------------------------- Constructors
public ComponentSystemEventHelper() {
// Initialize the 'sources' cache for, ahem, readability...
// ~generics++
Factory, Cache, EventInfo>> eventCacheFactory =
new Factory, Cache, EventInfo>>() {
public Cache, EventInfo> newInstance(
final Class> sourceClass)
throws InterruptedException {
Factory, EventInfo> eventInfoFactory =
new Factory, EventInfo>() {
public EventInfo newInstance(final Class extends SystemEvent> systemEventClass)
throws InterruptedException {
return new EventInfo(systemEventClass, sourceClass);
}
};
return new Cache, EventInfo>(eventInfoFactory);
}
};
sourceCache = new Cache,Cache,EventInfo>>(eventCacheFactory);
}
// ------------------------------------------------------ Public Methods
public EventInfo getEventInfo(Class extends SystemEvent> systemEvent,
Class> sourceClass) {
Cache, EventInfo> eventsCache =
sourceCache.get(sourceClass);
return eventsCache.get(systemEvent);
}
} // END ComponentSystemEventHelper
/**
* Simple wrapper class for application level SystemEvents. It provides the
* structure to map a single SystemEvent to multiple sources which are
* represented by SourceInfo
instances.
*/
private static class SystemEventInfo {
private Cache,EventInfo> cache = new Cache,EventInfo>(
new Factory, EventInfo>() {
public EventInfo newInstance(Class> arg)
throws InterruptedException {
return new EventInfo(systemEvent, arg);
}
}
);
private Class extends SystemEvent> systemEvent;
// -------------------------------------------------------- Constructors
private SystemEventInfo(Class extends SystemEvent> systemEvent) {
this.systemEvent = systemEvent;
}
// ------------------------------------------------------ Public Methods
public EventInfo getEventInfo(Class> source) {
Class> sourceClass = ((source == null) ? Void.class : source);
return cache.get(sourceClass);
}
} // END SystemEventInfo
/**
* Represent a logical association between a SystemEvent and a Source.
* This call will contain the Listeners specific to this association
* as well as provide a method to construct new SystemEvents as required.
*/
private static class EventInfo {
private Class extends SystemEvent> systemEvent;
private Class> sourceClass;
private Set listeners;
private Constructor eventConstructor;
private Map,Constructor> constructorMap;
// -------------------------------------------------------- Constructors
public EventInfo(Class extends SystemEvent> systemEvent,
Class> sourceClass) {
this.systemEvent = systemEvent;
this.sourceClass = sourceClass;
this.listeners = new CopyOnWriteArraySet();
this.constructorMap = new HashMap,Constructor>();
if (!sourceClass.equals(Void.class)) {
eventConstructor = getEventConstructor(sourceClass);
}
}
// ------------------------------------------------------ Public Methods
public Set getListeners() {
return listeners;
}
public SystemEvent createSystemEvent(Object source) {
Constructor toInvoke = getCachedConstructor(source.getClass());
if (toInvoke != null) {
try {
return (SystemEvent) toInvoke.newInstance(source);
} catch (Exception e) {
throw new FacesException(e);
}
}
return null;
}
// ----------------------------------------------------- Private Methods
private Constructor getCachedConstructor(Class> source) {
if (eventConstructor != null) {
return eventConstructor;
} else {
Constructor c = constructorMap.get(source);
if (c == null) {
c = getEventConstructor(source);
if (c != null) {
constructorMap.put(source, c);
}
}
return c;
}
}
private Constructor getEventConstructor(Class> source) {
Constructor ctor = null;
try {
return systemEvent.getDeclaredConstructor(source);
} catch (NoSuchMethodException ignored) {
Constructor[] ctors = systemEvent.getConstructors();
if (ctors != null) {
for (Constructor c : ctors) {
Class>[] params = c.getParameterTypes();
if (params.length != 1) {
continue;
}
if (params[0].isAssignableFrom(source)) {
return c;
}
}
}
if (eventConstructor == null) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE,
"Unable to find Constructor within {0} that accepts {1} instances.",
new Object[] { systemEvent.getName(), sourceClass.getName() });
}
}
}
return ctor;
}
} // END SourceInfo
/**
* Factory interface for creating various cacheable objects.
*/
private interface Factory {
V newInstance(final K arg) throws InterruptedException;
} // END Factory
/**
* A concurrent caching mechanism.
*/
private static final class Cache {
private ConcurrentMap> cache =
new ConcurrentHashMap>();
private Factory factory;
// -------------------------------------------------------- Constructors
/**
* Constructs this cache using the specified Factory
.
* @param factory
*/
public Cache(Factory factory) {
this.factory = factory;
}
// ------------------------------------------------------ Public Methods
/**
* If a value isn't associated with the specified key, a new
* {@link java.util.concurrent.Callable} will be created wrapping the Factory
* specified via the constructor and passed to a {@link java.util.concurrent.FutureTask}. This task
* will be passed to the backing ConcurrentMap. When {@link java.util.concurrent.FutureTask#get()}
* is invoked, the Factory will return the new Value which will be cached
* by the {@link java.util.concurrent.FutureTask}.
*
* @param key the key the value is associated with
* @return the value for the specified key, if any
*/
public V get(final K key) {
while (true) {
Future f = cache.get(key);
if (f == null) {
Callable callable = new Callable() {
public V call() throws Exception {
return factory.newInstance(key);
}
};
FutureTask ft = new FutureTask(callable);
// here is the real beauty of the concurrent utilities.
// 1. putIfAbsent() is atomic
// 2. putIfAbsent() will return the value already associated
// with the specified key
// So, if multiple threads make it to this point
// they will all be calling f.get() on the same
// FutureTask instance, so this guarantees that the instances
// that the invoked Callable will return will be created once
f = cache.putIfAbsent(key, ft);
if (f == null) {
f = ft;
ft.run();
}
}
try {
return f.get();
} catch (CancellationException ce) {
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.log(Level.FINEST,
ce.toString(),
ce);
}
cache.remove(key);
} catch (InterruptedException ie) {
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.log(Level.FINEST,
ie.toString(),
ie);
}
cache.remove(key);
} catch (ExecutionException ee) {
throw new FacesException(ee);
}
}
}
} // END Cache
}