001/* 002 * (C) Copyright 2015 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 * Nicolas Chapurlat <nchapurlat@nuxeo.com> 018 */ 019 020package org.nuxeo.ecm.core.io.marshallers.json.document; 021 022import static org.nuxeo.ecm.core.io.marshallers.json.document.DocumentModelJsonWriter.ENTITY_TYPE; 023import static org.nuxeo.ecm.core.io.registry.reflect.Instantiations.SINGLETON; 024import static org.nuxeo.ecm.core.io.registry.reflect.Priorities.REFERENCE; 025 026import java.io.IOException; 027import java.io.InputStream; 028import java.io.Serializable; 029import java.lang.reflect.ParameterizedType; 030import java.lang.reflect.Type; 031import java.util.List; 032 033import javax.ws.rs.core.MediaType; 034 035import org.apache.commons.lang.StringUtils; 036import org.apache.commons.lang3.reflect.TypeUtils; 037import org.codehaus.jackson.JsonNode; 038import org.nuxeo.ecm.core.api.Blob; 039import org.nuxeo.ecm.core.api.DocumentModel; 040import org.nuxeo.ecm.core.api.IdRef; 041import org.nuxeo.ecm.core.api.impl.DataModelImpl; 042import org.nuxeo.ecm.core.api.impl.SimpleDocumentModel; 043import org.nuxeo.ecm.core.api.model.Property; 044import org.nuxeo.ecm.core.api.model.PropertyNotFoundException; 045import org.nuxeo.ecm.core.api.model.impl.ComplexProperty; 046import org.nuxeo.ecm.core.api.model.impl.ListProperty; 047import org.nuxeo.ecm.core.api.model.impl.primitives.BlobProperty; 048import org.nuxeo.ecm.core.io.marshallers.json.EntityJsonReader; 049import org.nuxeo.ecm.core.io.registry.Reader; 050import org.nuxeo.ecm.core.io.registry.context.RenderingContext.SessionWrapper; 051import org.nuxeo.ecm.core.io.registry.reflect.Setup; 052import org.nuxeo.ecm.core.schema.types.Field; 053 054/** 055 * Convert Json as {@link DocumentModel}. 056 * <p> 057 * Format is (any additional json property is ignored): 058 * 059 * <pre> 060 * { 061 * "entity-type": "document", 062 * "uid": "EXISTING_DOCUMENT_UID", <- use it to update an existing document 063 * "repository": "REPOSITORY_NAME" , <- explicitely specify the repository name 064 * "name": "DOCUMENT_NAME", <- use it to create an new document 065 * "type": "DOCUMENT_TYPE", <- use it to create an new document 066 * "properties": ... <-- see {@link DocumentPropertiesJsonReader} 067 * } 068 * </pre> 069 * 070 * </p> 071 * 072 * @since 7.2 073 */ 074@Setup(mode = SINGLETON, priority = REFERENCE) 075public class DocumentModelJsonReader extends EntityJsonReader<DocumentModel> { 076 077 public static final String LEGACY_MODE_READER = "DocumentModelLegacyModeReader"; 078 079 public DocumentModelJsonReader() { 080 super(ENTITY_TYPE); 081 } 082 083 @Override 084 public DocumentModel read(Class<?> clazz, Type genericType, MediaType mediaType, InputStream in) throws IOException { 085 Reader<DocumentModel> reader = ctx.getParameter(LEGACY_MODE_READER); 086 if (reader != null) { 087 DocumentModel doc = reader.read(clazz, genericType, mediaType, in); 088 return doc; 089 } else { 090 return super.read(clazz, genericType, mediaType, in); 091 } 092 } 093 094 @Override 095 protected DocumentModel readEntity(JsonNode jn) throws IOException { 096 097 SimpleDocumentModel simpleDoc = new SimpleDocumentModel(); 098 String name = getStringField(jn, "name"); 099 if (StringUtils.isNotBlank(name)) { 100 simpleDoc.setPathInfo(null, name); 101 } 102 String type = getStringField(jn, "type"); 103 if (StringUtils.isNotBlank(type)) { 104 simpleDoc.setType(type); 105 } 106 107 JsonNode propsNode = jn.get("properties"); 108 if (propsNode != null && !propsNode.isNull() && propsNode.isObject()) { 109 ParameterizedType genericType = TypeUtils.parameterize(List.class, Property.class); 110 List<Property> properties = readEntity(List.class, genericType, propsNode); 111 for (Property property : properties) { 112 String propertyName = property.getName(); 113 // handle schema with no prefix 114 if (!propertyName.contains(":")) { 115 propertyName = property.getField().getDeclaringType().getName() + ":" + propertyName; 116 } 117 simpleDoc.setPropertyValue(propertyName, property.getValue()); 118 } 119 } 120 121 DocumentModel doc = null; 122 123 String uid = getStringField(jn, "uid"); 124 if (StringUtils.isNotBlank(uid)) { 125 try (SessionWrapper wrapper = ctx.getSession(null)) { 126 doc = wrapper.getSession().getDocument(new IdRef(uid)); 127 } 128 avoidBlobUpdate(simpleDoc, doc); 129 applyPropertyValues(simpleDoc, doc); 130 } else { 131 doc = simpleDoc; 132 } 133 134 return doc; 135 } 136 137 /** 138 * Avoid the blob updates. It's managed by custom ways. 139 */ 140 private void avoidBlobUpdate(DocumentModel docToClean, DocumentModel docRef) { 141 for (String schema : docToClean.getSchemas()) { 142 for (String field : docToClean.getDataModel(schema).getDirtyFields()) { 143 avoidBlobUpdate(docToClean.getProperty(field), docRef); 144 } 145 } 146 } 147 148 private void avoidBlobUpdate(Property propToClean, DocumentModel docRef) { 149 if (propToClean instanceof BlobProperty) { 150 // if the blob used to exist 151 if (propToClean.getValue() == null) { 152 Serializable value = docRef.getPropertyValue(propToClean.getPath()); 153 propToClean.setValue(value); 154 } 155 } else if (propToClean instanceof ComplexProperty) { 156 ComplexProperty complexPropToClean = (ComplexProperty) propToClean; 157 for (Field field : complexPropToClean.getType().getFields()) { 158 Property childPropToClean = complexPropToClean.get(field.getName().getLocalName()); 159 avoidBlobUpdate(childPropToClean, docRef); 160 } 161 } else if (propToClean instanceof ListProperty) { 162 ListProperty listPropToClean = (ListProperty) propToClean; 163 for (int i = 0; i < listPropToClean.size(); i++) { 164 Property elPropToClean = listPropToClean.get(i); 165 avoidBlobUpdate(elPropToClean, docRef); 166 } 167 } 168 } 169 170 public static void applyPropertyValues(DocumentModel src, DocumentModel dst) { 171 for (String schema : src.getSchemas()) { 172 DataModelImpl dataModel = (DataModelImpl) dst.getDataModel(schema); 173 DataModelImpl fromDataModel = (DataModelImpl) src.getDataModel(schema); 174 175 for (String field : fromDataModel.getDirtyFields()) { 176 Serializable data = (Serializable) fromDataModel.getData(field); 177 try { 178 if (!(dataModel.getDocumentPart().get(field) instanceof BlobProperty)) { 179 dataModel.setData(field, data); 180 } else { 181 dataModel.setData(field, decodeBlob(data)); 182 } 183 } catch (PropertyNotFoundException e) { 184 continue; 185 } 186 } 187 } 188 } 189 190 private static Serializable decodeBlob(Serializable data) { 191 if (data instanceof Blob) { 192 return data; 193 } else { 194 return null; 195 } 196 } 197 198}