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