001/*
002 * Copyright (c) 2006-2015 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *    Bogdan Stefanescu
011 */
012package org.nuxeo.ecm.automation.core.util;
013
014import org.codehaus.jackson.JsonGenerationException;
015import org.codehaus.jackson.JsonGenerator;
016import org.nuxeo.common.utils.URIUtils;
017import org.nuxeo.ecm.core.api.Blob;
018import org.nuxeo.ecm.core.api.PropertyException;
019import org.nuxeo.ecm.core.api.model.Property;
020import org.nuxeo.ecm.core.api.model.impl.ArrayProperty;
021import org.nuxeo.ecm.core.api.model.impl.ComplexProperty;
022import org.nuxeo.ecm.core.api.model.impl.ListProperty;
023import org.nuxeo.ecm.core.api.model.impl.primitives.BlobProperty;
024import org.nuxeo.ecm.core.schema.types.ComplexTypeImpl;
025import org.nuxeo.ecm.core.schema.types.ListType;
026import org.nuxeo.ecm.core.schema.types.primitives.*;
027
028import java.io.IOException;
029import java.io.UnsupportedEncodingException;
030import java.util.Calendar;
031import java.util.Date;
032
033/**
034 * Helper to marshaling properties into JSON.
035 *
036 * @since 7.1
037 */
038public class JSONPropertyWriter {
039
040    /** Utility class. */
041    private JSONPropertyWriter() {
042    }
043
044    /**
045     * Converts the value of the given core property to JSON. The given filesBaseUrl is the baseUrl that can be used to
046     * locate blob content and is useful to generate blob URLs.
047     */
048    public static void writePropertyValue(JsonGenerator jg, Property prop, DateTimeFormat dateTimeFormat,
049            String filesBaseUrl) throws PropertyException, JsonGenerationException, IOException {
050        if (prop.isScalar()) {
051            writeScalarPropertyValue(jg, prop, dateTimeFormat);
052        } else if (prop.isList()) {
053            writeListPropertyValue(jg, prop, dateTimeFormat, filesBaseUrl);
054        } else {
055            if (prop.isPhantom()) {
056                jg.writeNull();
057            } else if (prop instanceof BlobProperty) { // a blob
058                writeBlobPropertyValue(jg, prop, filesBaseUrl);
059            } else { // a complex property
060                writeMapPropertyValue(jg, (ComplexProperty) prop, dateTimeFormat, filesBaseUrl);
061            }
062        }
063    }
064
065    @SuppressWarnings("boxing")
066    protected static void writeScalarPropertyValue(JsonGenerator jg, Property prop, DateTimeFormat dateTimeFormat)
067            throws PropertyException, IOException {
068        org.nuxeo.ecm.core.schema.types.Type type = prop.getType();
069        Object v = prop.getValue();
070        if (v == null) {
071            jg.writeNull();
072        } else {
073            if (type instanceof BooleanType) {
074                jg.writeBoolean((Boolean) v);
075            } else if (type instanceof LongType) {
076                jg.writeNumber((Long) v);
077            } else if (type instanceof DoubleType) {
078                jg.writeNumber((Double) v);
079            } else if (type instanceof IntegerType) {
080                jg.writeNumber((Integer) v);
081            } else if (type instanceof BinaryType) {
082                jg.writeBinary((byte[]) v);
083            } else if (type instanceof DateType && dateTimeFormat == DateTimeFormat.TIME_IN_MILLIS) {
084                if (v instanceof Date) {
085                    jg.writeNumber(((Date) v).getTime());
086                } else if (v instanceof Calendar) {
087                    jg.writeNumber(((Calendar) v).getTimeInMillis());
088                } else {
089                    throw new PropertyException("Unknown class for DateType: " + v.getClass().getName() + ", " + v);
090                }
091            } else {
092                jg.writeString(type.encode(v));
093            }
094        }
095    }
096
097    protected static void writeListPropertyValue(JsonGenerator jg, Property prop, DateTimeFormat dateTimeFormat,
098            String filesBaseUrl) throws PropertyException, JsonGenerationException, IOException {
099        jg.writeStartArray();
100        if (prop instanceof ArrayProperty) {
101            Object[] ar = (Object[]) prop.getValue();
102            if (ar == null) {
103                jg.writeEndArray();
104                return;
105            }
106            org.nuxeo.ecm.core.schema.types.Type type = ((ListType) prop.getType()).getFieldType();
107            for (Object o : ar) {
108                jg.writeString(type.encode(o));
109            }
110        } else {
111            ListProperty listp = (ListProperty) prop;
112            for (Property p : listp.getChildren()) {
113                writePropertyValue(jg, p, dateTimeFormat, filesBaseUrl);
114            }
115        }
116        jg.writeEndArray();
117    }
118
119    protected static void writeMapPropertyValue(JsonGenerator jg, ComplexProperty prop, DateTimeFormat dateTimeFormat,
120            String filesBaseUrl) throws JsonGenerationException, IOException, PropertyException {
121        jg.writeStartObject();
122        for (Property p : prop.getChildren()) {
123            jg.writeFieldName(p.getName());
124            writePropertyValue(jg, p, dateTimeFormat, filesBaseUrl);
125        }
126        jg.writeEndObject();
127    }
128
129    protected static void writeBlobPropertyValue(JsonGenerator jg, Property prop, String filesBaseUrl)
130            throws PropertyException, JsonGenerationException, IOException {
131        Blob blob = (Blob) prop.getValue();
132        if (blob == null) {
133            jg.writeNull();
134            return;
135        }
136        jg.writeStartObject();
137        String v = blob.getFilename();
138        if (v == null) {
139            jg.writeNullField("name");
140        } else {
141            jg.writeStringField("name", v);
142        }
143        v = blob.getMimeType();
144        if (v == null) {
145            jg.writeNullField("mime-type");
146        } else {
147            jg.writeStringField("mime-type", v);
148        }
149        v = blob.getEncoding();
150        if (v == null) {
151            jg.writeNullField("encoding");
152        } else {
153            jg.writeStringField("encoding", v);
154        }
155        v = blob.getDigest();
156        if (v == null) {
157            jg.writeNullField("digest");
158        } else {
159            jg.writeStringField("digest", v);
160        }
161        jg.writeStringField("length", Long.toString(blob.getLength()));
162        if (filesBaseUrl != null) {
163            jg.writeStringField("data", getBlobUrl(prop, filesBaseUrl));
164        }
165        jg.writeEndObject();
166    }
167
168    /**
169     * Gets the full URL of where a blob can be downloaded.
170     *
171     * @since 5.9.3
172     */
173    private static String getBlobUrl(Property prop, String filesBaseUrl) throws UnsupportedEncodingException,
174            PropertyException {
175        StringBuilder blobUrlBuilder = new StringBuilder(filesBaseUrl);
176        blobUrlBuilder.append(prop.getSchema().getName());
177        blobUrlBuilder.append(":");
178        String canonicalXPath = ComplexTypeImpl.canonicalXPath(prop.getPath().substring(1));
179        blobUrlBuilder.append(canonicalXPath);
180        blobUrlBuilder.append("/");
181        String filename = ((Blob) prop.getValue()).getFilename();
182        if (filename != null) {
183            blobUrlBuilder.append(URIUtils.quoteURIPathComponent(filename, true));
184        }
185        return blobUrlBuilder.toString();
186    }
187
188}