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