001/* 002 * (C) Copyright 2015-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 * 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; 032import java.util.stream.Stream; 033 034import javax.ws.rs.core.MediaType; 035 036import org.apache.commons.lang3.StringUtils; 037import org.apache.commons.lang3.reflect.TypeUtils; 038import org.apache.logging.log4j.LogManager; 039import org.apache.logging.log4j.Logger; 040import org.nuxeo.ecm.core.api.CoreSession; 041import org.nuxeo.ecm.core.api.DocumentModel; 042import org.nuxeo.ecm.core.api.IdRef; 043import org.nuxeo.ecm.core.api.impl.DataModelImpl; 044import org.nuxeo.ecm.core.api.impl.SimpleDocumentModel; 045import org.nuxeo.ecm.core.api.model.DocumentPart; 046import org.nuxeo.ecm.core.api.model.Property; 047import org.nuxeo.ecm.core.api.model.PropertyNotFoundException; 048import org.nuxeo.ecm.core.api.model.ReadOnlyPropertyException; 049import org.nuxeo.ecm.core.io.marshallers.json.EntityJsonReader; 050import org.nuxeo.ecm.core.io.registry.Reader; 051import org.nuxeo.ecm.core.io.registry.reflect.Setup; 052import org.nuxeo.ecm.core.schema.DocumentType; 053import org.nuxeo.ecm.core.schema.SchemaManager; 054import org.nuxeo.runtime.api.Framework; 055 056import com.fasterxml.jackson.databind.JsonNode; 057 058/** 059 * Convert Json as {@link DocumentModel}. 060 * <p> 061 * Format is (any additional json property is ignored): 062 * 063 * <pre> 064 * { 065 * "entity-type": "document", 066 * "uid": "EXISTING_DOCUMENT_UID", <- use it to update an existing document 067 * "name": "DOCUMENT_NAME", <- use it to create an new document 068 * "type": "DOCUMENT_TYPE", <- use it to create an new document 069 * "changeToken": "CHANGE_TOKEN", <- pass the previous change token for optimistic locking 070 * "properties": ... <-- see {@link DocumentPropertiesJsonReader} 071 * } 072 * </pre> 073 * 074 * @since 7.2 075 */ 076@Setup(mode = SINGLETON, priority = REFERENCE) 077public class DocumentModelJsonReader extends EntityJsonReader<DocumentModel> { 078 079 private static final Logger log = LogManager.getLogger(DocumentModelJsonReader.class); 080 081 public static final String LEGACY_MODE_READER = "DocumentModelLegacyModeReader"; 082 083 public DocumentModelJsonReader() { 084 super(ENTITY_TYPE); 085 } 086 087 @Override 088 public DocumentModel read(Class<?> clazz, Type genericType, MediaType mediaType, InputStream in) 089 throws IOException { 090 Reader<DocumentModel> reader = ctx.getParameter(LEGACY_MODE_READER); 091 if (reader != null) { 092 return reader.read(clazz, genericType, mediaType, in); 093 } else { 094 return super.read(clazz, genericType, mediaType, in); 095 } 096 } 097 098 @Override 099 @SuppressWarnings("deprecation") 100 protected DocumentModel readEntity(JsonNode jn) throws IOException { 101 DocumentModel doc = getDocument(jn); 102 103 JsonNode propsNode = jn.get("properties"); 104 if (propsNode != null && !propsNode.isNull() && propsNode.isObject()) { 105 ParameterizedType genericType = TypeUtils.parameterize(List.class, Property.class); 106 List<Property> properties = readEntity(List.class, genericType, propsNode); 107 for (Property property : properties) { 108 // security has been applied in previous step while reading properties 109 Framework.doPrivileged(() -> { 110 DocumentPart part = doc.getPart(property.getSchema().getName()); 111 if (part != null) { 112 part.set(property.getName(), property); 113 } 114 }); 115 } 116 } 117 118 return doc; 119 } 120 121 /** 122 * @since 11.1 123 */ 124 protected DocumentModel getDocument(JsonNode jn) throws IOException { 125 String uid = getStringField(jn, "uid"); 126 if (StringUtils.isNotBlank(uid) && ctx != null) { 127 try (var wrapper = ctx.getSession(null)) { 128 DocumentModel doc = wrapper.getSession().getDocument(new IdRef(uid)); 129 String changeToken = getStringField(jn, "changeToken"); 130 doc.putContextData(CoreSession.CHANGE_TOKEN, changeToken); 131 return doc; 132 } 133 } else { 134 String type = getStringField(jn, "type"); 135 DocumentModel doc = StringUtils.isNotBlank(type) ? SimpleDocumentModel.ofType(type) 136 : SimpleDocumentModel.empty(); 137 String name = getStringField(jn, "name"); 138 if (StringUtils.isNotBlank(name)) { 139 doc.setPathInfo(null, name); 140 } 141 return doc; 142 } 143 } 144 145 public static void applyPropertyValues(DocumentModel src, DocumentModel dst) { 146 applyPropertyValues(src, dst, true); 147 // copy change token 148 dst.getContextData().putAll(src.getContextData()); 149 } 150 151 public static void applyPropertyValues(DocumentModel src, DocumentModel dst, boolean dirtyOnly) { 152 // if not "dirty only", it handles all the schemas for the given type 153 // so it will trigger the default values initialization 154 if (dirtyOnly) { 155 applyDirtyPropertyValues(src, dst); 156 } else { 157 applyAllPropertyValues(src, dst); 158 } 159 } 160 161 public static void applyDirtyPropertyValues(DocumentModel src, DocumentModel dst) { 162 Stream.of(src.getSchemas()) 163 .flatMap(s -> src.getPropertyObjects(s).stream()) 164 .filter(Property::isDirty) 165 .forEach(p -> applyPropertyValue(p, dst)); 166 } 167 168 public static void applyAllPropertyValues(DocumentModel src, DocumentModel dst) { 169 SchemaManager service = Framework.getService(SchemaManager.class); 170 DocumentType type = service.getDocumentType(src.getType()); 171 Stream.of(type.getSchemaNames()) 172 .flatMap(s -> src.getPropertyObjects(s).stream()) 173 .forEach(p -> applyPropertyValue(p, dst)); 174 } 175 176 protected static void applyPropertyValue(Property property, DocumentModel dst) { 177 try { 178 dst.setPropertyValue(getXPath(property), property.getValue()); 179 } catch (PropertyNotFoundException | ReadOnlyPropertyException e) { 180 log.trace("Can't apply property: {} to dst: {}", property, dst, e); 181 } 182 } 183 184 protected static String getXPath(Property property) { 185 String xpath = property.getXPath(); 186 // if no prefix, use schema name as prefix 187 if (!xpath.contains(":")) { 188 xpath = property.getSchema().getName() + ":" + xpath; 189 } 190 return xpath; 191 } 192 193 /** 194 * @deprecated since 11.1. Not used anymore. 195 */ 196 @Deprecated(since = "11.1") 197 protected static void applyPropertyValue(DataModelImpl srcDataModel, DataModelImpl dstDataModel, String fieldName) { 198 Serializable data = (Serializable) srcDataModel.getData(fieldName); 199 try { 200 dstDataModel.setData(fieldName, data); 201 } catch (PropertyNotFoundException | ReadOnlyPropertyException e) { 202 log.trace("Can't apply value: {} to src: {}", data, srcDataModel, e); 203 } 204 205 } 206}