001/*
002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Nicolas Chapurlat <nchapurlat@nuxeo.com>
016 */
017
018package org.nuxeo.ecm.core.io.marshallers.json;
019
020import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
021import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
022
023import java.io.IOException;
024import java.io.InputStream;
025import java.lang.reflect.Type;
026
027import javax.inject.Inject;
028import javax.ws.rs.core.MediaType;
029
030import org.codehaus.jackson.JsonNode;
031import org.codehaus.jackson.JsonParseException;
032import org.codehaus.jackson.JsonParser;
033import org.codehaus.jackson.JsonProcessingException;
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
039/**
040 * Base class for Json {@link Reader}.
041 * <p>
042 * This class provides an easy way to create java object from json and also provides the current context:
043 * {@link AbstractJsonReader#ctx}. It provides you a {@link JsonNode} to manage the unmarshalling.
044 * </p>
045 * <p>
046 * The use of this class optimize the JsonFactory usage especially when aggregating unmarshallers.
047 * </p>
048 *
049 * @param <EntityType> The expected Java type.
050 * @since 7.2
051 */
052@Supports(APPLICATION_JSON)
053public abstract class AbstractJsonReader<EntityType> implements Reader<EntityType> {
054
055    /**
056     * The current {@link RenderingContext}.
057     */
058    @Inject
059    protected RenderingContext ctx;
060
061    /**
062     * The marshaller registry. You may use it to use other marshallers.
063     */
064    @Inject
065    protected MarshallerRegistry registry;
066
067    @Override
068    public boolean accept(Class<?> clazz, Type genericType, MediaType mediatype) {
069        return true;
070    }
071
072    @Override
073    public EntityType read(Class<?> clazz, Type genericType, MediaType mediaType, InputStream in) throws IOException {
074        JsonNode jn = getNode(in, true);
075        return read(jn);
076    }
077
078    /**
079     * Provide a {@link JsonNode}, try to get it from the context.
080     *
081     * @param in The current {@link InputStream}.
082     * @param getCurrentIfAvailable If true, try to get it from the context (if another marshaller already create it and
083     *            call this marshaller).
084     * @return A valid {@link JsonNode}.
085     * @since 7.2
086     */
087    protected JsonNode getNode(InputStream in, boolean getCurrentIfAvailable) throws IOException, JsonParseException,
088            JsonProcessingException {
089        if (getCurrentIfAvailable) {
090            if (in instanceof InputStreamWithJsonNode) {
091                return ((InputStreamWithJsonNode) in).getJsonNode();
092            }
093        }
094        JsonParser jp = JsonFactoryProvider.get().createJsonParser(in);
095        JsonNode jn = jp.readValueAsTree();
096        return jn;
097    }
098
099    /**
100     * Implement this method, read the entity data in the provided {@link JsonNode} and return corresponding java
101     * object.
102     *
103     * @param jn A ready to use {@link JsonNode}.
104     * @return The unmarshalled entity.
105     * @since 7.2
106     */
107    public abstract EntityType read(JsonNode jn) throws IOException;
108
109    /**
110     * Use this method to delegate the unmarshalling of a part or your Json to the {@link MarshallerRegistry}. This will
111     * work only if a Json {@link Reader} is registered for the provided clazz and if the node format is the same as the
112     * one expected by the marshaller.
113     *
114     * @param clazz The expected Java class.
115     * @param genericType The generic type of the expected object: usefull if it's a generic List for example (use
116     *            TypeUtils to create the parametrize type).
117     * @param jn The {@link JsonNode} to unmarshall.
118     * @return An object implementing the expected clazz.
119     * @since 7.2
120     */
121    @SuppressWarnings("unchecked")
122    protected <T> T readEntity(Class<?> clazz, Type genericType, JsonNode jn) throws IOException {
123        Type effectiveGenericType = genericType != null ? genericType : clazz;
124        Reader<T> reader = (Reader<T>) registry.getReader(ctx, clazz, effectiveGenericType, APPLICATION_JSON_TYPE);
125        return reader.read(clazz, effectiveGenericType, APPLICATION_JSON_TYPE, new InputStreamWithJsonNode(jn));
126    }
127
128    /**
129     * Try to get a string property of the given {@link JsonNode}. Return null if the node is null.
130     *
131     * @param jn The {@link JsonNode} to parse.
132     * @param elName The property name.
133     * @return The property text if it exists and it's a text, null otherwise.
134     * @since 7.2
135     */
136    protected String getStringField(JsonNode jn, String elName) {
137        JsonNode elNode = jn.get(elName);
138        if (elNode != null && !elNode.isNull() && elNode.isTextual()) {
139            return elNode.getTextValue();
140        } else {
141            return null;
142        }
143    }
144
145}