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;
028
029import org.apache.commons.lang.StringUtils;
030import org.codehaus.jackson.JsonGenerator;
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.thoughtworks.xstream.io.json.JsonWriter;
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(Schema, JsonWriter)}.
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 *
088 * </p>
089 *
090 * @since 7.2
091 */
092@Setup(mode = SINGLETON, priority = REFERENCE)
093public class SchemaJsonWriter extends ExtensibleEntityJsonWriter<Schema> {
094
095    public static final String ENTITY_TYPE = "schema";
096
097    /**
098     * @since 8.10
099     */
100    public static final String FETCH_FIELDS = "fields";
101
102    public SchemaJsonWriter() {
103        super(ENTITY_TYPE, Schema.class);
104    }
105
106    @Override
107    protected void writeEntityBody(Schema schema, JsonGenerator jg) throws IOException {
108        jg.writeStringField("name", schema.getName());
109        String prefix = schema.getNamespace().prefix;
110        if (StringUtils.isNotBlank(prefix)) {
111            jg.writeStringField("prefix", prefix);
112            // backward compat for old schema writers
113            jg.writeStringField("@prefix", prefix);
114        }
115        jg.writeObjectFieldStart("fields");
116        for (Field field : schema.getFields()) {
117            writeField(jg, field);
118        }
119        jg.writeEndObject();
120    }
121
122    protected void writeField(JsonGenerator jg, Field field) throws IOException {
123        if (!field.getType().isComplexType()) {
124            if (field.getType().isListType()) {
125                ListType lt = (ListType) field.getType();
126                if (lt.getFieldType().isComplexType()) {
127                    if (lt.getFieldType().getName().equals("content")) {
128                        jg.writeStringField(field.getName().getLocalName(), "blob[]");
129                    } else {
130                        jg.writeObjectFieldStart(field.getName().getLocalName());
131                        jg.writeStringField("type", "complex[]");
132                        jg.writeObjectFieldStart("fields");
133                        ComplexType cplXType = (ComplexType) lt.getField().getType();
134                        for (Field subField : cplXType.getFields()) {
135                            writeField(jg, subField);
136                        }
137                        jg.writeEndObject();
138                        jg.writeEndObject();
139                    }
140                } else {
141                    final boolean isFetchFields = ctx.getFetched(ENTITY_TYPE).contains(FETCH_FIELDS);
142                    Type type = lt.getFieldType();
143                    while (!(type instanceof PrimitiveType)) {
144                        type = type.getSuperType();
145                    }
146                    doWriteField(jg, field, type.getName() + "[]", isFetchFields);
147                }
148            } else {
149                final boolean isFetchFields = ctx.getFetched(ENTITY_TYPE).contains(FETCH_FIELDS);
150                Type type = field.getType();
151                while (!(type instanceof PrimitiveType)) {
152                    type = type.getSuperType();
153                }
154                doWriteField(jg, field, type.getName(), isFetchFields);
155            }
156        } else {
157            if (field.getType().getName().equals("content")) {
158                jg.writeStringField(field.getName().getLocalName(), "blob");
159            } else {
160                jg.writeObjectFieldStart(field.getName().getLocalName());
161                ComplexType cplXType = (ComplexType) field.getType();
162                jg.writeObjectFieldStart("fields");
163                for (Field subField : cplXType.getFields()) {
164                    writeField(jg, subField);
165                }
166                jg.writeEndObject();
167                jg.writeStringField("type", "complex");
168                jg.writeEndObject();
169            }
170        }
171    }
172
173    /**
174     * @since 8.10
175     */
176    protected void doWriteField(JsonGenerator jg, Field field, String typeValue, boolean extended) throws IOException {
177        if (extended) {
178            jg.writeObjectFieldStart(field.getName().getLocalName());
179            jg.writeStringField("type", typeValue);
180            Writer<Constraint> constraintWriter = registry.getWriter(ctx, Constraint.class, APPLICATION_JSON_TYPE);
181            OutputStream out = new OutputStreamWithJsonWriter(jg);
182            jg.writeArrayFieldStart("constraints");
183            for (Constraint c : field.getConstraints()) {
184                constraintWriter.write(c, Constraint.class, Constraint.class, APPLICATION_JSON_TYPE, out);
185            }
186            jg.writeEndArray();
187            jg.writeEndObject();
188        } else {
189            jg.writeStringField(field.getName().getLocalName(), typeValue);
190        }
191
192    }
193
194}