/*
* (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Nicolas Chapurlat <nchapurlat@nuxeo.com>
*/
package org.nuxeo.ecm.core.io.marshallers.json.document;
import static org.nuxeo.ecm.core.io.marshallers.json.document.DocumentModelJsonWriter.ENTITY_TYPE;
import static org.nuxeo.ecm.core.io.registry.reflect.Instantiations.SINGLETON;
import static org.nuxeo.ecm.core.io.registry.reflect.Priorities.REFERENCE;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import javax.ws.rs.core.MediaType;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.reflect.TypeUtils;
import org.codehaus.jackson.JsonNode;
import org.nuxeo.ecm.core.api.Blob;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.IdRef;
import org.nuxeo.ecm.core.api.impl.DataModelImpl;
import org.nuxeo.ecm.core.api.impl.SimpleDocumentModel;
import org.nuxeo.ecm.core.api.model.Property;
import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
import org.nuxeo.ecm.core.api.model.impl.ComplexProperty;
import org.nuxeo.ecm.core.api.model.impl.ListProperty;
import org.nuxeo.ecm.core.api.model.impl.primitives.BlobProperty;
import org.nuxeo.ecm.core.io.marshallers.json.EntityJsonReader;
import org.nuxeo.ecm.core.io.registry.Reader;
import org.nuxeo.ecm.core.io.registry.context.RenderingContext.SessionWrapper;
import org.nuxeo.ecm.core.io.registry.reflect.Setup;
import org.nuxeo.ecm.core.schema.DocumentType;
import org.nuxeo.ecm.core.schema.SchemaManager;
import org.nuxeo.ecm.core.schema.types.Field;
import org.nuxeo.ecm.core.schema.types.Schema;
import org.nuxeo.runtime.api.Framework;
/**
* Convert Json as {@link DocumentModel}.
* <p>
* Format is (any additional json property is ignored):
*
* <pre>
* {
* "entity-type": "document",
* "uid": "EXISTING_DOCUMENT_UID", <- use it to update an existing document
* "repository": "REPOSITORY_NAME" , <- explicitely specify the repository name
* "name": "DOCUMENT_NAME", <- use it to create an new document
* "type": "DOCUMENT_TYPE", <- use it to create an new document
* "properties": ... <-- see {@link DocumentPropertiesJsonReader}
* }
* </pre>
*
* </p>
*
* @since 7.2
*/
@Setup(mode = SINGLETON, priority = REFERENCE)
public class DocumentModelJsonReader extends EntityJsonReader<DocumentModel> {
public static final String LEGACY_MODE_READER = "DocumentModelLegacyModeReader";
public DocumentModelJsonReader() {
super(ENTITY_TYPE);
}
@Override
public DocumentModel read(Class<?> clazz, Type genericType, MediaType mediaType, InputStream in) throws IOException {
Reader<DocumentModel> reader = ctx.getParameter(LEGACY_MODE_READER);
if (reader != null) {
DocumentModel doc = reader.read(clazz, genericType, mediaType, in);
return doc;
} else {
return super.read(clazz, genericType, mediaType, in);
}
}
@Override
protected DocumentModel readEntity(JsonNode jn) throws IOException {
SimpleDocumentModel simpleDoc = new SimpleDocumentModel();
String name = getStringField(jn, "name");
if (StringUtils.isNotBlank(name)) {
simpleDoc.setPathInfo(null, name);
}
String type = getStringField(jn, "type");
if (StringUtils.isNotBlank(type)) {
simpleDoc.setType(type);
}
JsonNode propsNode = jn.get("properties");
if (propsNode != null && !propsNode.isNull() && propsNode.isObject()) {
ParameterizedType genericType = TypeUtils.parameterize(List.class, Property.class);
List<Property> properties = readEntity(List.class, genericType, propsNode);
for (Property property : properties) {
String propertyName = property.getName();
// handle schema with no prefix
if (!propertyName.contains(":")) {
propertyName = property.getField().getDeclaringType().getName() + ":" + propertyName;
}
simpleDoc.setPropertyValue(propertyName, property.getValue());
}
}
DocumentModel doc = null;
String uid = getStringField(jn, "uid");
if (StringUtils.isNotBlank(uid)) {
try (SessionWrapper wrapper = ctx.getSession(null)) {
doc = wrapper.getSession().getDocument(new IdRef(uid));
}
avoidBlobUpdate(simpleDoc, doc);
applyDirtyPropertyValues(simpleDoc, doc);
} else if (StringUtils.isNotBlank(type)) {
SimpleDocumentModel createdDoc = new SimpleDocumentModel();
if (StringUtils.isNotBlank(name)) {
createdDoc.setPathInfo(null, name);
}
createdDoc.setType(simpleDoc.getType());
applyAllPropertyValues(simpleDoc, createdDoc);
doc = createdDoc;
} else {
doc = simpleDoc;
}
return doc;
}
/**
* Avoid the blob updates. It's managed by custom ways.
*/
private static void avoidBlobUpdate(DocumentModel docToClean, DocumentModel docRef) {
for (String schema : docToClean.getSchemas()) {
for (String field : docToClean.getDataModel(schema).getDirtyFields()) {
avoidBlobUpdate(docToClean.getProperty(field), docRef);
}
}
}
private static void avoidBlobUpdate(Property propToClean, DocumentModel docRef) {
if (propToClean instanceof BlobProperty) {
// if the blob used to exist
if (propToClean.getValue() == null) {
Serializable value = docRef.getPropertyValue(propToClean.getXPath());
propToClean.setValue(value);
}
} else if (propToClean instanceof ComplexProperty) {
ComplexProperty complexPropToClean = (ComplexProperty) propToClean;
for (Field field : complexPropToClean.getType().getFields()) {
Property childPropToClean = complexPropToClean.get(field.getName().getLocalName());
avoidBlobUpdate(childPropToClean, docRef);
}
} else if (propToClean instanceof ListProperty) {
ListProperty listPropToClean = (ListProperty) propToClean;
for (int i = 0; i < listPropToClean.size(); i++) {
Property elPropToClean = listPropToClean.get(i);
avoidBlobUpdate(elPropToClean, docRef);
}
}
}
public static void applyPropertyValues(DocumentModel src, DocumentModel dst) {
avoidBlobUpdate(src, dst);
applyPropertyValues(src, dst, true);
}
public static void applyPropertyValues(DocumentModel src, DocumentModel dst, boolean dirtyOnly) {
// if not "dirty only", it handles all the schemas for the given type
// so it will trigger the default values initialization
if (dirtyOnly) {
applyDirtyPropertyValues(src, dst);
} else {
applyAllPropertyValues(src, dst);
}
}
public static void applyDirtyPropertyValues(DocumentModel src, DocumentModel dst) {
String[] schemas = src.getSchemas();
for (String schema : schemas) {
DataModelImpl dataModel = (DataModelImpl) dst.getDataModel(schema);
DataModelImpl fromDataModel = (DataModelImpl) src.getDataModel(schema);
for (String field : fromDataModel.getDirtyFields()) {
Serializable data = (Serializable) fromDataModel.getData(field);
try {
if (!(dataModel.getDocumentPart().get(field) instanceof BlobProperty)) {
dataModel.setData(field, data);
} else {
dataModel.setData(field, decodeBlob(data));
}
} catch (PropertyNotFoundException e) {
continue;
}
}
}
}
public static void applyAllPropertyValues(DocumentModel src, DocumentModel dst) {
SchemaManager service = Framework.getService(SchemaManager.class);
DocumentType type = service.getDocumentType(src.getType());
String[] schemas = type.getSchemaNames();
for (String schemaName : schemas) {
Schema schema = service.getSchema(schemaName);
DataModelImpl dataModel = (DataModelImpl) dst.getDataModel(schemaName);
DataModelImpl fromDataModel = (DataModelImpl) src.getDataModel(schemaName);
for (Field field : schema.getFields()) {
String fieldName = field.getName().getLocalName();
Serializable data = (Serializable) fromDataModel.getData(fieldName);
try {
if (!(dataModel.getDocumentPart().get(fieldName) instanceof BlobProperty)) {
dataModel.setData(fieldName, data);
} else {
dataModel.setData(fieldName, decodeBlob(data));
}
} catch (PropertyNotFoundException e) {
continue;
}
}
}
}
private static Serializable decodeBlob(Serializable data) {
if (data instanceof Blob) {
return data;
} else {
return null;
}
}
}