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; 021 022import static javax.ws.rs.core.MediaType.APPLICATION_JSON; 023import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; 024 025import java.io.IOException; 026import java.io.OutputStream; 027import java.io.Serializable; 028import java.lang.reflect.Type; 029import java.util.Collection; 030import java.util.Map; 031import java.util.Map.Entry; 032 033import javax.inject.Inject; 034import javax.ws.rs.core.MediaType; 035 036import org.nuxeo.ecm.core.io.registry.MarshallerRegistry; 037import org.nuxeo.ecm.core.io.registry.MarshallingException; 038import org.nuxeo.ecm.core.io.registry.Writer; 039import org.nuxeo.ecm.core.io.registry.context.RenderingContext; 040import org.nuxeo.ecm.core.io.registry.reflect.Supports; 041 042import com.fasterxml.jackson.core.JsonGenerator; 043 044/** 045 * Base class for Json {@link Writer}. 046 * <p> 047 * This class provides a easy way to produce json and also provides the current context: {@link AbstractJsonWriter#ctx}. 048 * It provides you a {@link JsonGenerator} to manage the marshalling. 049 * </p> 050 * <p> 051 * The use of this class optimize the JsonFactory usage especially when aggregating marshallers. 052 * </p> 053 * 054 * @param <EntityType> The Java type to marshall as Json. 055 * @since 7.2 056 */ 057@Supports(APPLICATION_JSON) 058public abstract class AbstractJsonWriter<EntityType> implements Writer<EntityType> { 059 060 /** 061 * The current {@link RenderingContext}. 062 */ 063 @Inject 064 protected RenderingContext ctx; 065 066 /** 067 * The marshaller registry. 068 */ 069 @Inject 070 protected MarshallerRegistry registry; 071 072 @Override 073 public boolean accept(Class<?> clazz, Type genericType, MediaType mediatype) { 074 return true; 075 } 076 077 @Override 078 public void write(EntityType entity, Class<?> clazz, Type genericType, MediaType mediatype, OutputStream out) 079 throws IOException { 080 JsonGenerator jg = getGenerator(out, true); 081 write(entity, jg); 082 jg.flush(); 083 } 084 085 /** 086 * Implement this method to writes the entity in the provided {@link JsonGenerator}. 087 * <p> 088 * This method implementation can use injected properties. 089 * </p> 090 * <p> 091 * The {@link JsonGenerator}'s flushing is done by this abstract class, it's also not not necessary to flush it. Do 092 * not close the provided {@link JsonGenerator}. It may be used is another marshaller calling this one. 093 * </p> 094 * 095 * @param entity The entity to marshall as Json. 096 * @param jg The {@link JsonGenerator} used to produce Json output. 097 * @since 7.2 098 */ 099 public abstract void write(EntityType entity, JsonGenerator jg) throws IOException; 100 101 /** 102 * Delegates writing of an entity to the {@link MarshallerRegistry}. This will work if a Json {@link Writer} is 103 * registered in the registry for the given clazz. 104 * 105 * @param fieldName The name of the Json field in which the entity will be wrote. 106 * @param entity The entity to write. 107 * @param jg The {@link JsonGenerator} used to write the given entity. 108 * @since 7.2 109 */ 110 protected void writeEntityField(String fieldName, Object entity, JsonGenerator jg) throws IOException { 111 jg.writeFieldName(fieldName); 112 writeEntity(entity, jg); 113 } 114 115 /** 116 * Delegates writing of an entity to the {@link MarshallerRegistry}. This will work if a Json {@link Writer} is 117 * registered in the registry for the given clazz. 118 * 119 * @param entity The entity to write. 120 * @param jg The {@link JsonGenerator} used to write the given entity. 121 * @since 7.2 122 */ 123 protected void writeEntity(Object entity, JsonGenerator jg) throws IOException { 124 writeEntity(entity, new OutputStreamWithJsonWriter(jg)); 125 } 126 127 /** 128 * Delegates writing of an entity to the {@link MarshallerRegistry}. This will work if a Json {@link Writer} is 129 * registered in the registry for the given clazz. 130 * 131 * @param entity The entity to write. 132 * @param out The {@link OutputStream} in which the given entity will be wrote. 133 * @throws IOException If some i/o error append while writing entity. 134 * @since 7.2 135 */ 136 protected <ObjectType> void writeEntity(ObjectType entity, OutputStream out) throws IOException { 137 @SuppressWarnings("unchecked") 138 Class<ObjectType> clazz = (Class<ObjectType>) entity.getClass(); 139 Writer<ObjectType> writer = registry.getWriter(ctx, clazz, APPLICATION_JSON_TYPE); 140 if (writer == null) { 141 throw new MarshallingException("Unable to get a writer for Java type " + entity.getClass() 142 + " and mimetype " + APPLICATION_JSON_TYPE); 143 } 144 writer.write(entity, entity.getClass(), entity.getClass(), APPLICATION_JSON_TYPE, out); 145 } 146 147 /** 148 * Get the current Json generator or create it if none was found. 149 * 150 * @param out The {@link OutputStream} on which the generator will generate Json. 151 * @param getCurrentIfAvailable If true, try to get the current generator in the context. 152 * @return The created generator. 153 * @since 7.2 154 */ 155 protected JsonGenerator getGenerator(OutputStream out, boolean getCurrentIfAvailable) throws IOException { 156 if (getCurrentIfAvailable && out instanceof OutputStreamWithJsonWriter) { 157 OutputStreamWithJsonWriter casted = (OutputStreamWithJsonWriter) out; 158 return casted.getJsonGenerator(); 159 } 160 return JsonFactoryProvider.get().createGenerator(out); 161 } 162 163 /** 164 * Writes a list of {@link Serializable}. 165 * 166 * @param fieldName The name of the Json field in which the serializables will be wrote. 167 * @param serializables The serializables to write. 168 * @param jg The {@link JsonGenerator} used to write the given serializables. 169 * @since 10.1 170 */ 171 protected <T extends Serializable> void writeSerializableListField(String fieldName, Collection<T> serializables, 172 JsonGenerator jg) throws IOException { 173 jg.writeArrayFieldStart(fieldName); 174 for (T serializable : serializables) { 175 writeSerializable(serializable, jg); 176 } 177 jg.writeEndArray(); 178 } 179 180 /** 181 * Writes a map whose values are {@link Serializable}. 182 * 183 * @param fieldName The name of the Json field in which the serializables will be wrote. 184 * @param map The map to write. 185 * @param jg The {@link JsonGenerator} used to write the given map. 186 * @since 10.1 187 */ 188 protected <T extends Serializable> void writeSerializableMapField(String fieldName, Map<String, T> map, 189 JsonGenerator jg) throws IOException { 190 jg.writeObjectFieldStart(fieldName); 191 for (Entry<String, T> entry : map.entrySet()) { 192 writeSerializableField(entry.getKey(), entry.getValue(), jg); 193 } 194 jg.writeEndObject(); 195 } 196 197 /** 198 * Writes a {@link Serializable}. 199 * <p> 200 * This method will first try to cast value to {@link Collection}, array, {@link String}, {@link Boolean} and 201 * {@link Number}. If none of previous cast could work, try to write it with marshallers 202 * 203 * @param fieldName The name of the Json field in which the serializable will be wrote. 204 * @param value The value to write. 205 * @param jg The {@link JsonGenerator} used to write the given serializable. 206 * @since 10.1 207 */ 208 protected void writeSerializableField(String fieldName, Serializable value, JsonGenerator jg) throws IOException { 209 jg.writeFieldName(fieldName); 210 writeSerializable(value, jg); 211 } 212 213 /** 214 * Writes a {@link Serializable}. 215 * <p> 216 * This method will first try to cast value to {@link Collection}, array, {@link String}, {@link Boolean} and 217 * {@link Number}. If none of previous cast could work, try to write it with marshallers 218 * 219 * @param value The value to write. 220 * @param jg The {@link JsonGenerator} used to write the given serializable. 221 * @since 10.1 222 */ 223 @SuppressWarnings("unchecked") 224 protected void writeSerializable(Serializable value, JsonGenerator jg) throws IOException { 225 if (value instanceof Collection) { 226 jg.writeStartArray(); 227 for (Serializable serializable : (Collection<Serializable>) value) { 228 writeSerializable(serializable, jg); 229 } 230 jg.writeEndArray(); 231 } else if (value instanceof Serializable[]) { 232 jg.writeStartArray(); 233 for (Serializable serializable : (Serializable[]) value) { 234 writeSerializable(serializable, jg); 235 } 236 jg.writeEndArray(); 237 } else if (value instanceof String) { 238 jg.writeString((String) value); 239 } else if (value instanceof Boolean) { 240 jg.writeBoolean((boolean) value); 241 } else if (value instanceof Number) { 242 jg.writeNumber(value.toString()); 243 } else { 244 // try with marshallers 245 writeEntity(value, jg); 246 } 247 } 248 249}