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