/******************************************************************************* * Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2015-2019) * * contact.vitam@culture.gouv.fr * * This software is a computer program whose purpose is to implement a digital archiving back-office system managing * high volumetry securely and efficiently. * * This software is governed by the CeCILL 2.1 license under French law and abiding by the rules of distribution of free * software. You can use, modify and/ or redistribute the software under the terms of the CeCILL 2.1 license as * circulated by CEA, CNRS and INRIA at the following URL "http://www.cecill.info". * * As a counterpart to the access to the source code and rights to copy, modify and redistribute granted by the license, * users are provided only with a limited warranty and the software's author, the holder of the economic rights, and the * successive licensors have only limited liability. * * In this respect, the user's attention is drawn to the risks associated with loading, using, modifying and/or * developing or reproducing the software by the user in light of its specific status of free software, that may mean * that it is complicated to manipulate, and that also therefore means that it is reserved for developers and * experienced professionals having in-depth computer knowledge. Users are therefore encouraged to load and test the * software's suitability as regards their requirements in conditions enabling the security of their systems and/or data * to be ensured and, more generally, to use and operate it in the same conditions as regards security. * * The fact that you are presently reading this means that you have had knowledge of the CeCILL 2.1 license and that you * accept its terms. *******************************************************************************/ package fr.gouv.vitam.common.json; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.google.common.collect.Lists; import fr.gouv.vitam.common.ParametersChecker; import fr.gouv.vitam.common.exception.InvalidParseOperationException; import fr.gouv.vitam.common.logging.SysErrLogger; import fr.gouv.vitam.common.logging.VitamLogger; import fr.gouv.vitam.common.logging.VitamLoggerFactory; /** * JSON handler using Json format * * * */ public final class JsonHandler { private static final VitamLogger LOGGER = VitamLoggerFactory.getInstance(JsonHandler.class); private static final String OBJECT = "object"; /** * Default JsonFactory */ private static final JsonFactory JSONFACTORY = new JsonFactory(); /** * Default ObjectMapper */ private static final ObjectMapper OBJECT_MAPPER; /** * Default ObjectMapperUnprettyPrint */ private static final ObjectMapper OBJECT_MAPPER_UNPRETTY; /** * Default ObjectMapperLowerCamelCase */ private static final ObjectMapper OBJECT_MAPPER_LOWER_CAMEL_CASE; static { OBJECT_MAPPER = buildObjectMapper(); OBJECT_MAPPER_UNPRETTY = buildObjectMapper(); OBJECT_MAPPER_UNPRETTY.disable(SerializationFeature.INDENT_OUTPUT); OBJECT_MAPPER_LOWER_CAMEL_CASE = buildObjectMapper(); OBJECT_MAPPER_LOWER_CAMEL_CASE.setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE); OBJECT_MAPPER_LOWER_CAMEL_CASE.disable(SerializationFeature.INDENT_OUTPUT); } private JsonHandler() { // Empty constructor } private static final ObjectMapper buildObjectMapper() { final ObjectMapper objectMapper = new ObjectMapper(JSONFACTORY); objectMapper.registerModule(new JavaTimeModule()); objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.UPPER_CAMEL_CASE); objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); objectMapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, true); objectMapper.configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, true); objectMapper.configure(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED, false); objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); objectMapper.configure(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS, true); objectMapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true); return objectMapper; } /** * @return the current factory */ public static final JsonNodeFactory getFactory() { return OBJECT_MAPPER.getNodeFactory(); } /** * * @return an empty ObjectNode */ public static final ObjectNode createObjectNode() { return OBJECT_MAPPER.createObjectNode(); } /** * @return an empty ArrayNode */ public static final ArrayNode createArrayNode() { return OBJECT_MAPPER.createArrayNode(); } /** * * @param value * @return the jsonNode (ObjectNode or ArrayNode) * @throws InvalidParseOperationException */ public static final JsonNode getFromString(final String value) throws InvalidParseOperationException { try { ParametersChecker.checkParameter("value", value); return OBJECT_MAPPER.readTree(value); } catch (final IOException | IllegalArgumentException e) { throw new InvalidParseOperationException(e); } } /** * * @param file * @return the jsonNode (ObjectNode or ArrayNode) * @throws InvalidParseOperationException */ public static final JsonNode getFromFile(final File file) throws InvalidParseOperationException { try { ParametersChecker.checkParameter("File", file); return OBJECT_MAPPER.readTree(file); } catch (final IOException | IllegalArgumentException e) { throw new InvalidParseOperationException(e); } } /** * * @param stream * @return the jsonNode (ObjectNode or ArrayNode) * @throws InvalidParseOperationException */ public static final JsonNode getFromInputStream(final InputStream stream) throws InvalidParseOperationException { try { ParametersChecker.checkParameter("InputStream", stream); return OBJECT_MAPPER.readTree(stream); } catch (final IOException | IllegalArgumentException e) { throw new InvalidParseOperationException(e); } } /** * * @param value * @param clasz * @return the object of type clasz * @throws InvalidParseOperationException */ public static final <T> T getFromString(final String value, final Class<T> clasz) throws InvalidParseOperationException { try { ParametersChecker.checkParameter("value or class", value, clasz); return OBJECT_MAPPER.readValue(value, clasz); } catch (final IOException | IllegalArgumentException e) { throw new InvalidParseOperationException(e); } } /** * * @param value * @param clasz * @param parameterClazz * @return the object of type clasz * @throws InvalidParseOperationException */ public static final <T> T getFromString(final String value, final Class<T> clasz, Class<?> parameterClazz ) throws InvalidParseOperationException { try { ParametersChecker.checkParameter("value, class or parameterClazz", value, clasz, parameterClazz); JavaType type = OBJECT_MAPPER.getTypeFactory().constructParametricType(clasz, parameterClazz); return OBJECT_MAPPER.readValue(value, type); } catch (final IOException | IllegalArgumentException e) { throw new InvalidParseOperationException(e); } } /** * * @param value * @param clasz * @return the object of type clasz * @throws InvalidParseOperationException */ public static final <T> T getFromStringLowerCamelCase(final String value, final Class<T> clasz) throws InvalidParseOperationException { try { ParametersChecker.checkParameter("value or class", value, clasz); return OBJECT_MAPPER_LOWER_CAMEL_CASE.readValue(value, clasz); } catch (final IOException | IllegalArgumentException e) { throw new InvalidParseOperationException(e); } } /** * * @param value * @return the jsonNode (ObjectNode or ArrayNode) * @throws InvalidParseOperationException */ public static final JsonNode getFromBytes(final byte[] value) throws InvalidParseOperationException { try { ParametersChecker.checkParameter("value", value); return OBJECT_MAPPER.readTree(value); } catch (final IOException | IllegalArgumentException e) { throw new InvalidParseOperationException(e); } } /** * * @param file * @param clasz * @return the corresponding object * @throws InvalidParseOperationException */ public static final <T> T getFromFile(File file, Class<T> clasz) throws InvalidParseOperationException { try { ParametersChecker.checkParameter("File or class", file, clasz); return OBJECT_MAPPER.readValue(file, clasz); } catch (final IOException | IllegalArgumentException e) { throw new InvalidParseOperationException(e); } } /** * * @param file * @param clasz * @return the corresponding object * @throws InvalidParseOperationException */ public static final <T> T getFromFileLowerCamelCase(File file, Class<T> clasz) throws InvalidParseOperationException { try { ParametersChecker.checkParameter("File or class", file, clasz); return OBJECT_MAPPER_LOWER_CAMEL_CASE.readValue(file, clasz); } catch (final IOException | IllegalArgumentException e) { throw new InvalidParseOperationException(e); } } /** * * @param jsonNode * @param clasz * @return the corresponding object * @throws InvalidParseOperationException */ public static final <T> T getFromJsonNode(JsonNode jsonNode, Class<T> clasz) throws InvalidParseOperationException { try { ParametersChecker.checkParameter("JsonNode or class", jsonNode, clasz); return OBJECT_MAPPER.treeToValue(jsonNode, clasz); } catch (final JsonProcessingException e) { throw new InvalidParseOperationException(e); } } /** * * @param jsonNode * @param clasz * @return the corresponding object * @throws InvalidParseOperationException */ public static final <T> T getFromJsonNodeLowerCamelCase(JsonNode jsonNode, Class<T> clasz) throws InvalidParseOperationException { try { ParametersChecker.checkParameter("JsonNode or class", jsonNode, clasz); return OBJECT_MAPPER_LOWER_CAMEL_CASE.treeToValue(jsonNode, clasz); } catch (final JsonProcessingException e) { throw new InvalidParseOperationException(e); } } /** * * @param object * @return the Json representation of the object * @throws InvalidParseOperationException */ public static final JsonNode toJsonNode(final Object object) throws InvalidParseOperationException { try { ParametersChecker.checkParameter(OBJECT, object); return OBJECT_MAPPER.convertValue(object, JsonNode.class); } catch (final IllegalArgumentException e) { throw new InvalidParseOperationException(e); } } /** * * @param object * @return the Json representation of the object (shall be prettyPrint) * @throws InvalidParseOperationException */ public static final String writeAsString(final Object object) throws InvalidParseOperationException { try { ParametersChecker.checkParameter(OBJECT, object); return OBJECT_MAPPER.writeValueAsString(object); } catch (final JsonProcessingException | IllegalArgumentException e) { throw new InvalidParseOperationException(e); } } /** * * @param object * @return the Json representation of the object in Pretty Print format */ public static String prettyPrint(Object object) { try { ParametersChecker.checkParameter(OBJECT, object); return OBJECT_MAPPER.writerWithDefaultPrettyPrinter() .writeValueAsString(object); } catch (final JsonProcessingException | IllegalArgumentException e) { LOGGER.info(e); return "{}"; } } /** * * @param object * @return the Json representation of the object in UnPretty Print format */ public static String unprettyPrint(Object object) { try { ParametersChecker.checkParameter(OBJECT, object); return OBJECT_MAPPER_UNPRETTY.writeValueAsString(object); } catch (final JsonProcessingException | IllegalArgumentException e) { LOGGER.info(e); return "{}"; } } /** * * @param object * @return the Json representation of the object in UnPretty Print format */ public static String unprettyPrintLowerCamelCase(Object object) { try { ParametersChecker.checkParameter(OBJECT, object); return OBJECT_MAPPER_LOWER_CAMEL_CASE.writeValueAsString(object); } catch (final JsonProcessingException | IllegalArgumentException e) { LOGGER.debug(e); return "{}"; } } /** * * @param object * @param file * @throws InvalidParseOperationException */ public static final void writeAsFile(final Object object, File file) throws InvalidParseOperationException { try { ParametersChecker.checkParameter("object or file", object, file); OBJECT_MAPPER.writeValue(file, object); } catch (final IOException | IllegalArgumentException e) { throw new InvalidParseOperationException(e); } } /** * * @param object * @param outputStream * @throws InvalidParseOperationException */ public static final void writeAsOutputStream(final Object object, OutputStream outputStream) throws InvalidParseOperationException { try { ParametersChecker.checkParameter("object or file", object, outputStream); OBJECT_MAPPER.writeValue(outputStream, object); } catch (final IOException | IllegalArgumentException e) { throw new InvalidParseOperationException(e); } } /** * Check if JsonNodes are not null and not empty * * @param message default message within exception * @param nodes * @throws IllegalArgumentException if nodes are null or empty */ public static final void checkNullOrEmpty(final String message, final JsonNode... nodes) { if (nodes != null) { for (final JsonNode jsonNode : nodes) { if (jsonNode == null || jsonNode.size() == 0) { throw new IllegalArgumentException(message); } } } else { throw new IllegalArgumentException(message); } } /** * node should have only one property * * @param nodeName name to print in case of error * @param node * @return the couple property name and property value * @throws InvalidParseOperationException */ public static final Entry<String, JsonNode> checkUnicity(final String nodeName, final JsonNode node) throws InvalidParseOperationException { if (node == null || node.isMissingNode()) { throw new InvalidParseOperationException( "The current Node is missing(empty): " + nodeName + ":" + node); } if (node.isValueNode()) { // not allowed throw new InvalidParseOperationException( "The current Node is a simple value and should not: " + nodeName + ":" + node); } final int size = node.size(); if (size > 1) { throw new InvalidParseOperationException( "More than one element in current Node: " + nodeName + ":" + node); } if (size == 0) { throw new InvalidParseOperationException( "Not enough element (0) in current Node: " + nodeName + ":" + node); } final Iterator<Entry<String, JsonNode>> iterator = node.fields(); return iterator.next(); } /** * node should have only one property ; simple value is allowed * * @param nodeName name to print in case of error * @param node * @return the couple property name and property value * @throws InvalidParseOperationException */ public static final Entry<String, JsonNode> checkLaxUnicity(final String nodeName, final JsonNode node) throws InvalidParseOperationException { if (node == null || node.isMissingNode()) { throw new InvalidParseOperationException( "The current Node is missing(empty): " + nodeName + ":" + node); } if (node.isValueNode()) { // already one node return new Entry<String, JsonNode>() { @Override public JsonNode setValue(final JsonNode value) { throw new IllegalArgumentException("Cannot set Value"); } @Override public JsonNode getValue() { return node; } @Override public String getKey() { return null; } }; } final int size = node.size(); if (size > 1) { throw new InvalidParseOperationException( "More than one element in current Node: " + nodeName + ":" + node); } if (size == 0) { throw new InvalidParseOperationException( "Not enough element (0) in current Node: " + nodeName + ":" + node); } final Iterator<Entry<String, JsonNode>> iterator = node.fields(); return iterator.next(); } /** * * @param value * @return the corresponding HashMap * @throws InvalidParseOperationException */ public static final Map<String, Object> getMapFromString(final String value) throws InvalidParseOperationException { if (value != null && !value.isEmpty()) { Map<String, Object> info = null; try { info = OBJECT_MAPPER.readValue(value, new TypeReference<Map<String, Object>>() {}); } catch (final IOException e) { throw new InvalidParseOperationException(e); } if (info == null) { info = new HashMap<>(); } return info; } else { return new HashMap<>(); } } /** * * @param value * @return the corresponding HashMap * @throws InvalidParseOperationException */ public static final Map<String, String> getMapStringFromString(final String value) throws InvalidParseOperationException { if (value != null && !value.isEmpty()) { Map<String, String> info = null; try { info = OBJECT_MAPPER.readValue(value, new TypeReference<Map<String, String>>() {}); } catch (final IOException e) { throw new InvalidParseOperationException(e); } if (info == null) { info = new HashMap<>(); } return info; } else { return new HashMap<>(); } } /** * * @param inputStream * @return the corresponding HashMap * @throws InvalidParseOperationException */ public static final Map<String, Object> getMapFromInputStream(final InputStream inputStream) throws InvalidParseOperationException { ParametersChecker.checkParameter("InputStream", inputStream); Map<String, Object> info = null; try { info = OBJECT_MAPPER.readValue(inputStream, new TypeReference<Map<String, Object>>() {}); } catch (final IOException e) { throw new InvalidParseOperationException(e); } finally { try { inputStream.close(); } catch (final IOException e) { SysErrLogger.FAKE_LOGGER.ignoreLog(e); } } if (info == null) { info = new HashMap<>(); } return info; } /** * transform an inputStream into a {@link Map<String, T>} * @param inputStream * @param parameterClazz type of the value on the Map * @param <T> * @return the corresponding HashMap * @throws InvalidParseOperationException */ public static final <T> Map<String, T> getMapFromInputStream(final InputStream inputStream, Class<T> parameterClazz) throws InvalidParseOperationException { ParametersChecker.checkParameter("InputStream", inputStream); Map<String, T> info ; try { JavaType type = OBJECT_MAPPER.getTypeFactory().constructParametricType(Map.class, String.class, parameterClazz); info = OBJECT_MAPPER.readValue(inputStream, type); } catch (final IOException e) { throw new InvalidParseOperationException(e); } finally { try { inputStream.close(); } catch (final IOException e) { SysErrLogger.FAKE_LOGGER.ignoreLog(e); } } if (info == null) { info = new HashMap<>(); } return info; } /** * * @param inputStream * @param clasz * @return the corresponding object * @throws InvalidParseOperationException */ public static final <T> T getFromInputStream(InputStream inputStream, Class<T> clasz) throws InvalidParseOperationException { try { ParametersChecker.checkParameter("InputStream or class", inputStream, clasz); return OBJECT_MAPPER.readValue(inputStream, clasz); } catch (final IOException | IllegalArgumentException e) { throw new InvalidParseOperationException(e); } finally { try { inputStream.close(); } catch (final IOException e) { SysErrLogger.FAKE_LOGGER.ignoreLog(e); } } } /** * From one ArrayNode, get a new ArrayNode from offset to limit items * * @param array * @param offset * @param limit * @return Sub ArrayNode */ public static ArrayNode getSubArrayNode(ArrayNode array, int offset, int limit) { final ArrayNode subResult = createArrayNode(); int i = 0; final Iterator<JsonNode> iterator = array.elements(); for (; i < offset && iterator.hasNext(); i++) { iterator.next(); } for (i = offset; i < offset + limit && iterator.hasNext(); i++) { subResult.add(iterator.next()); } return subResult; } /** * transform an {@link ArrayNode} (JSON Array) to an {@link java.util.ArrayList} * @param arrayNode {@link ArrayNode} to transform * @return list corresponding to the arrayNode in parameter */ public static List toArrayList(ArrayNode arrayNode) { return Lists.newArrayList(arrayNode.iterator()); } }