use of org.apache.tapestry5.ioc.services.PerThreadValue in project tapestry-5 by apache.
the class ParameterWorker method createComputedParameterConduit.
@SuppressWarnings("all")
private ComputedValue<FieldConduit<Object>> createComputedParameterConduit(final String parameterName, final String fieldTypeName, final Parameter annotation, final MethodHandle defaultMethodHandle) {
boolean primitive = PlasticUtils.isPrimitive(fieldTypeName);
final boolean allowNull = annotation.allowNull() && !primitive;
return new ComputedValue<FieldConduit<Object>>() {
public ParameterConduit get(InstanceContext context) {
final InternalComponentResources icr = context.get(InternalComponentResources.class);
final Class fieldType = classCache.forName(fieldTypeName);
final PerThreadValue<ParameterState> stateValue = perThreadManager.createValue();
return new ParameterConduit() {
// Default value for parameter, computed *once* at
// page load time.
private Object defaultValue = classCache.defaultValueForType(fieldTypeName);
private Binding parameterBinding;
boolean loaded = false;
private boolean invariant = false;
{
// Inform the ComponentResources about the parameter conduit, so it can be
// shared with mixins.
icr.setParameterConduit(parameterName, this);
icr.getPageLifecycleCallbackHub().addPageLoadedCallback(new Runnable() {
public void run() {
load();
}
});
}
private ParameterState getState() {
ParameterState state = stateValue.get();
if (state == null) {
state = new ParameterState();
state.value = defaultValue;
stateValue.set(state);
}
return state;
}
private boolean isLoaded() {
return loaded;
}
public void set(Object instance, InstanceContext context, Object newValue) {
ParameterState state = getState();
if (!loaded) {
state.value = newValue;
defaultValue = newValue;
return;
}
// This will catch read-only or unbound parameters.
writeToBinding(newValue);
state.value = newValue;
// If caching is enabled for the parameter (the typical case) and the
// component is currently rendering, then the result
// can be cached in this ParameterConduit (until the component finishes
// rendering).
state.cached = annotation.cache() && icr.isRendering();
}
private Object readFromBinding() {
Object result;
try {
Object boundValue = parameterBinding.get();
result = typeCoercer.coerce(boundValue, fieldType);
} catch (RuntimeException ex) {
throw new TapestryException(String.format("Failure reading parameter '%s' of component %s: %s", parameterName, icr.getCompleteId(), ExceptionUtils.toMessage(ex)), parameterBinding, ex);
}
if (result == null && !allowNull) {
throw new TapestryException(String.format("Parameter '%s' of component %s is bound to null. This parameter is not allowed to be null.", parameterName, icr.getCompleteId()), parameterBinding, null);
}
return result;
}
private void writeToBinding(Object newValue) {
if (parameterBinding == null) {
return;
}
try {
Object coerced = typeCoercer.coerce(newValue, parameterBinding.getBindingType());
parameterBinding.set(coerced);
} catch (RuntimeException ex) {
throw new TapestryException(String.format("Failure writing parameter '%s' of component %s: %s", parameterName, icr.getCompleteId(), ExceptionUtils.toMessage(ex)), icr, ex);
}
}
public void reset() {
if (!invariant) {
getState().reset(defaultValue);
}
}
public void load() {
if (logger.isDebugEnabled()) {
logger.debug("{} loading parameter {}", icr.getCompleteId(), parameterName);
}
if (!icr.isBound(parameterName)) {
if (logger.isDebugEnabled()) {
logger.debug("{} parameter {} not yet bound", icr.getCompleteId(), parameterName);
}
// Otherwise, construct a default binding, or use one provided from
// the component.
Binding binding = getDefaultBindingForParameter();
if (logger.isDebugEnabled()) {
logger.debug("{} parameter {} bound to default {}", icr.getCompleteId(), parameterName, binding);
}
if (binding != null) {
icr.bindParameter(parameterName, binding);
}
}
parameterBinding = icr.getBinding(parameterName);
loaded = true;
invariant = parameterBinding != null && parameterBinding.isInvariant();
getState().value = defaultValue;
}
public boolean isBound() {
return parameterBinding != null;
}
public Object get(Object instance, InstanceContext context) {
if (!isLoaded()) {
return defaultValue;
}
ParameterState state = getState();
if (state.cached || !isBound()) {
return state.value;
}
// Read the parameter's binding and cast it to the
// field's type.
Object result = readFromBinding();
if (invariant || (annotation.cache() && icr.isRendering())) {
state.value = result;
state.cached = true;
}
return result;
}
private Binding getDefaultBindingForParameter() {
if (InternalUtils.isNonBlank(annotation.value())) {
return bindingSource.newBinding("default " + parameterName, icr, annotation.defaultPrefix(), annotation.value());
}
if (annotation.autoconnect()) {
return defaultProvider.defaultBinding(parameterName, icr);
}
// Invoke the default method and install any value or Binding returned there.
invokeDefaultMethod();
return parameterBinding;
}
private void invokeDefaultMethod() {
if (defaultMethodHandle == null) {
return;
}
if (logger.isDebugEnabled()) {
logger.debug("{} invoking method {} to obtain default for parameter {}", icr.getCompleteId(), defaultMethodHandle, parameterName);
}
MethodInvocationResult result = defaultMethodHandle.invoke(icr.getComponent());
result.rethrow();
Object defaultValue = result.getReturnValue();
if (defaultValue == null) {
return;
}
if (defaultValue instanceof Binding) {
parameterBinding = (Binding) defaultValue;
return;
}
parameterBinding = new LiteralBinding(null, "default " + parameterName, defaultValue);
}
};
}
};
}
use of org.apache.tapestry5.ioc.services.PerThreadValue in project tapestry-5 by apache.
the class TapestryModule method provideTransformWorkers.
/**
* Adds a number of standard component class transform workers:
* <dl>
* <dt>Parameter</dt>
* <dd>Identifies parameters based on the {@link org.apache.tapestry5.annotations.Parameter} annotation</dd>
* <dt>BindParameter</dt>
* <dd>Support for the {@link BindParameter} annotation</dd>
* <dt>Property</dt>
* <dd>Generates accessor methods if {@link org.apache.tapestry5.annotations.Property} annotation is present</dd>
* <dt>Import</dt>
* <dd>Supports the {@link Import} annotation</dd>
* <dt>UnclaimedField</dt>
* <dd>Manages unclaimed fields, storing their value in a {@link PerThreadValue}</dd>
* <dt>OnEvent</dt>
* <dd>Handle the @OnEvent annotation, and related naming convention</dd>
* <dt>RenderCommand</dt>
* <dd>Ensures all components also implement {@link org.apache.tapestry5.runtime.RenderCommand}</dd>
* <dt>SupportsInformalParameters</dt>
* <dd>Checks for the annotation</dd>
* <dt>RenderPhase</dt>
* <dd>Link in render phase methods</dd>
* <dt>Retain</dt>
* <dd>Allows fields to retain their values between requests</dd>
* <dt>Meta</dt>
* <dd>Checks for meta data annotations and adds it to the component model</dd>
* <dt>PageActivationContext</dt> <dd>Support for {@link PageActivationContext} annotation</dd>
* <dt>DiscardAfter</dt> <dd>Support for {@link DiscardAfter} method annotation </dd>
* <dt>MixinAfter</dt> <dd>Support for the {@link MixinAfter} mixin class annotation</dd>
* <dt>PageReset</dt>
* <dd>Checks for the {@link PageReset} annotation</dd>
* <dt>Mixin</dt>
* <dd>Adds a mixin as part of a component's implementation</dd>
* <dt>Cached</dt>
* <dd>Checks for the {@link org.apache.tapestry5.annotations.Cached} annotation</dd>
* <dt>ActivationRequestParameter</dt>
* <dd>Support for the {@link ActivationRequestParameter} annotation</dd>
* <dt>PageLoaded, PageAttached, PageDetached</dt>
* <dd>Support for annotations {@link PageLoaded}, {@link PageAttached}, {@link PageDetached}</dd>
* <dt>InjectService</dt>
* <dd>Handles the {@link org.apache.tapestry5.ioc.annotations.InjectService} annotation</dd>
* <dt>Component</dt>
* <dd>Defines embedded components based on the {@link org.apache.tapestry5.annotations.Component} annotation</dd>
* <dt>Environment</dt>
* <dd>Allows fields to contain values extracted from the {@link org.apache.tapestry5.services.Environment} service</dd>
* <dt>ApplicationState</dt>
* <dd>Converts fields that reference application state objects</dd>
* <dt>Persist</dt>
* <dd>Allows fields to store their their value persistently between requests via {@link Persist}</dd>
* <dt>SessionAttribute</dt>
* <dd>Support for the {@link SessionAttribute}</dd>
* <dt>Log</dt>
* <dd>Checks for the {@link org.apache.tapestry5.annotations.Log} annotation</dd>
* <dt>HeartbeatDeferred
* <dd>Support for the {@link HeartbeatDeferred} annotation, which defers method invocation to the end of the {@link Heartbeat}
* <dt>Inject</dt>
* <dd>Used with the {@link org.apache.tapestry5.ioc.annotations.Inject} annotation, when a value is supplied</dd>
* <dt>Operation</dt> <dd>Support for the {@link Operation} method annotation</dd>
* </dl>
*/
@Contribute(ComponentClassTransformWorker2.class)
@Primary
public static void provideTransformWorkers(OrderedConfiguration<ComponentClassTransformWorker2> configuration, MetaWorker metaWorker, ComponentClassResolver resolver) {
configuration.add("Property", new PropertyWorker());
// Order this one pretty early:
configuration.addInstance("Operation", OperationWorker.class);
configuration.add("RenderCommand", new RenderCommandWorker());
configuration.addInstance("OnEvent", OnEventWorker.class);
configuration.add("MixinAfter", new MixinAfterWorker());
// These must come after Property, since they actually delete fields
// that may still have the annotation
configuration.addInstance("ApplicationState", ApplicationStateWorker.class);
configuration.addInstance("Environment", EnvironmentalWorker.class);
configuration.add("Component", new ComponentWorker(resolver));
configuration.add("Mixin", new MixinWorker(resolver));
configuration.addInstance("InjectPage", InjectPageWorker.class);
configuration.addInstance("InjectComponent", InjectComponentWorker.class);
configuration.addInstance("InjectContainer", InjectContainerWorker.class);
// Default values for parameters are often some form of injection, so
// make sure that Parameter fields are processed after injections.
configuration.addInstance("Parameter", ParameterWorker.class);
// bind parameter should always go after parameter to make sure all
// parameters have been properly setup.
configuration.addInstance("BindParameter", BindParameterWorker.class);
configuration.add("SupportsInformalParameters", new SupportsInformalParametersWorker());
configuration.addInstance("RenderPhase", RenderPhaseMethodWorker.class);
// Import advises methods, usually render phase methods, so it must come after RenderPhase.
configuration.addInstance("Import", ImportWorker.class);
configuration.add("Meta", metaWorker.getWorker());
configuration.add("Retain", new RetainWorker());
configuration.add("PageActivationContext", new PageActivationContextWorker());
configuration.addInstance("ActivationRequestParameter", ActivationRequestParameterWorker.class);
configuration.addInstance("Cached", CachedWorker.class);
configuration.addInstance("DiscardAfter", DiscardAfterWorker.class);
add(configuration, PageLoaded.class, TransformConstants.CONTAINING_PAGE_DID_LOAD_DESCRIPTION);
add(configuration, PageAttached.class, TransformConstants.CONTAINING_PAGE_DID_ATTACH_DESCRIPTION);
add(configuration, PageDetached.class, TransformConstants.CONTAINING_PAGE_DID_DETACH_DESCRIPTION);
configuration.addInstance("PageReset", PageResetAnnotationWorker.class);
configuration.addInstance("InjectService", InjectServiceWorker.class);
configuration.addInstance("Inject", InjectWorker.class);
configuration.addInstance("Persist", PersistWorker.class);
configuration.addInstance("SessionAttribute", SessionAttributeWorker.class);
configuration.addInstance("Log", LogWorker.class);
configuration.addInstance("HeartbeatDeferred", HeartbeatDeferredWorker.class);
// This one is always last. Any additional private fields that aren't
// annotated will
// be converted to clear out at the end of the request.
configuration.addInstance("UnclaimedField", UnclaimedFieldWorker.class, "after:*");
}
use of org.apache.tapestry5.ioc.services.PerThreadValue in project tapestry-5 by apache.
the class CachedWorker method adviseMethod.
private void adviseMethod(PlasticClass plasticClass, PlasticMethod method) {
// Every instance of the clas srequires its own per-thread value. This handles the case of multiple
// pages containing the component, or the same page containing the component multiple times.
PlasticField cacheField = plasticClass.introduceField(PerThreadValue.class, "cache$" + method.getDescription().methodName);
cacheField.injectComputed(new ComputedValue<PerThreadValue>() {
public PerThreadValue get(InstanceContext context) {
// Each instance will get a new PerThreadValue
return perThreadManager.createValue();
}
});
Cached annotation = method.getAnnotation(Cached.class);
MethodResultCacheFactory factory = createFactory(plasticClass, annotation.watch(), method);
MethodAdvice advice = createAdvice(cacheField, factory);
method.addAdvice(advice);
}
Aggregations