/*
* (C) Copyright 2015-2016 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>
* Ronan DANIELLOU <rdaniellou@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.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.JsonGenerator;
import org.nuxeo.ecm.core.api.Blob;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.model.Property;
import org.nuxeo.ecm.core.api.model.impl.ArrayProperty;
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.download.DownloadService;
import org.nuxeo.ecm.core.io.marshallers.json.AbstractJsonWriter;
import org.nuxeo.ecm.core.io.registry.MarshallingException;
import org.nuxeo.ecm.core.io.registry.reflect.Setup;
import org.nuxeo.ecm.core.schema.types.ListType;
import org.nuxeo.ecm.core.schema.types.SimpleType;
import org.nuxeo.ecm.core.schema.types.Type;
import org.nuxeo.ecm.core.schema.types.primitives.BinaryType;
import org.nuxeo.ecm.core.schema.types.primitives.BooleanType;
import org.nuxeo.ecm.core.schema.types.primitives.DoubleType;
import org.nuxeo.ecm.core.schema.types.primitives.IntegerType;
import org.nuxeo.ecm.core.schema.types.primitives.LongType;
import org.nuxeo.ecm.core.schema.types.resolver.ObjectResolver;
import org.nuxeo.runtime.api.Framework;
/**
* Convert {@link Property} to Json.
* <p>
* Format is:
*
* <pre>
* "stringPropertyValue" <-- for string property, 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
* or
* true|false <- for boolean property
* or
* 123 <- for int property
* ...
* { <- for complex property
* "subProperty": ...,
* ...
* },
* [ ... ] <- for list property
* }
* </pre>
*
* </p>
*
* @since 7.2
*/
@Setup(mode = SINGLETON, priority = REFERENCE)
public class DocumentPropertyJsonWriter extends AbstractJsonWriter<Property> {
private static final Log log = LogFactory.getLog(DocumentPropertyJsonWriter.class);
@Override
public void write(Property prop, JsonGenerator jg) throws IOException {
writeProperty(jg, prop);
jg.flush();
}
protected void writeProperty(JsonGenerator jg, Property prop) throws IOException {
if (prop.isScalar()) {
writeScalarProperty(jg, prop);
} else if (prop.isList()) {
writeListProperty(jg, prop);
} else {
if (prop.isPhantom()) {
jg.writeNull();
} else if (prop instanceof BlobProperty) { // a blob
writeBlobProperty(jg, prop);
} else { // a complex property
writeComplexProperty(jg, prop);
}
}
}
protected void writeScalarProperty(JsonGenerator jg, Property prop) throws IOException {
Type type = prop.getType();
Object value = prop.getValue();
if (!fetchProperty(jg, prop.getType().getObjectResolver(), value, prop.getXPath())) {
writeScalarPropertyValue(jg, ((SimpleType) type).getPrimitiveType(), value);
}
}
private void writeScalarPropertyValue(JsonGenerator jg, Type type, Object value) throws IOException {
if (value == null) {
jg.writeNull();
} else if (type instanceof BooleanType) {
jg.writeBoolean((Boolean) value);
} else if (type instanceof LongType) {
jg.writeNumber((Long) value);
} else if (type instanceof DoubleType) {
jg.writeNumber((Double) value);
} else if (type instanceof IntegerType) {
jg.writeNumber((Integer) value);
} else if (type instanceof BinaryType) {
jg.writeBinary((byte[]) value);
} else {
jg.writeString(type.encode(value));
}
}
protected boolean fetchProperty(JsonGenerator jg, ObjectResolver resolver, Object value, String path)
throws IOException {
if (value == null) {
return false;
}
boolean fetched = false;
if (resolver != null) {
String genericPropertyPath = path.replaceAll("/[0-9]*/", "/*/");
Set<String> fetchElements = ctx.getFetched(ENTITY_TYPE);
boolean fetch = false;
for (String fetchElement : fetchElements) {
if ("properties".equals(fetchElement) || path.startsWith(fetchElement)
|| genericPropertyPath.startsWith(fetchElement)) {
fetch = true;
break;
}
}
if (fetch) {
Object object = resolver.fetch(value);
if (object != null) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
writeEntity(object, baos);
jg.writeRawValue(baos.toString());
fetched = true;
} catch (MarshallingException e) {
log.error("Unable to marshall as json the entity referenced by the property " + path, e);
}
}
}
}
return fetched;
}
protected void writeListProperty(JsonGenerator jg, Property prop) throws IOException {
jg.writeStartArray();
if (prop instanceof ArrayProperty) {
Object[] ar = (Object[]) prop.getValue();
if (ar == null) {
jg.writeEndArray();
return;
}
Type itemType = ((ListType) prop.getType()).getFieldType();
ObjectResolver resolver = itemType.getObjectResolver();
String path = prop.getXPath();
for (Object o : ar) {
if (!fetchProperty(jg, resolver, o, path)) {
writeScalarPropertyValue(jg, ((SimpleType) itemType).getPrimitiveType(), o);
}
}
} else {
ListProperty listp = (ListProperty) prop;
for (Property p : listp.getChildren()) {
writeProperty(jg, p);
}
}
jg.writeEndArray();
}
protected void writeComplexProperty(JsonGenerator jg, Property prop) throws IOException {
jg.writeStartObject();
for (Property p : prop.getChildren()) {
jg.writeFieldName(p.getName());
writeProperty(jg, p);
}
jg.writeEndObject();
}
protected void writeBlobProperty(JsonGenerator jg, Property prop) throws IOException {
Blob blob = (Blob) prop.getValue();
if (blob == null) {
jg.writeNull();
return;
}
jg.writeStartObject();
String v = blob.getFilename();
if (v == null) {
jg.writeNullField("name");
} else {
jg.writeStringField("name", v);
}
v = blob.getMimeType();
if (v == null) {
jg.writeNullField("mime-type");
} else {
jg.writeStringField("mime-type", v);
}
v = blob.getEncoding();
if (v == null) {
jg.writeNullField("encoding");
} else {
jg.writeStringField("encoding", v);
}
v = blob.getDigestAlgorithm();
if (v == null) {
jg.writeNullField("digestAlgorithm");
} else {
jg.writeStringField("digestAlgorithm", v);
}
v = blob.getDigest();
if (v == null) {
jg.writeNullField("digest");
} else {
jg.writeStringField("digest", v);
}
jg.writeStringField("length", Long.toString(blob.getLength()));
String blobUrl = getBlobUrl(prop);
if (blobUrl == null) {
blobUrl = "";
}
jg.writeStringField("data", blobUrl);
jg.writeEndObject();
}
/**
* Gets the full URL of where a blob can be downloaded.
*
* @since 7.2
*/
private String getBlobUrl(Property prop) {
DocumentModel doc = ctx.getParameter(ENTITY_TYPE);
if (doc == null) {
return "";
}
DownloadService downloadService = Framework.getService(DownloadService.class);
String xpath = prop.getXPath();
// if no prefix, use schema name as prefix:
if (!xpath.contains(":")) {
xpath = prop.getSchema().getName() + ":" + xpath;
}
String filename = ((Blob) prop.getValue()).getFilename();
return ctx.getBaseUrl() + downloadService.getDownloadUrl(doc, xpath, filename);
}
}