/*
* (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* 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.
*
* Contributors:
* bstefanescu
*/
package org.nuxeo.ecm.automation.jaxrs.io.operations;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.HashMap;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;
import org.apache.commons.io.IOUtils;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.nuxeo.ecm.automation.io.services.codec.ObjectCodecService;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.io.registry.MarshallingConstants;
import org.nuxeo.ecm.webengine.jaxrs.session.SessionFactory;
import org.nuxeo.runtime.api.Framework;
/**
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
@Provider
@Consumes({ "application/json", "application/json+nxrequest" })
public class JsonRequestReader implements MessageBodyReader<ExecutionRequest> {
@Context
private HttpServletRequest request;
@Context
JsonFactory factory;
public CoreSession getCoreSession() {
return SessionFactory.getSession(request);
}
public static final MediaType targetMediaTypeNXReq = new MediaType("application", "json+nxrequest");
public static final MediaType targetMediaType = new MediaType("application", "json");
protected static final HashMap<String, InputResolver<?>> inputResolvers = new HashMap<String, InputResolver<?>>();
static {
addInputResolver(new DocumentInputResolver());
addInputResolver(new DocumentsInputResolver());
addInputResolver(new BlobInputResolver());
addInputResolver(new BlobsInputResolver());
}
public static void addInputResolver(InputResolver<?> resolver) {
inputResolvers.put(resolver.getType(), resolver);
}
public static Object resolveInput(String input) throws IOException {
int p = input.indexOf(':');
if (p <= 0) {
// pass the String object directly
return input;
}
String type = input.substring(0, p);
String ref = input.substring(p + 1);
InputResolver<?> ir = inputResolvers.get(type);
if (ir != null) {
return ir.getInput(ref);
}
// no resolver found, pass the String object directly.
return input;
}
@Override
public boolean isReadable(Class<?> arg0, Type arg1, Annotation[] arg2, MediaType arg3) {
return ((targetMediaTypeNXReq.isCompatible(arg3) || targetMediaType.isCompatible(arg3)) && ExecutionRequest.class.isAssignableFrom(arg0));
}
@Override
public ExecutionRequest readFrom(Class<ExecutionRequest> arg0, Type arg1, Annotation[] arg2, MediaType arg3,
MultivaluedMap<String, String> headers, InputStream in) throws IOException, WebApplicationException {
return readRequest(in, headers, getCoreSession());
}
public ExecutionRequest readRequest(InputStream in, MultivaluedMap<String, String> headers, CoreSession session)
throws IOException, WebApplicationException {
// As stated in http://tools.ietf.org/html/rfc4627.html UTF-8 is the
// default encoding for JSON content
// TODO: add introspection on the first bytes to detect other admissible
// json encodings, namely: UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or
// LE)
String content = IOUtils.toString(in, "UTF-8");
if (content.isEmpty()) {
throw new WebApplicationException(Response.Status.BAD_REQUEST);
}
return readRequest(content, headers, session);
}
public ExecutionRequest readRequest(String content, MultivaluedMap<String, String> headers, CoreSession session)
throws WebApplicationException {
try {
return readRequest0(content, headers, session);
} catch (WebApplicationException e) {
throw e;
} catch (IOException e) {
throw new WebApplicationException(e);
}
}
public ExecutionRequest readRequest0(String content, MultivaluedMap<String, String> headers, CoreSession session)
throws IOException {
JsonParser jp = factory.createJsonParser(content);
return readRequest(jp, headers, session);
}
/**
* @param jp
* @param headers
* @param session
* @return
* @since TODO
*/
public static ExecutionRequest readRequest(JsonParser jp, MultivaluedMap<String, String> headers,
CoreSession session) throws IOException {
ExecutionRequest req = new ExecutionRequest();
ObjectCodecService codecService = Framework.getLocalService(ObjectCodecService.class);
jp.nextToken(); // skip {
JsonToken tok = jp.nextToken();
while (tok != null && tok != JsonToken.END_OBJECT) {
String key = jp.getCurrentName();
jp.nextToken();
if ("input".equals(key)) {
JsonNode inputNode = jp.readValueAsTree();
if (inputNode.isTextual()) {
// string values are expected to be micro-parsed with
// the "type:value" syntax for backward compatibility
// reasons.
req.setInput(resolveInput(inputNode.getTextValue()));
} else {
req.setInput(codecService.readNode(inputNode, session));
}
} else if ("params".equals(key)) {
readParams(jp, req, session);
} else if ("context".equals(key)) {
readContext(jp, req, session);
} else if ("documentProperties".equals(key)) {
// TODO XXX - this is wrong - headers are ready only! see with
// td
String documentProperties = jp.getText();
if (documentProperties != null) {
headers.putSingle(MarshallingConstants.EMBED_PROPERTIES, documentProperties);
}
}
tok = jp.nextToken();
}
if (tok == null) {
throw new IllegalArgumentException("Unexpected end of stream.");
}
return req;
}
private static void readParams(JsonParser jp, ExecutionRequest req, CoreSession session) throws IOException {
ObjectCodecService codecService = Framework.getLocalService(ObjectCodecService.class);
JsonToken tok = jp.nextToken(); // move to first entry
while (tok != null && tok != JsonToken.END_OBJECT) {
String key = jp.getCurrentName();
tok = jp.nextToken();
req.setParam(key, codecService.readNode(jp.readValueAsTree(), session));
tok = jp.nextToken();
}
if (tok == null) {
throw new IllegalArgumentException("Unexpected end of stream.");
}
}
private static void readContext(JsonParser jp, ExecutionRequest req, CoreSession session) throws IOException {
ObjectCodecService codecService = Framework.getLocalService(ObjectCodecService.class);
JsonToken tok = jp.nextToken(); // move to first entry
while (tok != null && tok != JsonToken.END_OBJECT) {
String key = jp.getCurrentName();
tok = jp.nextToken();
req.setContextParam(key, codecService.readNode(jp.readValueAsTree(), session));
tok = jp.nextToken();
}
if (tok == null) {
throw new IllegalArgumentException("Unexpected end of stream.");
}
}
}