/* * (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 javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; import static org.nuxeo.ecm.core.io.registry.MarshallingConstants.WILDCARD_VALUE; 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.Closeable; import java.io.IOException; import java.io.OutputStream; import java.util.Calendar; import java.util.Set; import javax.inject.Inject; import org.codehaus.jackson.JsonGenerator; import org.joda.time.DateTime; import org.joda.time.format.ISODateTimeFormat; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.Lock; import org.nuxeo.ecm.core.api.model.Property; import org.nuxeo.ecm.core.io.marshallers.json.ExtensibleEntityJsonWriter; import org.nuxeo.ecm.core.io.marshallers.json.OutputStreamWithJsonWriter; import org.nuxeo.ecm.core.io.marshallers.json.enrichers.AbstractJsonEnricher; import org.nuxeo.ecm.core.io.registry.Writer; import org.nuxeo.ecm.core.io.registry.context.MaxDepthReachedException; import org.nuxeo.ecm.core.io.registry.reflect.Setup; 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.ecm.core.schema.utils.DateParser; import org.nuxeo.runtime.api.Framework; import com.thoughtworks.xstream.io.json.JsonWriter; /** * Convert {@link DocumentModel} to Json. * <p> * This marshaller is enrichable: register class implementing {@link AbstractJsonEnricher} and managing * {@link DocumentModel}. * </p> * <p> * This marshaller is also extensible: extend it and simply override * {@link ExtensibleEntityJsonWriter#extend(DocumentModel, JsonWriter)}. * </p> * <p> * Format is: * * <pre> * { * "entity-type":"document", * "repository": "REPOSITORY_NAME", * "uid": "DOCUMENT_UID", * "path": "DOCUMENT_PATH", * "type": "DOCUMENT_TYPE", * "state": "DOCUMENT_STATE", * "parentRef": "PARENT_DOCUMENT_UID", * "isCheckedOut": true|false, * "changeToken": null|"CHANGE_TOKEN", * "isCheckedOut": true|false, * "title": "DOCUMENT_TITLE", * "lastModified": "DATE_UPDATE", <-- if dublincore is present and if dc:modified is not null * "versionLabel": "DOCUMENT_VERSION", <-- only activated with parameter fetch.document=versionLabel or system property nuxeo.document.json.fetch.heavy=true * "lockOwner": "LOCK_OWNER", <-- only activated if locked and with parameter fetch.document=lock or system property nuxeo.document.json.fetch.heavy=true * "lockCreated": "LOCK_DATE", <-- only activated if locked and with parameter fetch.document=lock or system property nuxeo.document.json.fetch.heavy=true * "properties": { <-- only present with parameter properties=schema1,schema2,... see {@link DocumentPropertyJsonWriter} for format * "schemaPrefix:stringProperty": "stringPropertyValue", <-- each property may be fetched if a resolver is associated with that property and if a parameter fetch.document=propXPath is present, in this case, an object will be marshalled as value * "schemaPrefix:booleanProperty": true|false, * "schemaPrefix:integerProperty": 123, * ... * "schemaPrefix:complexProperty": { * "subProperty": ..., * ... * }, * "schemaPrefix:listProperty": [ * ... * ] * } * <-- contextParameters if there are enrichers activated * <-- additional property provided by extend() method * } * </pre> * * </p> * * @since 7.2 */ @Setup(mode = SINGLETON, priority = REFERENCE) public class DocumentModelJsonWriter extends ExtensibleEntityJsonWriter<DocumentModel> { public static final String ENTITY_TYPE = "document"; public static final String DOCUMENT_JSON_FETCH_HEAVY_KEY = "nuxeo.document.json.fetch.heavy"; private static Boolean FETCH_HEAVY_VALUES = null; private static boolean fetchHeavy() { if (FETCH_HEAVY_VALUES == null) { try { FETCH_HEAVY_VALUES = Framework.isBooleanPropertyTrue("nuxeo.document.json.fetch.heavy"); } catch (Exception e) { FETCH_HEAVY_VALUES = false; } } return FETCH_HEAVY_VALUES; } private boolean mustFetch(String name) { return ctx.getFetched(ENTITY_TYPE).contains(name) || fetchHeavy(); } @Inject private SchemaManager schemaManager; public DocumentModelJsonWriter() { super(ENTITY_TYPE, DocumentModel.class); } @Override protected void writeEntityBody(DocumentModel doc, JsonGenerator jg) throws IOException { jg.writeStringField("repository", doc.getRepositoryName()); jg.writeStringField("uid", doc.getId()); jg.writeStringField("path", doc.getPathAsString()); jg.writeStringField("type", doc.getType()); jg.writeStringField("state", doc.getRef() != null ? doc.getCurrentLifeCycleState() : null); jg.writeStringField("parentRef", doc.getParentRef() != null ? doc.getParentRef().toString() : null); jg.writeBooleanField("isCheckedOut", doc.isCheckedOut()); jg.writeBooleanField("isVersion", doc.isVersion()); jg.writeBooleanField("isProxy", doc.isProxy()); jg.writeStringField("changeToken", doc.getChangeToken()); jg.writeStringField("title", doc.getTitle()); if (mustFetch("versionLabel")) { String versionLabel = doc.getVersionLabel(); jg.writeStringField("versionLabel", versionLabel != null ? versionLabel : ""); } if (mustFetch("lock")) { Lock lock = doc.getLockInfo(); if (lock != null) { jg.writeStringField("lockOwner", lock.getOwner()); jg.writeStringField("lockCreated", ISODateTimeFormat.dateTime().print(new DateTime(lock.getCreated()))); } } if (doc.hasSchema("dublincore")) { Calendar cal = (Calendar) doc.getPropertyValue("dc:modified"); if (cal != null) { jg.writeStringField("lastModified", DateParser.formatW3CDateTime(cal.getTime())); } } try (Closeable resource = ctx.wrap().controlDepth().open()) { Set<String> schemas = ctx.getProperties(); if (schemas.size() > 0) { jg.writeObjectFieldStart("properties"); if (schemas.contains(WILDCARD_VALUE)) { // full document for (String schema : doc.getSchemas()) { writeSchemaProperties(jg, doc, schema); } } else { for (String schema : schemas) { if (doc.hasSchema(schema)) { writeSchemaProperties(jg, doc, schema); } } } jg.writeEndObject(); } } catch (MaxDepthReachedException e) { // do not load properties } jg.writeArrayFieldStart("facets"); for (String facet : doc.getFacets()) { jg.writeString(facet); } jg.writeEndArray(); } private void writeSchemaProperties(JsonGenerator jg, DocumentModel doc, String schemaName) throws IOException { Writer<Property> propertyWriter = registry.getWriter(ctx, Property.class, APPLICATION_JSON_TYPE); // provides the current document to the property marshaller try (Closeable resource = ctx.wrap().with(ENTITY_TYPE, doc).open()) { Schema schema = schemaManager.getSchema(schemaName); String prefix = schema.getNamespace().prefix; if (prefix == null || prefix.length() == 0) { prefix = schemaName; } prefix = prefix + ":"; for (Field field : schema.getFields()) { String prefixedName = prefix + field.getName().getLocalName(); jg.writeFieldName(prefixedName); Property property = doc.getProperty(prefixedName); OutputStream out = new OutputStreamWithJsonWriter(jg); propertyWriter.write(property, Property.class, Property.class, APPLICATION_JSON_TYPE, out); } } } }