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