001/*
002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Nicolas Chapurlat <nchapurlat@nuxeo.com>
016 */
017
018package org.nuxeo.ecm.core.io.marshallers.json.document;
019
020import static org.nuxeo.ecm.core.io.marshallers.json.document.DocumentModelJsonWriter.ENTITY_TYPE;
021import static org.nuxeo.ecm.core.io.registry.reflect.Instantiations.SINGLETON;
022import static org.nuxeo.ecm.core.io.registry.reflect.Priorities.REFERENCE;
023
024import java.io.IOException;
025import java.io.InputStream;
026import java.io.Serializable;
027import java.lang.reflect.ParameterizedType;
028import java.lang.reflect.Type;
029import java.util.List;
030
031import javax.ws.rs.core.MediaType;
032
033import org.apache.commons.lang.StringUtils;
034import org.apache.commons.lang3.reflect.TypeUtils;
035import org.codehaus.jackson.JsonNode;
036import org.nuxeo.ecm.core.api.Blob;
037import org.nuxeo.ecm.core.api.DocumentModel;
038import org.nuxeo.ecm.core.api.IdRef;
039import org.nuxeo.ecm.core.api.impl.DataModelImpl;
040import org.nuxeo.ecm.core.api.impl.SimpleDocumentModel;
041import org.nuxeo.ecm.core.api.model.Property;
042import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
043import org.nuxeo.ecm.core.api.model.impl.primitives.BlobProperty;
044import org.nuxeo.ecm.core.io.marshallers.json.EntityJsonReader;
045import org.nuxeo.ecm.core.io.registry.Reader;
046import org.nuxeo.ecm.core.io.registry.context.RenderingContext.SessionWrapper;
047import org.nuxeo.ecm.core.io.registry.reflect.Setup;
048
049/**
050 * Convert Json as {@link DocumentModel}.
051 * <p>
052 * Format is (any additional json property is ignored):
053 *
054 * <pre>
055 * {
056 *   "entity-type": "document",
057 *   "uid": "EXISTING_DOCUMENT_UID", <- use it to update an existing document
058 *   "repository": "REPOSITORY_NAME" , <- explicitely specify the repository name
059 *   "name": "DOCUMENT_NAME", <- use it to create an new document
060 *   "type": "DOCUMENT_TYPE", <- use it to create an new document
061 *   "properties": ...  <-- see {@link DocumentPropertiesJsonReader}
062 * }
063 * </pre>
064 *
065 * </p>
066 *
067 * @since 7.2
068 */
069@Setup(mode = SINGLETON, priority = REFERENCE)
070public class DocumentModelJsonReader extends EntityJsonReader<DocumentModel> {
071
072    public static final String LEGACY_MODE_READER = "DocumentModelLegacyModeReader";
073
074    public DocumentModelJsonReader() {
075        super(ENTITY_TYPE);
076    }
077
078    @Override
079    public DocumentModel read(Class<?> clazz, Type genericType, MediaType mediaType, InputStream in) throws IOException {
080        Reader<DocumentModel> reader = ctx.getParameter(LEGACY_MODE_READER);
081        if (reader != null) {
082            DocumentModel doc = reader.read(clazz, genericType, mediaType, in);
083            return doc;
084        } else {
085            return super.read(clazz, genericType, mediaType, in);
086        }
087    }
088
089    @Override
090    protected DocumentModel readEntity(JsonNode jn) throws IOException {
091
092        SimpleDocumentModel simpleDoc = new SimpleDocumentModel();
093        String name = getStringField(jn, "name");
094        if (StringUtils.isNotBlank(name)) {
095            simpleDoc.setPathInfo(null, name);
096        }
097        String type = getStringField(jn, "type");
098        if (StringUtils.isNotBlank(type)) {
099            simpleDoc.setType(type);
100        }
101
102        JsonNode propsNode = jn.get("properties");
103        if (propsNode != null && !propsNode.isNull() && propsNode.isObject()) {
104            ParameterizedType genericType = TypeUtils.parameterize(List.class, Property.class);
105            List<Property> properties = readEntity(List.class, genericType, propsNode);
106            for (Property property : properties) {
107                String propertyName = property.getName();
108                // handle schema with no prefix
109                if (!propertyName.contains(":")) {
110                    propertyName = property.getField().getDeclaringType().getName() + ":" + propertyName;
111                }
112                simpleDoc.setPropertyValue(propertyName, property.getValue());
113            }
114        }
115
116        DocumentModel doc = null;
117
118        String uid = getStringField(jn, "uid");
119        if (StringUtils.isNotBlank(uid)) {
120            try (SessionWrapper wrapper = ctx.getSession(null)) {
121                doc = wrapper.getSession().getDocument(new IdRef(uid));
122            }
123            applyPropertyValues(simpleDoc, doc);
124        } else {
125            doc = simpleDoc;
126        }
127
128        return doc;
129    }
130
131    public static void applyPropertyValues(DocumentModel src, DocumentModel dst) {
132        for (String schema : src.getSchemas()) {
133            DataModelImpl dataModel = (DataModelImpl) dst.getDataModel(schema);
134            DataModelImpl fromDataModel = (DataModelImpl) src.getDataModel(schema);
135
136            for (String field : fromDataModel.getDirtyFields()) {
137                Serializable data = (Serializable) fromDataModel.getData(field);
138                try {
139                    if (!(dataModel.getDocumentPart().get(field) instanceof BlobProperty)) {
140                        dataModel.setData(field, data);
141                    } else {
142                        dataModel.setData(field, decodeBlob(data));
143                    }
144                } catch (PropertyNotFoundException e) {
145                    continue;
146                }
147            }
148        }
149    }
150
151    private static Serializable decodeBlob(Serializable data) {
152        if (data instanceof Blob) {
153            return data;
154        } else {
155            return null;
156        }
157    }
158
159}