/*
* Copyright 2013 eXo Platform SAS
*
* 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 juzu.impl.request;
import juzu.Response;
import juzu.Scope;
import juzu.asset.AssetLocation;
import juzu.impl.bridge.Parameters;
import juzu.impl.bridge.spi.DispatchBridge;
import juzu.impl.bridge.spi.ScopedContext;
import juzu.impl.common.RunMode;
import juzu.impl.inject.ScopeController;
import juzu.impl.inject.Scoped;
import juzu.impl.inject.ScopingContext;
import juzu.impl.inject.spi.BeanLifeCycle;
import juzu.impl.bridge.spi.RequestBridge;
import juzu.impl.plugin.application.Application;
import juzu.impl.plugin.controller.ControllerService;
import juzu.impl.plugin.controller.descriptor.ControllersDescriptor;
import juzu.impl.value.ValueType;
import juzu.io.UndeclaredIOException;
import juzu.request.ApplicationContext;
import juzu.request.ClientContext;
import juzu.request.Dispatch;
import juzu.request.HttpContext;
import juzu.request.Phase;
import juzu.request.RequestParameter;
import juzu.request.SecurityContext;
import juzu.request.UserContext;
import java.io.IOException;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
/** @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> */
public class Request implements ScopingContext {
/** . */
private static final Object[] EMPTY = new Object[0];
public static Request getCurrent() {
ContextLifeCycle context = current.get();
return context != null ? context.getRequest() : null;
}
/** The unique static thread local we should use. */
static final ThreadLocal<ContextLifeCycle> current = new ThreadLocal<ContextLifeCycle>();
/** . */
final LinkedHashSet<ContextLifeCycle> contextLifeCycles = new LinkedHashSet<ContextLifeCycle>();
/** The controller for this request. */
BeanLifeCycle controllerLifeCycle = null;
/** . */
final RequestBridge bridge;
/** . */
final ControllerService controllerPlugin;
/** . */
final ControllerHandler<?> handler;
/** . */
private Map<String, RequestParameter> parameterArguments;
/** . */
private Map<ContextualParameter, Object> contextualArguments;
public Request(
ControllerService controllerPlugin,
ControllerHandler handler,
RequestBridge bridge) {
//
this.bridge = bridge;
this.controllerPlugin = controllerPlugin;
this.handler = handler;
this.parameterArguments = new HashMap<String, RequestParameter>();
this.contextualArguments = new HashMap<ContextualParameter, Object>();
}
public Map<String, RequestParameter> getParameterArguments() {
return parameterArguments;
}
public Map<ContextualParameter, Object> getContextualArguments() {
return contextualArguments;
}
public RunMode getRunMode() {
return bridge.getRunMode();
}
public Application getApplication() {
return controllerPlugin.getApplication();
}
public ClientContext getClientContext() {
return bridge.getClientContext();
}
public HttpContext getHttpContext() {
return getBridge().getHttpContext();
}
public SecurityContext getSecurityContext() {
return getBridge().getSecurityContext();
}
public UserContext getUserContext() {
return getBridge().getUserContext();
}
public ApplicationContext getApplicationContext() {
return getBridge().getApplicationContext();
}
public ControllerHandler<?> getHandler() {
return handler;
}
public Phase getPhase() {
return handler.getPhase();
}
public ScopeController getScopeController() {
return controllerPlugin.getInjectionContext().getScopeController();
}
public RequestBridge getBridge() {
return bridge;
}
public final Scoped getContextualValue(Scope scope, Object key) {
ScopedContext context = bridge.getScopedContext(scope, false);
return context != null ? context.get(key) : null;
}
public final void setContextualValue(Scope scope, Object key, Scoped value) {
if (value == null) {
ScopedContext context = bridge.getScopedContext(scope, false);
if (context != null) {
context.set(key, null);
}
}
else {
bridge.getScopedContext(scope, true).set(key, value);
}
}
public boolean isActive(Scope scope) {
return true;
}
/** The main contextual for this request. */
private ContextLifeCycle contextLifeCycle;
public Response invoke() {
boolean set = current.get() == null;
try {
//
if (set) {
current.set(contextLifeCycle = new ContextLifeCycle(this));
getScopeController().begin(this);
}
// Create first stage
Stage stage = new Stage.Unmarshalling(this);
// Dispatch request
return stage.invoke();
}
finally {
if (set) {
contextLifeCycle.endContextual();
current.set(null);
}
}
}
public Executor getExecutor() {
final Iterable<ExecutionFilter> filters = controllerPlugin.getInjectionContext().resolveInstances(ExecutionFilter.class);
return new Executor() {
public void execute(Runnable command) {
for (ExecutionFilter filter : filters) {
command = filter.onCommand(command);
}
Request.this.execute(command);
}
};
}
private void execute(final Runnable runnable) throws RejectedExecutionException {
// Create a new context - we add it here to keep a reference
final ContextLifeCycle contextLifeCycle = new ContextLifeCycle(this);
contextLifeCycles.add(contextLifeCycle);
// Our wrapper for cleanup
Runnable wrapper = new Runnable() {
public void run() {
try {
getScopeController().begin(Request.this);
current.set(contextLifeCycle);
runnable.run();
}
finally {
current.set(null);
contextLifeCycle.endContextual();
}
}
};
// In some case execute cannot honour the execution and we should do the cleanup
// otherwise it will never occur
boolean executed = false;
try {
bridge.execute(wrapper);
executed = true;
}
finally {
if (!executed) {
contextLifeCycle.endContextual();
}
}
}
public ContextLifeCycle suspend() {
//
ContextLifeCycle lifeCycle = current.get();
if (lifeCycle == null) {
throw new IllegalStateException("No current active request");
} else if (lifeCycle.getRequest() != this) {
throw new IllegalStateException("Current request is not active");
}
//
current.set(null);
//
return lifeCycle;
}
private Dispatch createDispatch(ControllerHandler<?> handler, DispatchBridge spi) {
ControllersDescriptor desc = controllerPlugin.getDescriptor();
Dispatch dispatch;
if (handler.getPhase() == Phase.ACTION) {
dispatch = new Phase.Action.Dispatch(spi);
} else if (handler.getPhase() == Phase.VIEW) {
dispatch = new Phase.View.Dispatch(spi);
dispatch.escapeXML(desc.getEscapeXML());
} else if (handler.getPhase() == Phase.RESOURCE) {
dispatch = new Phase.Resource.Dispatch(spi);
dispatch.escapeXML(desc.getEscapeXML());
} else {
throw new AssertionError();
}
dispatch.escapeXML(desc.getEscapeXML());
return dispatch;
}
private String valueOf(AnnotatedElement annotated, Object o) {
ValueType vt = controllerPlugin.resolveValueType(o.getClass());
if (vt != null) {
return vt.format(annotated, o);
} else {
return null;
}
}
private void setArgs(Object[] args, Parameters parameterMap, final ControllerHandler<?> handler) {
int index = 0;
for (ControlParameter parameter : handler.getParameters()) {
if (parameter instanceof PhaseParameter) {
PhaseParameter phaseParameter = (PhaseParameter)parameter;
final int at = index++;
Object value = args[at];
if (value != null) {
String name = phaseParameter.getMappedName();
switch (phaseParameter.getCardinality()) {
case SINGLE: {
parameterMap.setParameter(name, valueOf(phaseParameter.getAnnotations(), value));
break;
}
case ARRAY: {
int length = Array.getLength(value);
String[] array = new String[length];
for (int i = 0;i < length;i++) {
Object component = Array.get(value, i);
array[i] = valueOf(phaseParameter.getAnnotations(), component);
}
parameterMap.setParameter(name, array);
break;
}
case LIST: {
Collection<?> c = (Collection<?>)value;
int length = c.size();
String[] array = new String[length];
Iterator<?> iterator = c.iterator();
for (int i = 0;i < length;i++) {
Object element = iterator.next();
array[i] = valueOf(phaseParameter.getAnnotations(), element);
}
parameterMap.setParameter(name, array);
break;
}
default:
throw new UnsupportedOperationException("Not yet implemented");
}
}
} else if (parameter instanceof BeanParameter) {
BeanParameter beanParameter = (BeanParameter)parameter;
Object value = args[index++];
Map<String, String[]> p = beanParameter.buildBeanParameter(controllerPlugin, handler.requiresPrefix, beanParameter.getName(), value);
parameterMap.setParameters(p);
}
}
}
public Dispatch createDispatch(ControllerHandler<?> handler, Object[] args) {
Parameters parameters = new Parameters();
setArgs(args, parameters, handler);
DispatchBridge spi = getBridge().createDispatch(handler.getPhase(), handler.getHandle(), parameters);
return createDispatch(handler, spi);
}
public Dispatch createDispatch(ControllerHandler<?> handler) {
DispatchBridge spi = getBridge().createDispatch(handler.getPhase(), handler.getHandle(), new Parameters());
return createDispatch(handler, spi);
}
private static Dispatch safeCreateDispatch(ControllerHandler<?> handler, Object[] args) {
ContextLifeCycle context = current.get();
if (context != null) {
return context.getRequest().createDispatch(handler, args);
} else {
// Should we output some warning ?
return null;
}
}
public static Phase.Action.Dispatch createActionDispatch(ControllerHandler<Phase.Action> handler) {
return (Phase.Action.Dispatch)safeCreateDispatch(handler, EMPTY);
}
public static Phase.Action.Dispatch createActionDispatch(ControllerHandler<Phase.Action> handler, Object arg) {
return (Phase.Action.Dispatch)safeCreateDispatch(handler, new Object[]{arg});
}
public static Phase.Action.Dispatch createActionDispatch(ControllerHandler<Phase.Action> handler, Object[] args) {
return (Phase.Action.Dispatch)safeCreateDispatch(handler, args);
}
public static Phase.View.Dispatch createViewDispatch(ControllerHandler<Phase.View> handler) {
return (Phase.View.Dispatch)safeCreateDispatch(handler, EMPTY);
}
public static Phase.View.Dispatch createViewDispatch(ControllerHandler<Phase.View> handler, Object arg) {
return (Phase.View.Dispatch)safeCreateDispatch(handler, new Object[]{arg});
}
public static Phase.View.Dispatch createViewDispatch(ControllerHandler<Phase.View> handler, Object[] args) {
return (Phase.View.Dispatch)safeCreateDispatch(handler, args);
}
public static Phase.Resource.Dispatch createResourceDispatch(ControllerHandler<Phase.Resource> handler) {
return (Phase.Resource.Dispatch)safeCreateDispatch(handler, EMPTY);
}
public static Phase.Resource.Dispatch createResourceDispatch(ControllerHandler<Phase.Resource> handler, Object arg) {
return (Phase.Resource.Dispatch)safeCreateDispatch(handler, new Object[]{arg});
}
public static Phase.Resource.Dispatch createResourceDispatch(ControllerHandler<Phase.Resource> handler, Object[] args) {
return (Phase.Resource.Dispatch)safeCreateDispatch(handler, args);
}
public void renderAssetURL(AssetLocation location, String uri, Appendable appendable) throws NullPointerException, UnsupportedOperationException, UndeclaredIOException {
try {
getBridge().renderAssetURL(location, uri, appendable);
}
catch (IOException e) {
throw new UndeclaredIOException(e);
}
}
}