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.webengine.WebException; 054import org.nuxeo.ecm.webengine.jaxrs.session.SessionFactory; 055 056/** 057 * JAX-RS reader for a DocumentModel. If an id is given, it tries to reattach the document to the session. If not, it 058 * creates a ready to create DocumentModel filled with the properties found. 059 * 060 * @since 5.7.2 061 */ 062@Provider 063@Consumes({ "application/json+nxentity", "application/json" }) 064public class JSONDocumentModelReader implements MessageBodyReader<DocumentModel> { 065 066 // private static final String REQUEST_BATCH_ID = "batchId"; 067 068 protected static final Log log = LogFactory.getLog(JSONDocumentModelReader.class); 069 070 @Context 071 HttpServletRequest request; 072 073 @Context 074 JsonFactory factory; 075 076 @Override 077 public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { 078 return DocumentModel.class.isAssignableFrom(type); 079 } 080 081 @Override 082 public DocumentModel readFrom(Class<DocumentModel> type, Type genericType, Annotation[] annotations, 083 MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) 084 throws IOException, WebApplicationException { 085 String content = IOUtils.toString(entityStream); 086 if (content.isEmpty()) { 087 if (content.isEmpty()) { 088 throw new WebException("No content in request body", Response.Status.BAD_REQUEST.getStatusCode()); 089 } 090 091 } 092 093 try { 094 return readRequest(content, httpHeaders); 095 } catch (IOException e) { 096 throw WebException.wrap(e); 097 } 098 } 099 100 private DocumentModel readRequest(String content, MultivaluedMap<String, String> httpHeaders) throws IOException { 101 return readRequest(content, httpHeaders, request); 102 } 103 104 protected DocumentModel readRequest(String content, MultivaluedMap<String, String> httpHeaders, 105 HttpServletRequest request) throws IOException { 106 JsonParser jp = factory.createJsonParser(content); 107 return readJson(jp, httpHeaders, request); 108 } 109 110 public static DocumentModel readJson(JsonParser jp, MultivaluedMap<String, String> httpHeaders, 111 HttpServletRequest request) throws IOException { 112 JsonToken tok = jp.nextToken(); 113 114 // skip { 115 if (jp.getCurrentToken() == JsonToken.START_OBJECT) { 116 tok = jp.nextToken(); 117 } 118 SimpleDocumentModel simpleDoc = new SimpleDocumentModel(); 119 String type = null; 120 String name = null; 121 String uid = null; 122 while (tok != null && tok != JsonToken.END_OBJECT) { 123 String key = jp.getCurrentName(); 124 jp.nextToken(); 125 if ("properties".equals(key)) { 126 DocumentHelper.setJSONProperties(null, simpleDoc, readProperties(jp)); 127 } else if ("name".equals(key)) { 128 name = jp.readValueAs(String.class); 129 } else if ("type".equals(key)) { 130 type = jp.readValueAs(String.class); 131 } else if ("uid".equals(key)) { 132 uid = jp.readValueAs(String.class); 133 } else if ("entity-type".equals(key)) { 134 String entityType = jp.readValueAs(String.class); 135 if (!"document".equals(entityType)) { 136 throw new WebApplicationException(Response.Status.BAD_REQUEST); 137 } 138 } else { 139 log.debug("Unknown key: " + key); 140 jp.skipChildren(); 141 } 142 143 tok = jp.nextToken(); 144 } 145 146 if (tok == null) { 147 throw new IllegalArgumentException("Unexpected end of stream."); 148 } 149 150 if (StringUtils.isNotBlank(type)) { 151 simpleDoc.setType(type); 152 } 153 154 if (StringUtils.isNotBlank(name)) { 155 simpleDoc.setPathInfo(null, name); 156 } 157 158 // If a uid is specified, we try to get the doc from 159 // the core session 160 if (uid != null) { 161 CoreSession session = SessionFactory.getSession(request); 162 DocumentModel doc = session.getDocument(new IdRef(uid)); 163 applyPropertyValues(simpleDoc, doc); 164 return doc; 165 } else { 166 return simpleDoc; 167 } 168 169 } 170 171 static Properties readProperties(JsonParser jp) throws IOException { 172 JsonNode node = jp.readValueAsTree(); 173 return new Properties(node); 174 175 } 176 177 /** 178 * Decodes a Serializable to make it a blob. 179 * 180 * @since 5.9.1 181 */ 182 private static Serializable decodeBlob(Serializable data) { 183 if (data instanceof Blob) { 184 return data; 185 } else { 186 return null; 187 } 188 } 189 190 public static void applyPropertyValues(DocumentModel src, DocumentModel dst) { 191 for (String schema : src.getSchemas()) { 192 DataModelImpl dataModel = (DataModelImpl) dst.getDataModel(schema); 193 DataModelImpl fromDataModel = (DataModelImpl) src.getDataModel(schema); 194 195 for (String field : fromDataModel.getDirtyFields()) { 196 Serializable data = (Serializable) fromDataModel.getData(field); 197 try { 198 if (!(dataModel.getDocumentPart().get(field) instanceof BlobProperty)) { 199 dataModel.setData(field, data); 200 } else { 201 dataModel.setData(field, decodeBlob(data)); 202 } 203 // } 204 } catch (PropertyNotFoundException e) { 205 log.warn(String.format("Trying to deserialize unexistent field : {%s}", field)); 206 } 207 } 208 } 209 } 210 211}