001/*
002 * (C) Copyright 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 *     Nicolas Chapurlat <nchapurlat@nuxeo.com>
018 */
019
020package org.nuxeo.ecm.core.io.marshallers.json.types;
021
022import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
023import static org.nuxeo.ecm.core.io.registry.reflect.Instantiations.SINGLETON;
024import static org.nuxeo.ecm.core.io.registry.reflect.Priorities.REFERENCE;
025
026import java.io.IOException;
027import java.io.OutputStream;
028import java.util.Set;
029
030import org.apache.commons.lang.StringUtils;
031import org.codehaus.jackson.JsonGenerator;
032import org.nuxeo.ecm.core.io.marshallers.json.ExtensibleEntityJsonWriter;
033import org.nuxeo.ecm.core.io.marshallers.json.OutputStreamWithJsonWriter;
034import org.nuxeo.ecm.core.io.marshallers.json.enrichers.AbstractJsonEnricher;
035import org.nuxeo.ecm.core.io.registry.Writer;
036import org.nuxeo.ecm.core.io.registry.reflect.Setup;
037import org.nuxeo.ecm.core.schema.types.ComplexType;
038import org.nuxeo.ecm.core.schema.types.Field;
039import org.nuxeo.ecm.core.schema.types.ListType;
040import org.nuxeo.ecm.core.schema.types.PrimitiveType;
041import org.nuxeo.ecm.core.schema.types.Schema;
042import org.nuxeo.ecm.core.schema.types.Type;
043import org.nuxeo.ecm.core.schema.types.constraints.Constraint;
044
045import com.thoughtworks.xstream.io.json.JsonWriter;
046
047/**
048 * Convert {@link Schema} to Json.
049 * <p>
050 * This marshaller is enrichable: register class implementing {@link AbstractJsonEnricher} and managing {@link Schema}.
051 * </p>
052 * <p>
053 * This marshaller is also extensible: extend it and simply override
054 * {@link ExtensibleEntityJsonWriter#extend(Schema, JsonWriter)}.
055 * </p>
056 * <p>
057 * Format is:
058 *
059 * <pre>
060 * {@code
061 * {
062 *   "entity-type":"schema",
063 *   "name": "SCHEMA_NAME",
064 *   "prefix: "SCHEMA_PREFIX",  <- only if there's a prefix
065 *   "fields", {
066 *     "PRIMITIVE_FIELD_LOCAL_NAME": "FIELD_TYPE", <- where field type is {@link Type#getName()} (string, boolean, integer, ...)
067 *     "PRIMITIVE_LIST_LOCAL_NAME": "FIELD_TYPE[]" <- where field type is {@link Type#getName()} (string, boolean, integer, ...)
068 *     "COMPLEX_FIELD_LOCAL_NAME" : {
069 *       "type": "complex",
070 *       "fields": {
071 *         loop the same format
072 *       }
073 *     },
074 *     "COMPLEX_LIST_FIELD_LOCAL_NAME" : {
075 *       "type": "complex[]",
076 *       "fields": {
077 *         loop the same format
078 *       }
079 *     },
080 *     "CONTENT_FIELD": "blob",
081 *     "CONTENT_LIST_FIELD": "blob[]",
082 *     ...
083 *   }
084 *             <-- contextParameters if there are enrichers activated
085 *             <-- additional property provided by extend() method
086 * }
087 * </pre>
088 *
089 * </p>
090 *
091 * @since 7.2
092 */
093@Setup(mode = SINGLETON, priority = REFERENCE)
094public class SchemaJsonWriter extends ExtensibleEntityJsonWriter<Schema> {
095
096    public static final String ENTITY_TYPE = "schema";
097
098    /**
099     * @since 8.10
100     */
101    public static final String FETCH_FIELDS = "fields";
102
103    public SchemaJsonWriter() {
104        super(ENTITY_TYPE, Schema.class);
105    }
106
107    @Override
108    protected void writeEntityBody(Schema schema, JsonGenerator jg) throws IOException {
109        jg.writeStringField("name", schema.getName());
110        String prefix = schema.getNamespace().prefix;
111        if (StringUtils.isNotBlank(prefix)) {
112            jg.writeStringField("prefix", prefix);
113            // backward compat for old schema writers
114            jg.writeStringField("@prefix", prefix);
115        }
116        jg.writeObjectFieldStart("fields");
117        for (Field field : schema.getFields()) {
118            writeField(jg, field);
119        }
120        jg.writeEndObject();
121    }
122
123    protected void writeField(JsonGenerator jg, Field field) throws IOException {
124        if (!field.getType().isComplexType()) {
125            if (field.getType().isListType()) {
126                ListType lt = (ListType) field.getType();
127                if (lt.getFieldType().isComplexType()) {
128                    if (lt.getFieldType().getName().equals("content")) {
129                        jg.writeStringField(field.getName().getLocalName(), "blob[]");
130                    } else {
131                        jg.writeObjectFieldStart(field.getName().getLocalName());
132                        jg.writeStringField("type", "complex[]");
133                        jg.writeObjectFieldStart("fields");
134                        ComplexType cplXType = (ComplexType) lt.getField().getType();
135                        for (Field subField : cplXType.getFields()) {
136                            writeField(jg, subField);
137                        }
138                        jg.writeEndObject();
139                        jg.writeEndObject();
140                    }
141                } else {
142                    doWriteField(jg, field);
143                }
144            } else {
145                doWriteField(jg, field);
146            }
147        } else {
148            if (field.getType().getName().equals("content")) {
149                jg.writeStringField(field.getName().getLocalName(), "blob");
150            } else {
151                jg.writeObjectFieldStart(field.getName().getLocalName());
152                ComplexType cplXType = (ComplexType) field.getType();
153                jg.writeObjectFieldStart("fields");
154                for (Field subField : cplXType.getFields()) {
155                    writeField(jg, subField);
156                }
157                jg.writeEndObject();
158                jg.writeStringField("type", "complex");
159                jg.writeEndObject();
160            }
161        }
162    }
163
164    /**
165     * @since 8.10
166     */
167    protected void doWriteField(JsonGenerator jg, Field field) throws IOException {
168        final boolean extended = ctx.getFetched(ENTITY_TYPE).contains(FETCH_FIELDS);
169        String typeValue;
170        Set<Constraint> itemConstraints = null;
171        if (field.getType().isListType()) {
172            ListType lt = (ListType) field.getType();
173            Type type = lt.getFieldType();
174            itemConstraints = type.getConstraints();
175            while (!(type instanceof PrimitiveType)) {
176                type = type.getSuperType();
177            }
178            typeValue = type.getName() + "[]";
179        } else {
180            Type type = field.getType();
181            while (!(type instanceof PrimitiveType)) {
182                type = type.getSuperType();
183            }
184            typeValue = type.getName();
185        }
186        if (extended) {
187            jg.writeObjectFieldStart(field.getName().getLocalName());
188            jg.writeStringField("type", typeValue);
189            Writer<Constraint> constraintWriter = registry.getWriter(ctx, Constraint.class, APPLICATION_JSON_TYPE);
190            OutputStream out = new OutputStreamWithJsonWriter(jg);
191            jg.writeArrayFieldStart("constraints");
192            for (Constraint c : field.getConstraints()) {
193                constraintWriter.write(c, Constraint.class, Constraint.class, APPLICATION_JSON_TYPE, out);
194            }
195            jg.writeEndArray();
196            if (itemConstraints != null) {
197                jg.writeArrayFieldStart("itemConstraints");
198                for (Constraint c : itemConstraints) {
199                    constraintWriter.write(c, Constraint.class, Constraint.class, APPLICATION_JSON_TYPE, out);
200                }
201                jg.writeEndArray();
202            }
203            jg.writeEndObject();
204        } else {
205            jg.writeStringField(field.getName().getLocalName(), typeValue);
206        }
207
208    }
209
210}