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;
028
029import javax.inject.Inject;
030import javax.ws.rs.core.MediaType;
031
032import org.codehaus.jackson.JsonNode;
033import org.codehaus.jackson.JsonParseException;
034import org.codehaus.jackson.JsonParser;
035import org.codehaus.jackson.JsonProcessingException;
036import org.nuxeo.ecm.core.io.registry.MarshallerRegistry;
037import org.nuxeo.ecm.core.io.registry.Reader;
038import org.nuxeo.ecm.core.io.registry.context.RenderingContext;
039import org.nuxeo.ecm.core.io.registry.reflect.Supports;
040
041/**
042 * Base class for Json {@link Reader}.
043 * <p>
044 * This class provides an easy way to create java object from json and also provides the current context:
045 * {@link AbstractJsonReader#ctx}. It provides you a {@link JsonNode} to manage the unmarshalling.
046 * </p>
047 * <p>
048 * The use of this class optimize the JsonFactory usage especially when aggregating unmarshallers.
049 * </p>
050 *
051 * @param <EntityType> The expected Java type.
052 * @since 7.2
053 */
054@Supports(APPLICATION_JSON)
055public abstract class AbstractJsonReader<EntityType> implements Reader<EntityType> {
056
057    /**
058     * The current {@link RenderingContext}.
059     */
060    @Inject
061    protected RenderingContext ctx;
062
063    /**
064     * The marshaller registry. You may use it to use other marshallers.
065     */
066    @Inject
067    protected MarshallerRegistry registry;
068
069    @Override
070    public boolean accept(Class<?> clazz, Type genericType, MediaType mediatype) {
071        return true;
072    }
073
074    @Override
075    public EntityType read(Class<?> clazz, Type genericType, MediaType mediaType, InputStream in) throws IOException {
076        JsonNode jn = getNode(in, true);
077        return read(jn);
078    }
079
080    /**
081     * Provide a {@link JsonNode}, try to get it from the context.
082     *
083     * @param in The current {@link InputStream}.
084     * @param getCurrentIfAvailable If true, try to get it from the context (if another marshaller already create it and
085     *            call this marshaller).
086     * @return A valid {@link JsonNode}.
087     * @since 7.2
088     */
089    protected JsonNode getNode(InputStream in, boolean getCurrentIfAvailable) throws IOException, JsonParseException,
090            JsonProcessingException {
091        if (getCurrentIfAvailable) {
092            if (in instanceof InputStreamWithJsonNode) {
093                return ((InputStreamWithJsonNode) in).getJsonNode();
094            }
095        }
096        JsonParser jp = JsonFactoryProvider.get().createJsonParser(in);
097        JsonNode jn = jp.readValueAsTree();
098        return jn;
099    }
100
101    /**
102     * Implement this method, read the entity data in the provided {@link JsonNode} and return corresponding java
103     * object.
104     *
105     * @param jn A ready to use {@link JsonNode}.
106     * @return The unmarshalled entity.
107     * @since 7.2
108     */
109    public abstract EntityType read(JsonNode jn) throws IOException;
110
111    /**
112     * Use this method to delegate the unmarshalling of a part or your Json to the {@link MarshallerRegistry}. This will
113     * work only if a Json {@link Reader} is registered for the provided clazz and if the node format is the same as the
114     * one expected by the marshaller.
115     *
116     * @param clazz The expected Java class.
117     * @param genericType The generic type of the expected object: usefull if it's a generic List for example (use
118     *            TypeUtils to create the parametrize type).
119     * @param jn The {@link JsonNode} to unmarshall.
120     * @return An object implementing the expected clazz.
121     * @since 7.2
122     */
123    @SuppressWarnings("unchecked")
124    protected <T> T readEntity(Class<?> clazz, Type genericType, JsonNode jn) throws IOException {
125        Type effectiveGenericType = genericType != null ? genericType : clazz;
126        Reader<T> reader = (Reader<T>) registry.getReader(ctx, clazz, effectiveGenericType, APPLICATION_JSON_TYPE);
127        return reader.read(clazz, effectiveGenericType, APPLICATION_JSON_TYPE, new InputStreamWithJsonNode(jn));
128    }
129
130    /**
131     * Try to get a string property of the given {@link JsonNode}. Return null if the node is null.
132     *
133     * @param jn The {@link JsonNode} to parse.
134     * @param elName The property name.
135     * @return The property text if it exists and it's a text, null otherwise.
136     * @since 7.2
137     */
138    protected String getStringField(JsonNode jn, String elName) {
139        JsonNode elNode = jn.get(elName);
140        if (elNode != null && !elNode.isNull() && elNode.isTextual()) {
141            return elNode.getTextValue();
142        } else {
143            return null;
144        }
145    }
146
147}