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.InputStream; 027import java.lang.reflect.Type; 028import java.util.ArrayList; 029import java.util.List; 030 031import javax.inject.Inject; 032import javax.ws.rs.core.MediaType; 033 034import org.nuxeo.ecm.core.io.registry.MarshallerRegistry; 035import org.nuxeo.ecm.core.io.registry.Reader; 036import org.nuxeo.ecm.core.io.registry.context.RenderingContext; 037import org.nuxeo.ecm.core.io.registry.reflect.Supports; 038 039import com.fasterxml.jackson.core.JsonParseException; 040import com.fasterxml.jackson.core.JsonParser; 041import com.fasterxml.jackson.core.JsonProcessingException; 042import com.fasterxml.jackson.databind.JsonNode; 043 044/** 045 * Base class for Json {@link Reader}. 046 * <p> 047 * This class provides an easy way to create java object from json and also provides the current context: 048 * {@link AbstractJsonReader#ctx}. It provides you a {@link JsonNode} to manage the unmarshalling. 049 * </p> 050 * <p> 051 * The use of this class optimize the JsonFactory usage especially when aggregating unmarshallers. 052 * </p> 053 * 054 * @param <EntityType> The expected Java type. 055 * @since 7.2 056 */ 057@Supports(APPLICATION_JSON) 058public abstract class AbstractJsonReader<EntityType> implements Reader<EntityType> { 059 060 /** 061 * The current {@link RenderingContext}. 062 */ 063 @Inject 064 protected RenderingContext ctx; 065 066 /** 067 * The marshaller registry. You may use it to use other marshallers. 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 EntityType read(Class<?> clazz, Type genericType, MediaType mediaType, InputStream in) throws IOException { 079 JsonNode jn = getNode(in, true); 080 return read(jn); 081 } 082 083 /** 084 * Provide a {@link JsonNode}, try to get it from the context. 085 * 086 * @param in The current {@link InputStream}. 087 * @param getCurrentIfAvailable If true, try to get it from the context (if another marshaller already create it and 088 * call this marshaller). 089 * @return A valid {@link JsonNode}. 090 * @since 7.2 091 */ 092 protected JsonNode getNode(InputStream in, boolean getCurrentIfAvailable) 093 throws IOException, JsonParseException, JsonProcessingException { 094 if (getCurrentIfAvailable && in instanceof InputStreamWithJsonNode) { 095 return ((InputStreamWithJsonNode) in).getJsonNode(); 096 } 097 try (JsonParser jp = JsonFactoryProvider.get().createParser(in)) { 098 return jp.readValueAsTree(); 099 } 100 } 101 102 /** 103 * Implement this method, read the entity data in the provided {@link JsonNode} and return corresponding java 104 * object. 105 * 106 * @param jn A ready to use {@link JsonNode}. 107 * @return The unmarshalled entity. 108 * @since 7.2 109 */ 110 public abstract EntityType read(JsonNode jn) throws IOException; 111 112 /** 113 * Use this method to delegate the unmarshalling of a part or your Json to the {@link MarshallerRegistry}. This will 114 * work only if a Json {@link Reader} is registered for the provided clazz and if the node format is the same as the 115 * one expected by the marshaller. 116 * 117 * @param clazz The expected Java class. 118 * @param genericType The generic type of the expected object: usefull if it's a generic List for example (use 119 * TypeUtils to create the parametrize type). 120 * @param jn The {@link JsonNode} to unmarshall. 121 * @return An object implementing the expected clazz. 122 * @since 7.2 123 */ 124 @SuppressWarnings("unchecked") 125 protected <T> T readEntity(Class<?> clazz, Type genericType, JsonNode jn) throws IOException { 126 Type effectiveGenericType = genericType != null ? genericType : clazz; 127 Reader<T> reader = (Reader<T>) registry.getReader(ctx, clazz, effectiveGenericType, APPLICATION_JSON_TYPE); 128 return reader.read(clazz, effectiveGenericType, APPLICATION_JSON_TYPE, new InputStreamWithJsonNode(jn)); 129 } 130 131 /** 132 * Try to get a string property of the given {@link JsonNode}. Return null if the node is null. 133 * 134 * @param jn The {@link JsonNode} to parse. 135 * @param elName The property name. 136 * @return The property text if it exists and it's a text, null otherwise. 137 * @since 7.2 138 */ 139 protected String getStringField(JsonNode jn, String elName) { 140 JsonNode elNode = jn.get(elName); 141 if (elNode != null && !elNode.isNull() && elNode.isTextual()) { 142 return elNode.textValue(); 143 } else { 144 return null; 145 } 146 } 147 148 /** 149 * Tries to get a boolean property of the given {@link JsonNode}. Return {@code null} if the node is {@code null} or 150 * not a boolean. 151 * 152 * @param jn the {@link JsonNode} to parse 153 * @param elName the property name 154 * @return the boolean value if it exists and is a boolean property, {@code null} otherwise 155 * @since 9.2 156 */ 157 protected Boolean getBooleanField(JsonNode jn, String elName) { 158 JsonNode elNode = jn.get(elName); 159 if (elNode != null && !elNode.isNull()) { 160 if (elNode.isBoolean()) { 161 return elNode.booleanValue(); 162 } else if (elNode.isTextual()) { 163 return Boolean.valueOf(elNode.textValue()); 164 } else { 165 return null; 166 } 167 } else { 168 return null; 169 } 170 } 171 172 /** 173 * Tries to get a long property of the given {@link JsonNode}. Return {@code null} if the node is {@code null} or 174 * not a number. 175 * 176 * @param jn the {@link JsonNode} to parse 177 * @param elName the property name 178 * @return the long value if it exists and is a long property, {@code null} otherwise 179 * @since 10.2 180 */ 181 protected Long getLongField(JsonNode jn, String elName) { 182 JsonNode elNode = jn.get(elName); 183 if (elNode != null && !elNode.isNull()) { 184 if (elNode.isNumber()) { 185 return elNode.longValue(); 186 } else if (elNode.isTextual()) { 187 return Long.valueOf(elNode.textValue()); 188 } else { 189 return null; 190 } 191 } else { 192 return null; 193 } 194 } 195 196 /** 197 * Tries to get a string list property of the given {@link JsonNode}. Return {@code null} if the node is 198 * {@code null} or not a string list. 199 * 200 * @param jn the {@link JsonNode} to parse 201 * @param elName the property name 202 * @return a string list if it exists and is a valid string list property, {@code null} otherwise 203 * @since 9.2 204 */ 205 protected List<String> getStringListField(JsonNode jn, String elName) { 206 JsonNode elNode = jn.get(elName); 207 if (elNode != null && !elNode.isNull()) { 208 if (elNode.isArray()) { 209 List<String> result = new ArrayList<>(); 210 String value; 211 for (JsonNode subNode : elNode) { 212 if (subNode != null && !subNode.isNull() && subNode.isTextual()) { 213 value = subNode.textValue(); 214 } else { 215 value = null; 216 } 217 result.add(value); 218 } 219 return result; 220 } else { 221 return null; 222 } 223 } else { 224 return null; 225 } 226 } 227 228}