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.logging.log4j.LogManager; 042import org.apache.logging.log4j.Logger; 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.session.SessionFactory; 057 058import com.fasterxml.jackson.core.JsonFactory; 059import com.fasterxml.jackson.core.JsonParser; 060import com.fasterxml.jackson.core.JsonToken; 061import com.fasterxml.jackson.databind.JsonNode; 062 063/** 064 * JAX-RS reader for a DocumentModel. If an id is given, it tries to reattach the document to the session. If not, it 065 * creates a ready to create DocumentModel filled with the properties found. 066 * 067 * @since 5.7.2 068 * @deprecated since 7.10 The Nuxeo JSON marshalling was migrated to nuxeo-core-io. This class is replaced by 069 * {@link DocumentModelJsonReader} which is registered by default and available to marshal 070 * {@link DocumentModel} from the Nuxeo Rest API thanks to the JAX-RS marshaller 071 * {@link org.nuxeo.ecm.webengine.jaxrs.coreiodelegate.CoreIODelegate} . On removal, need to remove also 072 * {@link DocumentModelJsonReaderLegacy} because it uses it using reflexion. 073 */ 074@Deprecated 075@Provider 076@Consumes(MediaType.APPLICATION_JSON) 077public class JSONDocumentModelReader implements MessageBodyReader<DocumentModel> { 078 079 // private static final String REQUEST_BATCH_ID = "batchId"; 080 081 protected static final Logger log = LogManager.getLogger(JSONDocumentModelReader.class); 082 083 @Context 084 HttpServletRequest request; 085 086 @Context 087 JsonFactory factory; 088 089 @Override 090 public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { 091 return DocumentModel.class.isAssignableFrom(type); 092 } 093 094 @Override 095 public DocumentModel readFrom(Class<DocumentModel> type, Type genericType, Annotation[] annotations, 096 MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) 097 throws IOException, WebApplicationException { 098 String content = IOUtils.toString(entityStream); 099 if (content.isEmpty()) { 100 throw new NuxeoException("No content in request body", SC_BAD_REQUEST); 101 102 } 103 104 try { 105 return readRequest(content, httpHeaders); 106 } catch (IOException e) { 107 throw new NuxeoException(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 = SimpleDocumentModel.empty(); 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("Trying to deserialize unExistent field : {}", field); 217 } 218 } 219 } 220 } 221 222}