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