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