package org.openntf.domino.graph; import java.io.Serializable; import java.util.Collections; import java.util.Comparator; //import java.util.HashMap; //import java.util.HashSet; //import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; import javolution.util.FastMap; import javolution.util.FastSet; import org.openntf.domino.Database; import org.openntf.domino.Document; import org.openntf.domino.Session; import org.openntf.domino.View; import org.openntf.domino.ViewEntry; import org.openntf.domino.ViewEntryCollection; import org.openntf.domino.transactions.DatabaseTransaction; import org.openntf.domino.utils.DominoUtils; import org.openntf.domino.utils.Factory; import org.openntf.domino.utils.Factory.SessionType; import com.google.common.cache.Cache; import com.google.common.cache.CacheLoader; import com.ibm.commons.util.StringUtil; import com.tinkerpop.blueprints.Direction; import com.tinkerpop.blueprints.Edge; import com.tinkerpop.blueprints.Element; import com.tinkerpop.blueprints.Features; import com.tinkerpop.blueprints.Graph; import com.tinkerpop.blueprints.GraphQuery; import com.tinkerpop.blueprints.MetaGraph; import com.tinkerpop.blueprints.TransactionalGraph; import com.tinkerpop.blueprints.Vertex; import com.tinkerpop.blueprints.util.DefaultGraphQuery; @SuppressWarnings({ "rawtypes", "unused" }) @Deprecated public class DominoGraph implements Graph, MetaGraph, TransactionalGraph { public static class GraphCacheLoader extends CacheLoader { private transient Database rawDb_; public GraphCacheLoader(final Database db) { rawDb_ = db; } @Override public Object load(final Object key) throws Exception { DominoElement result = null; String unid = null; if (key instanceof String) { String sid = (String) key; if (DominoUtils.isUnid(sid)) { unid = sid; } else if (sid.length() > 32) { unid = DominoUtils.toUnid(sid); } else { unid = DominoUtils.toUnid(sid); } } else if (key instanceof Serializable) { unid = DominoUtils.toUnid((Serializable) key); } return result; } } public static class DominoGraphException extends RuntimeException { private static final long serialVersionUID = 1L; final String message_; final DominoElement elem1_; final DominoElement elem2_; public DominoGraphException(final String message) { super(message); message_ = message; elem1_ = null; elem2_ = null; } public DominoGraphException(final String message, final DominoElement element) { super(message); message_ = message; elem1_ = element; elem2_ = null; } public DominoGraphException(final String message, final DominoElement element1, final DominoElement element2) { super(message); message_ = message; elem1_ = element1; elem2_ = element2; } } private static final Logger log_ = Logger.getLogger(DominoGraph.class.getName()); public static final Set<String> EMPTY_IDS = Collections.emptySet(); public static final String EDGE_VIEW_NAME = "(_OPEN_Edges)"; public static final String VERTEX_VIEW_NAME = "(_OPEN_Vertices)"; private static final Features FEATURES = new Features(); public static final boolean COMPRESS_IDS = false; public static SortedSet<? extends Element> sortElements(final Iterable<? extends Element> elements, final String[] sortproperties) { // StringBuilder sb = new StringBuilder(); // for (String prop : sortproperties) { // sb.append(prop + ", "); // } // System.out.println("Sorting elements from String properties: " + sb.toString()); Comparator<Element> comp = new ElementComparator(sortproperties); SortedSet<Element> result = new TreeSet<Element>(comp); for (Object e : elements) { if (e instanceof Element) { result.add((Element) e); } } return Collections.unmodifiableSortedSet(result); } public static SortedSet<? extends Element> sortElements(final Iterable<? extends Element> elements, final IDominoProperties[] sortproperties) { Comparator<Element> comp = new ElementComparator(sortproperties); SortedSet<Element> result = new TreeSet<Element>(comp); for (Object e : elements) { if (e instanceof Element) { result.add((Element) e); } } return Collections.unmodifiableSortedSet(result); } @SuppressWarnings("unchecked") public static SortedSet<? extends Edge> sortEdges(final Iterable<? extends Edge> elements, final String[] sortproperties) { return (SortedSet<Edge>) sortElements(elements, sortproperties); } @SuppressWarnings("unchecked") public static SortedSet<? extends Vertex> sortVertexes(final Iterable<? extends Vertex> elements, final String[] sortproperties) { return (SortedSet<Vertex>) sortElements(elements, sortproperties); } @SuppressWarnings("unchecked") public static SortedSet<? extends Edge> sortEdges(final Iterable<? extends Edge> elements, final IDominoProperties[] sortproperties) { return (SortedSet<Edge>) sortElements(elements, sortproperties); } @SuppressWarnings("unchecked") public static SortedSet<? extends Vertex> sortVertexes(final Iterable<? extends Vertex> elements, final IDominoProperties[] sortproperties) { return (SortedSet<Vertex>) sortElements(elements, sortproperties); } static { DominoGraph.FEATURES.supportsDuplicateEdges = true; DominoGraph.FEATURES.supportsSelfLoops = true; DominoGraph.FEATURES.supportsSerializableObjectProperty = true; DominoGraph.FEATURES.supportsBooleanProperty = true; DominoGraph.FEATURES.supportsDoubleProperty = true; DominoGraph.FEATURES.supportsFloatProperty = true; DominoGraph.FEATURES.supportsIntegerProperty = true; DominoGraph.FEATURES.supportsPrimitiveArrayProperty = true; DominoGraph.FEATURES.supportsUniformListProperty = true; DominoGraph.FEATURES.supportsMixedListProperty = true; DominoGraph.FEATURES.supportsLongProperty = true; DominoGraph.FEATURES.supportsMapProperty = true; DominoGraph.FEATURES.supportsStringProperty = true; DominoGraph.FEATURES.ignoresSuppliedIds = false; DominoGraph.FEATURES.isWrapper = false; DominoGraph.FEATURES.supportsIndices = false; DominoGraph.FEATURES.supportsKeyIndices = false; DominoGraph.FEATURES.supportsVertexKeyIndex = false; DominoGraph.FEATURES.supportsEdgeKeyIndex = false; DominoGraph.FEATURES.supportsVertexIndex = false; DominoGraph.FEATURES.supportsEdgeIndex = false; DominoGraph.FEATURES.supportsTransactions = true; DominoGraph.FEATURES.supportsVertexIteration = false; DominoGraph.FEATURES.supportsEdgeIteration = false; DominoGraph.FEATURES.supportsEdgeRetrieval = true; DominoGraph.FEATURES.supportsVertexProperties = true; DominoGraph.FEATURES.supportsEdgeProperties = true; DominoGraph.FEATURES.supportsThreadedTransactions = true; DominoGraph.FEATURES.isPersistent = true; } private java.util.Map<Object, Element> cache_; private Cache<Object, Element> managedCache_; private String filepath_; private String server_; protected transient Map<String, IEdgeHelper> edgeHelpers_ = new FastMap<String, IEdgeHelper>(); public DominoGraph(final org.openntf.domino.Database database) { setRawDatabase(database); } public IEdgeHelper getHelper(final String key) { IEdgeHelper helper = edgeHelpers_.get(key); return helper; } public IEdgeHelper getHelper(final IDominoEdgeType edgeType) { return getHelper(edgeType.getLabel()); } public Set<IEdgeHelper> findHelpers(final Vertex in, final Vertex out) { Set<IEdgeHelper> result = new FastSet<IEdgeHelper>(); if (in == null || out == null) { return result; } Class<?> inCls = in.getClass(); Class<?> outCls = out.getClass(); for (IEdgeHelper helper : edgeHelpers_.values()) { boolean inChk = helper.getInType().isAssignableFrom(inCls); boolean outChk = helper.getOutType().isAssignableFrom(outCls); if (inChk && outChk) { result.add(helper); } } return result; } public void addHelper(final String key, final Class<? extends Vertex> inType, final Class<? extends Vertex> outType) { addHelper(key, inType, outType, true, key); } public void addHelper(final String key, final Class<? extends Vertex> inType, final Class<? extends Vertex> outType, final boolean unique) { addHelper(key, inType, outType, unique, key); } public void addHelper(final String key, final Class<? extends Vertex> inType, final Class<? extends Vertex> outType, final boolean unique, final String label) { if (getHelper(key) == null) { edgeHelpers_.put(key, new AbstractEdgeHelper(this, label, inType, outType, unique)); } } public void addHelper(final String key, final IEdgeHelper helper) { if (getHelper(key) == null) { edgeHelpers_.put(key, helper); } } public void removeHelper(final String key) { edgeHelpers_.remove(key); } public void setRawDatabase(final org.openntf.domino.Database database) { if (database != null) { server_ = database.getServer(); filepath_ = database.getFilePath(); } } private java.util.Map<Object, Element> getCache() { if (cache_ == null) { cache_ = new FastMap<Object, Element>().atomic(); } return cache_; } public void cache(final Element elem) { putCache(elem); } private void putCache(final Element elem) { if (elem != null) { Map<Object, Element> cache = getCache(); // synchronized (cache) { cache.put(elem.getId(), elem); // } } } private Element getCache(final Object id) { Map<Object, Element> cache = getCache(); Element result = null; // synchronized (cache) { result = cache.get(id); // } return result; } private void removeCache(final Element elem) { Map<Object, Element> cache = getCache(); // synchronized (cache) { cache.remove(elem); // } } private Set<Element> getCacheValues() { Map<Object, Element> cache = getCache(); FastSet<Element> result = new FastSet<Element>(); // synchronized (cache) { result.addAll(cache.values()); // } return result.unmodifiable(); } private void clearCache() { Map<Object, Element> cache = getCache(); // synchronized (cache) { try { cache.clear(); } catch (Throwable t) { t.printStackTrace(); } // } clearDocumentCache(); } @Override public Edge addEdge(Object id, final Vertex outVertex, final Vertex inVertex, final String label) { startTransaction(null); if (id == null) id = DominoUtils.toUnid(outVertex.getId() + label + inVertex.getId()); Document d = getDocument(id, true); d.replaceItemValue(DominoElement.TYPE_FIELD, DominoEdge.GRAPH_TYPE_VALUE); DominoEdge ed = new DominoEdge(this, d); putCache(ed); // putCache(outVertex); // putCache(inVertex); ed.setLabel(label); ed.setOutDoc((IDominoVertex) outVertex); ed.setInDoc((IDominoVertex) inVertex); return ed; } public Edge getEdge(final Vertex outVertex, final Vertex inVertex, final String label) { String id = DominoUtils.toUnid(outVertex.getId() + label + inVertex.getId()); Edge result = getEdge(id); if (result != null) { ((DominoEdge) result).setOutDoc((IDominoVertex) outVertex); ((DominoEdge) result).setInDoc((IDominoVertex) inVertex); } return result; } public Edge getOrAddEdge(Object id, final Vertex outVertex, final Vertex inVertex, final String label) { Edge result = null; if (id == null) { id = DominoUtils.toUnid(outVertex.getId() + label + inVertex.getId()); result = getEdge(id); if (result != null) { ((DominoEdge) result).setLabel(label); ((DominoEdge) result).setOutDoc((IDominoVertex) outVertex); ((DominoEdge) result).setInDoc((IDominoVertex) inVertex); } } // if (result == null) { // for (Edge e : outVertex.getEdges(Direction.OUT, label)) { // Vertex v = e.getVertex(Direction.IN); // if (v.getId().equals(inVertex.getId())) { // result = e; // ((DominoEdge) result).setLabel(label); // ((DominoEdge) result).setOutDoc(outVertex); // ((DominoEdge) result).setInDoc(inVertex); // break; // } // } // } if (result == null) { result = addEdge(id, outVertex, inVertex, label); } return result; } @Override public Vertex addVertex(final Object id) { startTransaction(null); Document d = null; if (id == null) { d = getDocument(null, true); } else { String vid = DominoUtils.toUnid((Serializable) id); d = getDocument(vid, true); } d.replaceItemValue(DominoElement.TYPE_FIELD, DominoVertex.GRAPH_TYPE_VALUE); DominoVertex result = new DominoVertex(this, d); putCache(result); return result; } protected String getServerName() { return server_; } protected String getDatabasePath() { return filepath_; } protected org.openntf.domino.Database getDatabase() { Session session = getCurrentSession(); if (session == null) { log_.log(Level.INFO, "Current session not available. Using native session instead."); session = getRawSession(); } return session.getDatabase(server_, filepath_); } public org.openntf.domino.Session getCurrentSession() { return Factory.getSession(SessionType.CURRENT); } public org.openntf.domino.Session getRawSession() { return Factory.getSession(SessionType.NATIVE); } public org.openntf.domino.Database getRawDatabase() { return getDatabase(); } public static String toUnid(final java.lang.Object value) { String unid = null; if (value == null) return null; if (value instanceof String) { String sid = (String) value; if (DominoUtils.isUnid(sid)) { unid = sid; } else if (sid.length() > 32) { unid = DominoUtils.toUnid(sid); } else { unid = DominoUtils.toUnid(sid); } } else if (value instanceof Serializable) { unid = DominoUtils.toUnid((Serializable) value); } return unid; } Document getDocument(final Object id, final boolean createOnFail) { Document result = null; String unid = toUnid(id); Map<String, Document> map = documentCache.get(); if (id == null && createOnFail) { result = getRawDatabase().createDocument(); // synchronized (map) { map.put(result.getUniversalID(), result); // } } unid = toUnid(id); if (unid != null && !DominoUtils.isUnid(unid)) { log_.log(Level.SEVERE, "ALERT! INVALID UNID FROM id type " + (id == null ? "null" : id.getClass().getName()) + ": " + id); } if (result == null) { result = map.get(unid); if (result == null) { result = getRawDatabase().getDocumentWithKey(unid, createOnFail); if (result != null) { String localUnid = result.getUniversalID(); if (!StringUtil.equalsIgnoreCase(unid, localUnid)) { log_.log(Level.SEVERE, "UNIDs do not match! Expected: " + unid + ", Result: " + localUnid); } // synchronized (map) { map.put(unid, result); // } } } } // if (result == null && createOnFail) { // log_.log(Level.SEVERE, "Returning a null document for id " + String.valueOf(id) // + " even though createOnFail was true. This should be guaranteed to return a real document!"); // } if (result == null && createOnFail) { String message = "We are about to return a null result even though createOnFail was true. We should ALWAYS return a Document in that case. For key: " + String.valueOf(id) + " in database " + String.valueOf(filepath_); log_.log(Level.SEVERE, message); System.out.println(message); new RuntimeException().printStackTrace(); } return result; } @Override public Edge getEdge(final Object id) { //see http://www.tinkerpop.com/docs/javadocs/blueprints/2.5.0/com/tinkerpop/blueprints/Graph.html#getEdge(java.lang.Object) Edge result = null; if (id == null) { // Document d = getDocument(id, false); // result = new DominoEdge(this, d); // putCache(result); return null; } else { Element cached = getCache(id); if (cached instanceof Edge) { result = (Edge) cached; } if (result == null) { // System.out.println("Cache miss on edge with id " + id); Document d = getDocument(id, false); if (d != null) { result = new DominoEdge(this, d); putCache(result); } } } return result; } @Override public Iterable<Edge> getEdges() { FastSet<Edge> result = new FastSet<Edge>(); ViewEntryCollection vec = getEdgeView().getAllEntries(); for (ViewEntry entry : vec) { result.add(getEdge(entry.getUniversalID())); } return result.unmodifiable(); } @Override public Iterable<Edge> getEdges(final String key, final Object value) { // TODO throw new UnsupportedOperationException(); } public FastSet<Edge> getEdgesFromIds(final Set<String> set) { FastSet<Edge> result = new FastSet<Edge>(); for (String id : set) { Edge edge = getEdge(id); if (edge != null) { result.add(edge); } } return result; } public Set<Edge> getEdgesFromIds(final Set<String> set, final String... labels) { FastSet<Edge> result = new FastSet<Edge>(); for (String id : set) { Edge edge = getEdge(id); if (edge != null) { for (String label : labels) { if (label.equals(edge.getLabel())) { result.add(edge); break; } } } } return result; } private View getEdgeView() { View result = getRawDatabase().getView(DominoGraph.EDGE_VIEW_NAME); if (result == null) { result = getRawDatabase().createView(DominoGraph.EDGE_VIEW_NAME, "SELECT " + DominoElement.TYPE_FIELD + "=\"" + DominoEdge.GRAPH_TYPE_VALUE + "\"", null, false); org.openntf.domino.ViewColumn column1 = result.createColumn(); column1.setTitle("Created"); column1.setFormula("@Created"); column1.setSortDescending(true); } return result; } @Override public Features getFeatures() { return DominoGraph.FEATURES; } @Override public Vertex getVertex(final Object id) { String vid = DominoUtils.toUnid((Serializable) id); Vertex result = (Vertex) getCache(vid); if (result == null) { Document d = getDocument(vid, false); if (d == null) return null; if (d.isDeleted()) { // System.out.println("Found vertex for id " + String.valueOf(id) + " but it's been deleted."); return null; } result = new DominoVertex(this, d); putCache(result); } return result; } private View getVertexView() { View result = getRawDatabase().getView(DominoGraph.VERTEX_VIEW_NAME); if (result == null) { result = getRawDatabase().createView(DominoGraph.VERTEX_VIEW_NAME, "SELECT " + DominoElement.TYPE_FIELD + "=\"" + DominoVertex.GRAPH_TYPE_VALUE + "\"", null, false); org.openntf.domino.ViewColumn column1 = result.createColumn(); column1.setTitle("Created"); column1.setFormula("@Created"); column1.setSortDescending(true); } return result; } @Override public Iterable<Vertex> getVertices() { FastSet<Vertex> result = new FastSet<Vertex>(); ViewEntryCollection vec = getVertexView().getAllEntries(); for (ViewEntry entry : vec) { result.add(getVertex(entry.getUniversalID())); } return result.unmodifiable(); } @Override public Iterable<Vertex> getVertices(final String key, final Object value) { throw new UnsupportedOperationException(); } @Override public GraphQuery query() { return new DefaultGraphQuery(this); } @Override public void removeEdge(final Edge edge) { startTransaction(edge); Vertex in = edge.getVertex(Direction.IN); if (in != null) { ((DominoVertex) in).removeEdge(edge); } Vertex out = edge.getVertex(Direction.OUT); if (out != null) { ((DominoVertex) out).removeEdge(edge); } removeCache(edge); ((DominoEdge) edge)._remove(); } @Override public void removeVertex(final Vertex vertex) { startTransaction(vertex); DominoVertex dv = (DominoVertex) vertex; for (Edge edge : dv.getEdges(Direction.BOTH)) { try { removeEdge(edge); } catch (Throwable t) { DominoUtils.handleException(t); } } removeCache(vertex); dv._remove(); } @Override public void shutdown() { commit(); } @Override public Object getRawGraph() { return getRawDatabase(); } // private boolean inTransaction_ = false; private static ThreadLocal<DatabaseTransaction> txnHolder_ = new ThreadLocal<DatabaseTransaction>() { }; public static void clearDocumentCache() { documentCache.set(null); } private static ThreadLocal<Map<String, Document>> documentCache = new ThreadLocal<Map<String, Document>>() { /* * (non-Javadoc) * * @see java.lang.ThreadLocal#initialValue() */ @Override protected Map<String, Document> initialValue() { return new FastMap<String, Document>(); } /* * (non-Javadoc) * * @see java.lang.ThreadLocal#get() */ @Override public Map<String, Document> get() { Map<String, Document> map = super.get(); if (map == null) { map = new FastMap<String, Document>(); super.set(map); } return map; } }; private DatabaseTransaction getTxn() { return getRawDatabase().getTransaction(); // return txnHolder_.get(); } // public void setTxn(final DatabaseTransaction txn) { // txnHolder_.set(txn); // } // private DatabaseTransaction txn_; public void startTransaction(final Element elem) { putCache(elem); if (getTxn() == null) { getRawDatabase().startTransaction(); // setTxn(getRawDatabase().startTransaction()); } } /* * (non-Javadoc) * * @see com.tinkerpop.blueprints.TransactionalGraph#stopTransaction(com.tinkerpop.blueprints.TransactionalGraph.Conclusion) */ @Override @Deprecated public void stopTransaction(final Conclusion conclusion) { } public void commit(final boolean clearCache) { DatabaseTransaction txn = getTxn(); if (txn != null) { if (getCache().size() > 0) { // System.out.println("Reapplying cache to " + getCache().size() + " elements..."); int vCount = 0; Set<Element> elems = getCacheValues(); for (Element elem : elems) { if (elem instanceof DominoElement) { ((DominoElement) elem).reapplyChanges(); } } } else { // System.out.println("Element cache is empty (so what are we committing?)"); } txn.commit(); // setTxn(null); } if (clearCache) clearCache(); // System.out.println("Transaction complete"); } /* * (non-Javadoc) * * @see com.tinkerpop.blueprints.TransactionalGraph#commit() */ @Override public void commit() { commit(true); } /* * (non-Javadoc) * * @see com.tinkerpop.blueprints.TransactionalGraph#rollback() */ @Override public void rollback() { DatabaseTransaction txn = getTxn(); if (txn != null) { txn.rollback(); // setTxn(null); } clearCache(); // System.out.println("Transaction rollbacked"); } }