/* * Copyright (c) 2014. by Robusta Code and individual contributors * as indicated by the @authors tag. See the copyright.txt in the * distribution for a full listing of individual contributors. * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package io.robusta.rra.representation.implementation; import io.robusta.rra.representation.JsonRepresentation; import io.robusta.rra.representation.Representation; import io.robusta.rra.representation.RepresentationException; import io.robusta.rra.resource.Resource; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; /** * Created by Nicolas Zozol for Robusta Code * <p/> * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * * @author Nicolas Zozol */ public class JacksonRepresentation implements JsonRepresentation<JsonNode> { JsonNode document; ObjectMapper mapper = new ObjectMapper(); List<String> missingKeys = new ArrayList<String>(5); /** * In that case, is serialization if not null, it's always a JsonObject * * @param serialization */ public JacksonRepresentation(HashMap<String, Object> serialization) { this.document = mapper.convertValue(serialization, JsonNode.class); } /** * constructor * * @param object */ public JacksonRepresentation(Object object) { // this.document = mapper.convertValue(object, JsonNode.class); this.document = mapper.valueToTree(object); } /** * constructor * * @param json */ public JacksonRepresentation(String json) { try { // this.document = mapper.readTree( json ); this.document = mapper.readValue(json, JsonNode.class); } catch (IOException e) { e.printStackTrace(); } } /** * constructor * * @param inputStream */ public JacksonRepresentation(InputStream inputStream) { try { this.document = mapper.readValue(inputStream, JsonNode.class); } catch (IOException e) { e.printStackTrace(); } } /** * constructor */ public JacksonRepresentation() { this.document = null; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#getDocument() */ @Override public JsonNode getDocument() { return this.document; } /** * if document is null, create a new document */ protected void createObjectIfEmtpy() { if (this.document == null) { this.document = mapper.createObjectNode(); } } // TODO /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#get(java.lang.Class) */ @Override public <T> T get(Class<T> type) throws RepresentationException { return get(type, this.document); } /** * convert a jsonNode into Object * * @param type * @param element * @return * @throws RepresentationException */ protected <T> T get(Class<T> type, JsonNode element) throws RepresentationException { try { return mapper.treeToValue(element, type); // return mapper.readValue(element.traverse(), type); } catch (IOException e) { e.printStackTrace(); } return null; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#get(java.lang.String) */ @Override public String get(String key) throws RepresentationException { return this.get(String.class, key); } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#get(java.lang.Class, * java.lang.String) */ @Override public <T> T get(Class<T> type, String key) throws RepresentationException { JsonNode element = this.document.get(key); return get(type, element); } /** * convert the document into a ObjectNode and return it. throw an exception * if the document is not an object * * @return */ protected ObjectNode asObject() { throwIfNotObject(); return (ObjectNode) this.document; } /** * convert the document into a ArrayNOde and return it. throw an exception * if the document is not an array * * @return */ protected ArrayNode asArray() { throwIfNotArray(); return (ArrayNode) this.document; } /** * throw an exception if the jsonNode is null * * @param elt * @throws RepresentationException */ protected void throwIfNull(JsonNode elt) throws RepresentationException { if (elt == null) { throw new RepresentationException("The current element is null"); } } /** * throw an exception if the jsonNode is not an array * * @param elt * @throws RepresentationException */ protected void throwIfNotArray(JsonNode elt) throws RepresentationException { if (!elt.isArray()) { throw new RepresentationException("The current element is not a JSON array but a " + this.getTypeof() + " and it can't add an object the correct way"); } } /** * throw an exception if the jsonNode is not an array * * @throws RepresentationException */ protected void throwIfNotArray() throws RepresentationException { throwIfNull(this.document); throwIfNotArray(this.document); } /** * throw an exception if the jsonNode is not an object * * @param elt * @throws RepresentationException */ protected void throwIfNotObject(JsonNode elt) throws RepresentationException { if (!elt.isObject()) { throw new RepresentationException("The current element is not a JSON object but a " + this.getTypeof() + " and thus has no key"); } } /** * throw an exception if the jsonNode is not an object * * @throws RepresentationException */ protected void throwIfNotObject() throws RepresentationException { throwIfNull(this.document); throwIfNotObject(this.document); } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.JsonRepresentation#addToArray(io.robusta * .rra.resource.Resource, boolean) */ @Override public Representation addToArray(Resource resource, boolean eager) { return null; } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.JsonRepresentation#addToArray(java.lang * .Object) */ @Override public Representation addToArray(Object value) { throwIfNotArray(); asArray().add(mapper.valueToTree(value)); return this; } // TODO /* * (non-Javadoc) * * @see * io.robusta.rra.representation.JsonRepresentation#pluck(java.lang.Class, * java.lang.String) */ @Override public <T> List<T> pluck(Class<T> type, String key) throws RepresentationException { throwIfNotArray(); List<T> result = new ArrayList<T>(); for (JsonNode elt : asArray()) { result.add(get(type, elt)); } return result; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.JsonRepresentation#isPrimitive() */ @Override public boolean isPrimitive() { return (this.document.isBoolean() && this.document.isShort() && this.document.isInt() && this.document.isLong() && this.document.isFloat() && this.document.isDouble()); } /* * (non-Javadoc) * * @see io.robusta.rra.representation.JsonRepresentation#isObject() */ @Override public boolean isObject() { return this.document.isObject(); } /* * (non-Javadoc) * * @see io.robusta.rra.representation.JsonRepresentation#isBoolean() */ @Override public boolean isBoolean() { return this.document.isBoolean(); } /* * (non-Javadoc) * * @see io.robusta.rra.representation.JsonRepresentation#isString() */ @Override public boolean isString() { return this.document.isTextual(); } /* * (non-Javadoc) * * @see io.robusta.rra.representation.JsonRepresentation#isNumber() */ @Override public boolean isNumber() { return this.document.isNumber(); } /* * (non-Javadoc) * * @see io.robusta.rra.representation.JsonRepresentation#isArray() */ @Override public boolean isArray() { return this.document.isArray(); } /* * (non-Javadoc) * * @see io.robusta.rra.representation.JsonRepresentation#isNull() */ @Override public boolean isNull() { return this.document.isNull(); } /* * (non-Javadoc) * * @see io.robusta.rra.representation.JsonRepresentation#getTypeof() */ @Override public JsonType getTypeof() { if (this.isString()) { return JsonType.STRING; } else if (this.isArray()) { return JsonType.ARRAY; } else if (this.isBoolean()) { return JsonType.BOOLEAN; } else if (this.isNumber()) { return JsonType.NUMBER; } else if (this.isNull()) { return JsonType.NULL; } else if (this.isObject()) { return JsonType.OBJECT; } else throw new IllegalStateException("Can't find the type of this document"); } /* * (non-Javadoc) * * @see io.robusta.rra.representation.JsonRepresentation#createObject() */ @Override public Representation<JsonNode> createObject() { if (this.document != null) { throw new IllegalStateException( "This representation is not Empty. Use createNewRepresentation() to get a new empty representation"); } this.document = mapper.createObjectNode(); return this; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.JsonRepresentation#createArray() */ @Override public Representation<JsonNode> createArray() { if (this.document != null) { throw new IllegalStateException( "This representation is not Empty. Use createNewRepresentation() to get a new empty representation"); } this.document = mapper.createArrayNode(); return this; } /** * check if document has the specified keys * * @param key * @return */ protected boolean has(String key) { throwIfNotObject(this.document); return asObject().has(key); } /** * check if document is not null * * @param key * @return */ protected boolean hasNotEmpty(String key) { throwIfNotObject(); JsonNode elt = asObject().get(key); if (elt == null || elt.isNull() || (elt.isTextual() && elt.textValue().isEmpty())) { // elt is null or empty string return false; } else { return true; } } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.Representation#hasPossiblyEmpty(java.lang * .String[]) */ @Override public boolean hasPossiblyEmpty(String... keys) { boolean result = true; for (String key : keys) { if (!has(key)) { missingKeys.add(key); result = false; } } return result; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#has(java.lang.String[]) */ @Override public boolean has(String... keys) { boolean result = true; for (String key : keys) { if (!hasNotEmpty(key)) { missingKeys.add(key); result = false; } } return result; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#getMissingKeys() */ @Override public List<String> getMissingKeys() { return missingKeys; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#set(java.lang.String, * java.lang.String) */ @Override public Representation set(String key, String value) { throwIfNotObject(); createObjectIfEmtpy(); asObject().put(key, value); return this; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#set(java.lang.String, * java.lang.Object) */ @Override public Representation set(String key, Object value) { throwIfNotObject(); createObjectIfEmtpy(); asObject().set(key, mapper.valueToTree(value)); return this; } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.Representation#getValues(java.lang.String) */ @Override public List<String> getValues(String key) throws RepresentationException { List<String> list = new ArrayList<String>(); for (JsonNode elt : asObject().get(key)) { list.add(elt.toString()); } return list; } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.Representation#getValues(java.lang.Class, * java.lang.String) */ @Override public <T> List<T> getValues(Class<T> type, String key) throws RepresentationException { List<T> list = new ArrayList<T>(); for (JsonNode elt : asObject().get(key)) { list.add(get(type, elt)); } return list; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#add(java.lang.String, * java.lang.Object) */ @Override public Representation add(String key, Object value) { throwIfNotObject(); throwIfNotArray(this.asObject().get(key)); asObject().withArray(key).add(mapper.valueToTree(value)); return this; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#add(java.lang.String, * io.robusta.rra.resource.Resource, boolean) */ @Override public Representation add(String key, Resource resource, boolean eager) { throwIfNotObject(); throwIfNotArray(this.asObject().get(key)); JsonNode element = mapper.valueToTree(resource); asObject().withArray(key).add(element); return this; } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.Representation#addAll(java.lang.String, * java.util.List) */ @Override public Representation addAll(String key, List values) { throwIfNotObject(); throwIfNotArray(this.asObject().get(key)); for (Object value : values) { asObject().withArray(key).add(mapper.valueToTree(value)); } return this; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#merge(java.lang.String, * java.lang.String, io.robusta.rra.representation.Representation) */ @Override public Representation merge(String keyForCurrent, String keyForNew, Representation representation) { if (!(representation instanceof JacksonRepresentation)) { throw new IllegalArgumentException("Can't merge a JacksonRepresentation with a " + representation.getClass().getSimpleName()); } JacksonRepresentation mergedRepresentation = new JacksonRepresentation(); mergedRepresentation.createObject(); ObjectNode object = (ObjectNode) mergedRepresentation.getDocument(); object.set(keyForCurrent, this.document); object.set(keyForNew, ((JacksonRepresentation) representation).getDocument()); return mergedRepresentation; } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.Representation#remove(java.lang.String) */ @Override public Representation remove(String key) throws RepresentationException { throwIfNotObject(); int i = 0; if (key.contains(".")) { String[] keys = key.split("\\."); if (keys.length == 0) { throw new IllegalArgumentException("Malformed key " + keys + " ; use something like user.school.id"); } ObjectNode current = asObject(); for (i = 0; i < keys.length - 1; i++) { current = (ObjectNode) current.get(keys[i]); if (current == null) { throw new IllegalArgumentException("There is no valid object for key " + keys[i] + " in '" + key + "'"); } } if (current.get(keys[keys.length - 1]) == null) { throw new IllegalArgumentException("There is no valid object for key " + keys[keys.length - 1] + " in '" + key + "'"); } current.remove(keys[keys.length - 1]); } else { if (document.get(key) == null) { throw new IllegalArgumentException("There is no valid object for key " + key); } asObject().remove(key); } return this; } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#fetch(java.lang.String) */ @Override public Representation fetch(String key) { throwIfNotObject(); JsonNode root; if (key.contains(".")) { String[] keys = key.split("\\."); if (keys.length == 0) { throw new IllegalArgumentException("Malformed key " + keys + " ; use something like user.school.id"); } String lastKey = keys[0]; JsonNode current = asObject(); for (String newKey : keys) { if (!current.isObject()) { throw new IllegalArgumentException("The key " + lastKey + " in '" + key + "' doesn't point to a Json Object"); } ; current = current.get(newKey); if (current == null) { throw new IllegalArgumentException("There is no valid object for key " + newKey + "in '" + key + "'"); } } root = current; } else { root = asObject().get(key); } if (root == null) { throw new IllegalArgumentException("There is no valid object for key " + key); } else { JacksonRepresentation fetchedRepresentation = new JacksonRepresentation(); fetchedRepresentation.document = root; return fetchedRepresentation; } } /* * (non-Javadoc) * * @see io.robusta.rra.representation.Representation#copy() */ @Override public Representation copy() { String serialization = this.document.toString(); JsonNode clone = null; try { clone = mapper.readTree(serialization); } catch (IOException e) { e.printStackTrace(); } JacksonRepresentation representation = new JacksonRepresentation(); representation.document = clone; return representation; } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.Representation#createNewRepresentation( * java.lang.Object) */ @Override public Representation createNewRepresentation(Object newObject) { return new JacksonRepresentation(newObject); } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.Representation#createNewRepresentation( * java.io.InputStream) */ @Override public Representation createNewRepresentation(InputStream inputStream) { return new JacksonRepresentation(inputStream); } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.Representation#createNewRepresentation( * java.lang.String) */ @Override public Representation createNewRepresentation(String json) { return new JacksonRepresentation(json); } /* * (non-Javadoc) * * @see * io.robusta.rra.representation.Representation#createNewRepresentation() */ @Override public Representation createNewRepresentation() { return new JacksonRepresentation(); } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public String toString() { return this.document.toString(); } }