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 try (InputStream in = new InputStreamWithJsonNode(jn)) { 129 return reader.read(clazz, effectiveGenericType, APPLICATION_JSON_TYPE, in); 130 } 131 } 132 133 /** 134 * Try to get a string property of the given {@link JsonNode}. Return null if the node is null. 135 * 136 * @param jn The {@link JsonNode} to parse. 137 * @param elName The property name. 138 * @return The property text if it exists and it's a text, null otherwise. 139 * @since 7.2 140 */ 141 protected String getStringField(JsonNode jn, String elName) { 142 JsonNode elNode = jn.get(elName); 143 if (elNode != null && !elNode.isNull() && elNode.isTextual()) { 144 return elNode.textValue(); 145 } else { 146 return null; 147 } 148 } 149 150 /** 151 * Tries to get a boolean property of the given {@link JsonNode}. Return {@code null} if the node is {@code null} or 152 * not a boolean. 153 * 154 * @param jn the {@link JsonNode} to parse 155 * @param elName the property name 156 * @return the boolean value if it exists and is a boolean property, {@code null} otherwise 157 * @since 9.2 158 */ 159 protected Boolean getBooleanField(JsonNode jn, String elName) { 160 JsonNode elNode = jn.get(elName); 161 if (elNode != null && !elNode.isNull()) { 162 if (elNode.isBoolean()) { 163 return elNode.booleanValue(); 164 } else if (elNode.isTextual()) { 165 return Boolean.valueOf(elNode.textValue()); 166 } else { 167 return null; 168 } 169 } else { 170 return null; 171 } 172 } 173 174 /** 175 * Tries to get a long property of the given {@link JsonNode}. Return {@code null} if the node is {@code null} or 176 * not a number. 177 * 178 * @param jn the {@link JsonNode} to parse 179 * @param elName the property name 180 * @return the long value if it exists and is a long property, {@code null} otherwise 181 * @since 10.2 182 */ 183 protected Long getLongField(JsonNode jn, String elName) { 184 JsonNode elNode = jn.get(elName); 185 if (elNode != null && !elNode.isNull()) { 186 if (elNode.isNumber()) { 187 return elNode.longValue(); 188 } else if (elNode.isTextual()) { 189 return Long.valueOf(elNode.textValue()); 190 } else { 191 return null; 192 } 193 } else { 194 return null; 195 } 196 } 197 198 /** 199 * Tries to get a string list property of the given {@link JsonNode}. Return {@code null} if the node is 200 * {@code null} or not a string list. 201 * 202 * @param jn the {@link JsonNode} to parse 203 * @param elName the property name 204 * @return a string list if it exists and is a valid string list property, {@code null} otherwise 205 * @since 9.2 206 */ 207 protected List<String> getStringListField(JsonNode jn, String elName) { 208 JsonNode elNode = jn.get(elName); 209 if (elNode != null && !elNode.isNull()) { 210 if (elNode.isArray()) { 211 List<String> result = new ArrayList<>(); 212 String value; 213 for (JsonNode subNode : elNode) { 214 if (subNode != null && !subNode.isNull() && subNode.isTextual()) { 215 value = subNode.textValue(); 216 } else { 217 value = null; 218 } 219 result.add(value); 220 } 221 return result; 222 } else { 223 return null; 224 } 225 } else { 226 return null; 227 } 228 } 229 230}