/*
* 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();
}
}