001/* 002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * bstefanescu 011 */ 012package org.nuxeo.ecm.automation.jaxrs.io.operations; 013 014import java.io.IOException; 015import java.io.InputStream; 016import java.lang.annotation.Annotation; 017import java.lang.reflect.Type; 018import java.util.HashMap; 019 020import javax.servlet.http.HttpServletRequest; 021import javax.ws.rs.Consumes; 022import javax.ws.rs.WebApplicationException; 023import javax.ws.rs.core.Context; 024import javax.ws.rs.core.MediaType; 025import javax.ws.rs.core.MultivaluedMap; 026import javax.ws.rs.core.Response; 027import javax.ws.rs.ext.MessageBodyReader; 028import javax.ws.rs.ext.Provider; 029 030import org.apache.commons.io.IOUtils; 031import org.codehaus.jackson.JsonFactory; 032import org.codehaus.jackson.JsonNode; 033import org.codehaus.jackson.JsonParser; 034import org.codehaus.jackson.JsonToken; 035import org.nuxeo.ecm.automation.io.services.codec.ObjectCodecService; 036import org.nuxeo.ecm.core.api.CoreSession; 037import org.nuxeo.ecm.core.io.registry.MarshallingConstants; 038import org.nuxeo.ecm.webengine.jaxrs.session.SessionFactory; 039import org.nuxeo.runtime.api.Framework; 040 041/** 042 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 043 */ 044@Provider 045@Consumes({ "application/json", "application/json+nxrequest" }) 046public class JsonRequestReader implements MessageBodyReader<ExecutionRequest> { 047 048 @Context 049 private HttpServletRequest request; 050 051 @Context 052 JsonFactory factory; 053 054 public CoreSession getCoreSession() { 055 return SessionFactory.getSession(request); 056 } 057 058 public static final MediaType targetMediaTypeNXReq = new MediaType("application", "json+nxrequest"); 059 060 public static final MediaType targetMediaType = new MediaType("application", "json"); 061 062 protected static final HashMap<String, InputResolver<?>> inputResolvers = new HashMap<String, InputResolver<?>>(); 063 064 static { 065 addInputResolver(new DocumentInputResolver()); 066 addInputResolver(new DocumentsInputResolver()); 067 } 068 069 public static void addInputResolver(InputResolver<?> resolver) { 070 inputResolvers.put(resolver.getType(), resolver); 071 } 072 073 public static Object resolveInput(String input) throws IOException { 074 int p = input.indexOf(':'); 075 if (p <= 0) { 076 // pass the String object directly 077 return input; 078 } 079 String type = input.substring(0, p); 080 String ref = input.substring(p + 1); 081 InputResolver<?> ir = inputResolvers.get(type); 082 if (ir != null) { 083 return ir.getInput(ref); 084 } 085 // no resolver found, pass the String object directly. 086 return input; 087 } 088 089 @Override 090 public boolean isReadable(Class<?> arg0, Type arg1, Annotation[] arg2, MediaType arg3) { 091 return ((targetMediaTypeNXReq.isCompatible(arg3) || targetMediaType.isCompatible(arg3)) && ExecutionRequest.class.isAssignableFrom(arg0)); 092 } 093 094 @Override 095 public ExecutionRequest readFrom(Class<ExecutionRequest> arg0, Type arg1, Annotation[] arg2, MediaType arg3, 096 MultivaluedMap<String, String> headers, InputStream in) throws IOException, WebApplicationException { 097 return readRequest(in, headers, getCoreSession()); 098 } 099 100 public ExecutionRequest readRequest(InputStream in, MultivaluedMap<String, String> headers, CoreSession session) 101 throws IOException, WebApplicationException { 102 // As stated in http://tools.ietf.org/html/rfc4627.html UTF-8 is the 103 // default encoding for JSON content 104 // TODO: add introspection on the first bytes to detect other admissible 105 // json encodings, namely: UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or 106 // LE) 107 String content = IOUtils.toString(in, "UTF-8"); 108 if (content.isEmpty()) { 109 throw new WebApplicationException(Response.Status.BAD_REQUEST); 110 } 111 return readRequest(content, headers, session); 112 } 113 114 public ExecutionRequest readRequest(String content, MultivaluedMap<String, String> headers, CoreSession session) 115 throws WebApplicationException { 116 try { 117 return readRequest0(content, headers, session); 118 } catch (WebApplicationException e) { 119 throw e; 120 } catch (IOException e) { 121 throw new WebApplicationException(e); 122 } 123 } 124 125 public ExecutionRequest readRequest0(String content, MultivaluedMap<String, String> headers, CoreSession session) 126 throws IOException { 127 128 JsonParser jp = factory.createJsonParser(content); 129 130 return readRequest(jp, headers, session); 131 } 132 133 /** 134 * @param jp 135 * @param headers 136 * @param session 137 * @return 138 * @since TODO 139 */ 140 public static ExecutionRequest readRequest(JsonParser jp, MultivaluedMap<String, String> headers, 141 CoreSession session) throws IOException { 142 ExecutionRequest req = new ExecutionRequest(); 143 144 ObjectCodecService codecService = Framework.getLocalService(ObjectCodecService.class); 145 jp.nextToken(); // skip { 146 JsonToken tok = jp.nextToken(); 147 while (tok != null && tok != JsonToken.END_OBJECT) { 148 String key = jp.getCurrentName(); 149 jp.nextToken(); 150 if ("input".equals(key)) { 151 JsonNode inputNode = jp.readValueAsTree(); 152 if (inputNode.isTextual()) { 153 // string values are expected to be micro-parsed with 154 // the "type:value" syntax for backward compatibility 155 // reasons. 156 req.setInput(resolveInput(inputNode.getTextValue())); 157 } else { 158 req.setInput(codecService.readNode(inputNode, session)); 159 } 160 } else if ("params".equals(key)) { 161 readParams(jp, req, session); 162 } else if ("context".equals(key)) { 163 readContext(jp, req, session); 164 } else if ("documentProperties".equals(key)) { 165 // TODO XXX - this is wrong - headers are ready only! see with 166 // td 167 String documentProperties = jp.getText(); 168 if (documentProperties != null) { 169 headers.putSingle(MarshallingConstants.EMBED_PROPERTIES, documentProperties); 170 } 171 } 172 tok = jp.nextToken(); 173 } 174 if (tok == null) { 175 throw new IllegalArgumentException("Unexpected end of stream."); 176 } 177 return req; 178 } 179 180 private static void readParams(JsonParser jp, ExecutionRequest req, CoreSession session) throws IOException { 181 ObjectCodecService codecService = Framework.getLocalService(ObjectCodecService.class); 182 JsonToken tok = jp.nextToken(); // move to first entry 183 while (tok != null && tok != JsonToken.END_OBJECT) { 184 String key = jp.getCurrentName(); 185 tok = jp.nextToken(); 186 req.setParam(key, codecService.readNode(jp.readValueAsTree(), session)); 187 tok = jp.nextToken(); 188 } 189 if (tok == null) { 190 throw new IllegalArgumentException("Unexpected end of stream."); 191 } 192 } 193 194 private static void readContext(JsonParser jp, ExecutionRequest req, CoreSession session) throws IOException { 195 ObjectCodecService codecService = Framework.getLocalService(ObjectCodecService.class); 196 JsonToken tok = jp.nextToken(); // move to first entry 197 while (tok != null && tok != JsonToken.END_OBJECT) { 198 String key = jp.getCurrentName(); 199 tok = jp.nextToken(); 200 req.setContextParam(key, codecService.readNode(jp.readValueAsTree(), session)); 201 tok = jp.nextToken(); 202 } 203 if (tok == null) { 204 throw new IllegalArgumentException("Unexpected end of stream."); 205 } 206 } 207 208}