/*
* 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.MimeType;
import juzu.PropertyType;
import juzu.Response;
import juzu.impl.common.Spliterator;
import juzu.impl.common.Tools;
import juzu.impl.inject.spi.InjectionContext;
import juzu.impl.value.ValueType;
import juzu.io.Streamable;
import juzu.request.ApplicationContext;
import juzu.request.ClientContext;
import juzu.request.HttpContext;
import juzu.request.Phase;
import juzu.request.RequestContext;
import juzu.request.RequestParameter;
import juzu.request.SecurityContext;
import juzu.request.UserContext;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* A stage in the request pipeline.
*
* @author Julien Viet
*/
public abstract class Stage {
/** . */
private int index = 0;
/** . */
final Request request;
/** . */
final List<RequestFilter<?>> filters;
public Request getRequest() {
return request;
}
public Stage(Request request) {
// Build the filter list
List<RequestFilter<?>> filters = new ArrayList<RequestFilter<?>>();
for (RequestFilter<?> filter : request.controllerPlugin.getFilters()) {
if (getClass().isAssignableFrom(filter.getStageType())) {
filters.add(filter);
}
}
//
this.request = request;
this.filters = filters;
}
public Response invoke() {
if (index >= 0 && index < filters.size()) {
RequestFilter plugin = filters.get(index);
try {
index++;
return (Response)plugin.handle(this);
}
finally {
index--;
}
}
else if (index == filters.size()) {
return response();
}
else {
throw new AssertionError();
}
}
protected abstract Response response();
/**
* This stage attempt to unmarshall the http entity when there is one.
*/
public static class Unmarshalling extends Stage {
public Unmarshalling(Request request) {
super(request);
}
@Override
protected Response response() {
//
Map<String, RequestParameter> parameterArguments = request.getParameterArguments();
parameterArguments.putAll(request.bridge.getRequestArguments());
//
Map<ContextualParameter,Object> contextualArguments = request.getContextualArguments();
for (ControlParameter controlParameter : request.handler.getParameters()) {
if (controlParameter instanceof ContextualParameter) {
ContextualParameter contextualParameter = (ContextualParameter)controlParameter;
if (!contextualArguments.containsKey(contextualParameter)) {
contextualArguments.put(contextualParameter, null);
}
}
}
contextualArguments.putAll(request.bridge.getContextualArguments(contextualArguments.keySet()));
//
ClientContext clientContext = request.bridge.getClientContext();
if (clientContext != null) {
String contentType = clientContext.getContentType();
if (contentType != null) {
Spliterator i = new Spliterator(contentType, ';');
//
String mediaType;
if (i.hasNext()) {
mediaType = i.next().trim();
//
if (!mediaType.equals("application/x-www-form-urlencoded")) {
for (EntityUnmarshaller reader : Tools.loadService(EntityUnmarshaller.class, request.controllerPlugin.getApplication().getClassLoader())) {
try {
if (reader.accept(mediaType)) {
reader.unmarshall(mediaType, clientContext, contextualArguments.entrySet(), parameterArguments);
break;
}
}
catch (IOException e) {
throw new UnsupportedOperationException("handle me gracefully", e);
}
}
}
}
}
}
//
return new Handler(request).invoke();
}
}
/**
* This stage prepares the handler for invocation, it determines the handler arguments and obtain the controller
* object from the injection context of the application.
*/
public static class Handler extends Stage {
public Handler(Request request) {
super(request);
}
public Response response() {
ControllerHandler<?> handler = request.getHandler();
InjectionContext<?, ?> manager = request.controllerPlugin.getInjectionContext();
//
Class<?> controllerType = handler.getType();
request.controllerLifeCycle = manager.get(controllerType);
if (request.controllerLifeCycle != null) {
// Create context
RequestContext context = new RequestContext(request, handler);
// Build arguments
Object[] args = new Object[handler.getParameters().size()];
for (int i = 0;i < args.length;i++) {
ControlParameter parameter = handler.getParameters().get(i);
Object value;
if (parameter instanceof PhaseParameter) {
PhaseParameter phaseParam = (PhaseParameter)parameter;
RequestParameter requestParam = request.getParameterArguments().get(phaseParam.getMappedName());
if (requestParam != null) {
ValueType<?> valueType = request.controllerPlugin.resolveValueType(phaseParam.getValueType());
if (valueType != null) {
List values = new ArrayList(requestParam.size());
for (String s : requestParam) {
Object converted;
try {
converted = valueType.parse(phaseParam.getAnnotations(), s);
}
catch (Exception e) {
return Response.error(e);
}
values.add(converted);
}
value = phaseParam.getValue(values);
} else {
value = null;
}
} else {
value = null;
}
Class<?> type = phaseParam.getType();
if (value == null && type.isPrimitive()) {
if (type == int.class) {
value = 0;
} else if (type == long.class) {
value = 0L;
} else if (type == byte.class) {
value = (byte)0;
} else if (type == short.class) {
value = (short)0;
} else if (type == boolean.class) {
value = false;
} else if (type == float.class) {
value = 0.0f;
} else if (type == double.class) {
value = 0.0d;
} else if (type == char.class) {
value = '\u0000';
}
}
} else if (parameter instanceof BeanParameter) {
BeanParameter beanParam = (BeanParameter)parameter;
Class<?> type = beanParam.getType();
try {
value = beanParam.createMappedBean(request.controllerPlugin, handler.requiresPrefix, type, beanParam.getName(), request.getParameterArguments());
}
catch (Exception e) {
value = null;
}
} else {
ContextualParameter contextualParameter = (ContextualParameter)parameter;
value = request.getContextualArguments().get(contextualParameter);
if (value == null) {
Class<?> contextualType = contextualParameter.getType();
if (RequestContext.class.isAssignableFrom(contextualType)) {
value = context;
} else if (HttpContext.class.isAssignableFrom(contextualType)) {
value = request.getHttpContext();
} else if (SecurityContext.class.isAssignableFrom(contextualType)) {
value = request.getSecurityContext();
} else if (ApplicationContext.class.isAssignableFrom(contextualType)) {
value = request.getApplicationContext();
} else if (UserContext.class.isAssignableFrom(contextualType)) {
value = request.getUserContext();
} else if (ClientContext.class.isAssignableFrom(contextualType) && (request.bridge.getPhase() == Phase.RESOURCE || request.bridge.getPhase() == Phase.ACTION)) {
value = request.getClientContext();
}
}
}
args[i] = value;
}
// Get controller
Object controller;
try {
controller = request.controllerLifeCycle.get();
}
catch (InvocationTargetException e) {
return Response.error(e.getCause());
}
//
Stage.LifeCycle lifeCycle = new LifeCycle(request, context, controller, args);
//
return lifeCycle.invoke();
} else {
// Handle that...
return null;
}
}
}
/**
* This stage takes care of triggering the controller life cycle when the controller implements the
* {@link juzu.request.RequestLifeCycle} interface.
*/
public static class LifeCycle extends Stage {
/** . */
private final RequestContext context;
/** . */
private final Object controller;
/** . */
private final Object[] args;
public LifeCycle(Request request, RequestContext context, Object controller, Object[] args) {
super(request);
this.context = context;
this.controller = controller;
this.args = args;
}
@Override
protected Response response() {
// Begin request callback
if (controller instanceof juzu.request.RequestLifeCycle) {
try {
((juzu.request.RequestLifeCycle)controller).beginRequest(context);
}
catch (Exception e) {
return new Response.Error(e);
}
}
//
Response response = context.getResponse();
if (response == null) {
Stage.Invoke invokeStage = new Invoke(request, context, controller, args);
response = invokeStage.invoke();
context.setResponse(response);
// End request callback
if (controller instanceof juzu.request.RequestLifeCycle) {
try {
((juzu.request.RequestLifeCycle)controller).endRequest(context);
}
catch (Exception e) {
context.setResponse(Response.error(e));
}
}
//
response = context.getResponse();
}
//
return response;
}
}
/**
* This stage invokes the handler on the controller with the specified arguments.
*/
public static class Invoke extends Stage {
/** . */
private final RequestContext context;
/** . */
private final Object controller;
/** . */
private final Object[] args;
public Invoke(Request request, RequestContext context, Object controller, Object[] args) {
super(request);
//
this.controller = controller;
this.context = context;
this.args = args;
}
public Object getController() {
return controller;
}
public Object[] getArguments() {
return args;
}
public Method getMethod() {
return context.getHandler().getMethod();
}
@Override
protected Response response() {
try {
Object ret = context.getHandler().getMethod().invoke(controller, args);
//
MimeType mimeType = null;
for (Annotation annotation : context.getHandler().getMethod().getDeclaredAnnotations()) {
if (annotation instanceof MimeType) {
mimeType = (MimeType)annotation;
} else {
mimeType = annotation.annotationType().getAnnotation(MimeType.class);
}
if (mimeType != null && mimeType.value().length > 0) {
// For now we stop but we should look at the accept types of the client
// for doing some basic content negociation
break;
}
}
//
if (ret instanceof Response) {
// We should check that it matches....
// btw we should try to enforce matching during compilation phase
// @Action -> Response.Action
// @View -> Response.Mime
// as we can do it
Response resp = (Response)ret;
if (mimeType != null) {
resp = resp.with(PropertyType.MIME_TYPE, mimeType.value()[0]);
}
return resp;
} else if (ret != null && mimeType != null) {
for (EntityMarshaller writer : Tools.loadService(EntityMarshaller.class, request.controllerPlugin.getApplication().getClassLoader())) {
for (String s : mimeType.value()) {
Streamable streamable = writer.marshall(s, context.getHandler().getMethod(), ret);
if (streamable != null) {
return Response.ok().with(PropertyType.MIME_TYPE, s).body(streamable);
}
}
}
}
return null;
}
catch (InvocationTargetException e) {
return Response.error(e.getCause());
}
catch (IllegalAccessException e) {
throw new UnsupportedOperationException("hanle me gracefully", e);
}
}
}
}