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