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", <- only if there's a prefix 063 * "fields", { 064 * "PRIMITIVE_FIELD_LOCAL_NAME": "FIELD_TYPE", <- where field type is {@link Type#getName()} (string, boolean, integer, ...) 065 * "PRIMITIVE_LIST_LOCAL_NAME": "FIELD_TYPE[]" <- 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 * <-- contextParameters if there are enrichers activated 083 * <-- 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}