001/* 002 * (C) Copyright 2013 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 * dmetzler 018 */ 019package org.nuxeo.ecm.automation.jaxrs.io.documents; 020 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.Serializable; 024import java.lang.annotation.Annotation; 025import java.lang.reflect.Type; 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.apache.commons.lang.StringUtils; 039import org.apache.commons.logging.Log; 040import org.apache.commons.logging.LogFactory; 041import org.codehaus.jackson.JsonFactory; 042import org.codehaus.jackson.JsonNode; 043import org.codehaus.jackson.JsonParser; 044import org.codehaus.jackson.JsonToken; 045import org.nuxeo.ecm.automation.core.util.DocumentHelper; 046import org.nuxeo.ecm.automation.core.util.Properties; 047import org.nuxeo.ecm.core.api.Blob; 048import org.nuxeo.ecm.core.api.CoreSession; 049import org.nuxeo.ecm.core.api.DocumentModel; 050import org.nuxeo.ecm.core.api.IdRef; 051import org.nuxeo.ecm.core.api.impl.DataModelImpl; 052import org.nuxeo.ecm.core.api.impl.SimpleDocumentModel; 053import org.nuxeo.ecm.core.api.model.PropertyNotFoundException; 054import org.nuxeo.ecm.core.api.model.impl.primitives.BlobProperty; 055import org.nuxeo.ecm.core.io.marshallers.json.document.DocumentModelJsonReader; 056import org.nuxeo.ecm.webengine.WebException; 057import org.nuxeo.ecm.webengine.jaxrs.coreiodelegate.DocumentModelJsonReaderLegacy; 058import org.nuxeo.ecm.webengine.jaxrs.coreiodelegate.JsonCoreIODelegate; 059import org.nuxeo.ecm.webengine.jaxrs.session.SessionFactory; 060 061/** 062 * JAX-RS reader for a DocumentModel. If an id is given, it tries to reattach the document to the session. If not, it 063 * creates a ready to create DocumentModel filled with the properties found. 064 * 065 * @since 5.7.2 066 * @deprecated since 7.10 The Nuxeo JSON marshalling was migrated to nuxeo-core-io. This class is replaced by 067 * {@link DocumentModelJsonReader} which is registered by default and available to marshal 068 * {@link DocumentModel} from the Nuxeo Rest API thanks to the JAX-RS marshaller {@link JsonCoreIODelegate} 069 * . On removal, need to remove also {@link DocumentModelJsonReaderLegacy} because it uses it using 070 * reflexion. 071 */ 072@Deprecated 073@Provider 074@Consumes({ "application/json+nxentity", "application/json" }) 075public class JSONDocumentModelReader implements MessageBodyReader<DocumentModel> { 076 077 // private static final String REQUEST_BATCH_ID = "batchId"; 078 079 protected static final Log log = LogFactory.getLog(JSONDocumentModelReader.class); 080 081 @Context 082 HttpServletRequest request; 083 084 @Context 085 JsonFactory factory; 086 087 @Override 088 public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { 089 return DocumentModel.class.isAssignableFrom(type); 090 } 091 092 @Override 093 public DocumentModel readFrom(Class<DocumentModel> type, Type genericType, Annotation[] annotations, 094 MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) 095 throws IOException, WebApplicationException { 096 String content = IOUtils.toString(entityStream); 097 if (content.isEmpty()) { 098 if (content.isEmpty()) { 099 throw new WebException("No content in request body", Response.Status.BAD_REQUEST.getStatusCode()); 100 } 101 102 } 103 104 try { 105 return readRequest(content, httpHeaders); 106 } catch (IOException e) { 107 throw WebException.wrap(e); 108 } 109 } 110 111 private DocumentModel readRequest(String content, MultivaluedMap<String, String> httpHeaders) throws IOException { 112 return readRequest(content, httpHeaders, request); 113 } 114 115 protected DocumentModel readRequest(String content, MultivaluedMap<String, String> httpHeaders, 116 HttpServletRequest request) throws IOException { 117 JsonParser jp = factory.createJsonParser(content); 118 return readJson(jp, httpHeaders, request); 119 } 120 121 public static DocumentModel readJson(JsonParser jp, MultivaluedMap<String, String> httpHeaders, 122 HttpServletRequest request) throws IOException { 123 JsonToken tok = jp.nextToken(); 124 125 // skip { 126 if (jp.getCurrentToken() == JsonToken.START_OBJECT) { 127 tok = jp.nextToken(); 128 } 129 SimpleDocumentModel simpleDoc = new SimpleDocumentModel(); 130 String type = null; 131 String name = null; 132 String uid = null; 133 while (tok != null && tok != JsonToken.END_OBJECT) { 134 String key = jp.getCurrentName(); 135 jp.nextToken(); 136 if ("properties".equals(key)) { 137 DocumentHelper.setJSONProperties(null, simpleDoc, readProperties(jp)); 138 } else if ("name".equals(key)) { 139 name = jp.readValueAs(String.class); 140 } else if ("type".equals(key)) { 141 type = jp.readValueAs(String.class); 142 } else if ("uid".equals(key)) { 143 uid = jp.readValueAs(String.class); 144 } else if ("entity-type".equals(key)) { 145 String entityType = jp.readValueAs(String.class); 146 if (!"document".equals(entityType)) { 147 throw new WebApplicationException(Response.Status.BAD_REQUEST); 148 } 149 } else { 150 log.debug("Unknown key: " + key); 151 jp.skipChildren(); 152 } 153 154 tok = jp.nextToken(); 155 } 156 157 if (tok == null) { 158 throw new IllegalArgumentException("Unexpected end of stream."); 159 } 160 161 if (StringUtils.isNotBlank(type)) { 162 simpleDoc.setType(type); 163 } 164 165 if (StringUtils.isNotBlank(name)) { 166 simpleDoc.setPathInfo(null, name); 167 } 168 169 // If a uid is specified, we try to get the doc from 170 // the core session 171 if (uid != null) { 172 CoreSession session = SessionFactory.getSession(request); 173 DocumentModel doc = session.getDocument(new IdRef(uid)); 174 applyPropertyValues(simpleDoc, doc); 175 return doc; 176 } else { 177 return simpleDoc; 178 } 179 180 } 181 182 static Properties readProperties(JsonParser jp) throws IOException { 183 JsonNode node = jp.readValueAsTree(); 184 return new Properties(node); 185 186 } 187 188 /** 189 * Decodes a Serializable to make it a blob. 190 * 191 * @since 5.9.1 192 */ 193 private static Serializable decodeBlob(Serializable data) { 194 if (data instanceof Blob) { 195 return data; 196 } else { 197 return null; 198 } 199 } 200 201 public static void applyPropertyValues(DocumentModel src, DocumentModel dst) { 202 for (String schema : src.getSchemas()) { 203 DataModelImpl dataModel = (DataModelImpl) dst.getDataModel(schema); 204 DataModelImpl fromDataModel = (DataModelImpl) src.getDataModel(schema); 205 206 for (String field : fromDataModel.getDirtyFields()) { 207 Serializable data = (Serializable) fromDataModel.getData(field); 208 try { 209 if (!(dataModel.getDocumentPart().get(field) instanceof BlobProperty)) { 210 dataModel.setData(field, data); 211 } else { 212 dataModel.setData(field, decodeBlob(data)); 213 } 214 // } 215 } catch (PropertyNotFoundException e) { 216 log.warn(String.format("Trying to deserialize unexistent field : {%s}", field)); 217 } 218 } 219 } 220 } 221 222}