////////////////////////////////////////////////////////////////////////////////////////// // // Implementation of a simple graph client for the ArangoDB. // // Copyright triAGENS GmbH Cologne. // ////////////////////////////////////////////////////////////////////////////////////////// package com.arangodb.blueprints.client; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import com.arangodb.ErrorNums; import com.google.gson.Gson; /** * The ArangoDB document class * * @author Achim Brandt (http://www.triagens.de) * @author Johannes Gocke (http://www.triagens.de) * @author Guido Schwab (http://www.triagens.de) * @author Jan Steemann (http://www.triagens.de) */ public abstract class ArangoDBBaseDocument { /** * the logger */ private static final Logger logger = Logger.getLogger(ArangoDBBaseDocument.class); /** * document id attribute */ public static final String _ID = "_id"; /** * document revision attribute */ public static final String _REV = "_rev"; /** * document key attribute */ public static final String _KEY = "_key"; /** * all document properties */ protected Map<String, Object> properties; /** * true if the document is deleted */ protected boolean deleted = false; /** * Returns all document properties as a JSON object * * @return the JSON object */ public Map<String, Object> getProperties() { return properties; } /** * Sets the document status to "deleted" */ public void setDeleted() { properties = new TreeMap<String, Object>(); deleted = true; } /** * Returns true if the document has status "deleted" * * @return true, if the document is deleted */ public boolean isDeleted() { return deleted; } /** * Sets all document properties by a JSON object * * @param properties * the JSON object * * @throws ArangoDBException * If the type is not supported */ public void setProperties(Map<String, Object> properties) throws ArangoDBException { if (properties != null) { this.properties = new TreeMap<String, Object>(properties); } else { this.properties = new TreeMap<String, Object>(); } checkStdProperties(); } protected void checkHasProperty(String name) throws ArangoDBException { if (!properties.containsKey(name)) { throw new ArangoDBException("Missing property '" + name + "'"); } } protected void checkStdProperties() throws ArangoDBException { checkHasProperty(_ID); checkHasProperty(_REV); checkHasProperty(_KEY); } /** * Returns a property value * * @param key * The key of the property * * @return the property value */ public Object getProperty(String key) { return this.properties.get(key); } /** * Returns property keys * * @return the property keys */ public Set<String> getPropertyKeys() { Set<String> result = new HashSet<String>(); for (Map.Entry<String, Object> entry : properties.entrySet()) { String key = entry.getKey(); if (key.charAt(0) != '_' && key.charAt(0) != '$') { result.add(key); } } return result; } /** * Checks the document values and returns converted values. Supported value * types: Boolean, Integer, Long, String, Double, Float, Map, List * * @param value * a value of an attribute * @return Object a supported (or converted) value * * @throws ArangoDBException * If the type is not supported */ public static Object toDocumentValue(Object value) throws ArangoDBException { Object result; if (value instanceof Boolean) { result = value; } else if (value instanceof Integer) { result = new Double(((Integer) value).doubleValue()); } else if (value instanceof Long) { result = new Double(((Long) value).doubleValue()); } else if (value instanceof Float) { result = new Double(((Float) value).doubleValue()); } else if (value instanceof String) { result = value; } else if (value instanceof Double) { result = value; } else if (value instanceof Map) { result = mapToDocumentValue((Map<?, ?>) value); } else if (value instanceof List) { result = listToDocumentValue((List<?>) value); } else { // TODO add more types throw new ArangoDBException("class of value not supported: " + value.getClass().toString(), ErrorNums.ERROR_GRAPH_INVALID_PARAMETER); } return result; } private static Object listToDocumentValue(List<?> value) throws ArangoDBException { for (final Object k : value) { toDocumentValue(k); } return value; } private static Object mapToDocumentValue(Map<?, ?> value) throws ArangoDBException { Map<String, Object> map = new TreeMap<String, Object>(); for (Map.Entry<?, ?> entry : value.entrySet()) { if (entry.getKey() instanceof String) { map.put((String) entry.getKey(), toDocumentValue(entry.getValue())); } else { throw new ArangoDBException("a key of a Map has to be a String", ErrorNums.ERROR_GRAPH_INVALID_PARAMETER); } } return map; } /** * Set a single property value * * @param key * the property key * @param value * the property value * * @throws ArangoDBException * if an error occurs */ public void setProperty(String key, Object value) throws ArangoDBException { if (StringUtils.isNotBlank(key)) { if (key.charAt(0) != '_' && key.charAt(0) != '$') { properties.put(key, toDocumentValue(value)); } else { throw new ArangoDBException("property key is reserved"); } } else { throw new ArangoDBException("property value cannot be empty"); } } protected void setSystemProperty(String key, Object value) throws ArangoDBException { if (StringUtils.isNotBlank(key)) { properties.put(key, toDocumentValue(value)); } else { throw new ArangoDBException("property value cannot be empty"); } } /** * Removes a single property * * @param key * the key of the property * * @return the value of the removed property * * @throws ArangoDBException * if an error occurs */ public Object removeProperty(String key) throws ArangoDBException { if (StringUtils.isNotBlank(key)) { if (key.charAt(0) != '_' && key.charAt(0) != '$') { return properties.remove(key); } else { throw new ArangoDBException("property key is reserved"); } } return null; } protected String getStringProperty(String key) { Object o = getProperty(key); if (o == null) { return ""; } return o.toString(); } /** * Returns the document identifier * * @return the document identifier */ public String getDocumentId() { return getStringProperty(_ID); } /** * Returns the document version * * @return the document version */ public String getDocumentRev() { return getStringProperty(_REV); } /** * Returns the document long identifier * * @return the document long identifier */ public String getDocumentKey() { return getStringProperty(_KEY); } @Override public String toString() { if (properties == null) { return "null"; } return properties.toString(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (deleted ? 1231 : 1237); result = prime * result + ((properties == null) ? 0 : serializeProperties(properties).hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ArangoDBBaseDocument other = (ArangoDBBaseDocument) obj; if (deleted != other.deleted) return false; if (properties == null) { if (other.properties != null) return false; } else if (!serializeProperties(properties).equals(serializeProperties(other.properties))) { return false; } return true; } private String serializeProperties(Map<String, Object> map) { Gson gson = new Gson(); try { return gson.toJson(map); } catch (Exception e) { logger.debug("could not create json object by map", e); return ""; } } }