View Javadoc

1   /*
2    * Copyright 2015 Data Archiving and Networked Services (an institute of
3    * Koninklijke Nederlandse Akademie van Wetenschappen), King's College London,
4    * Georg-August-Universitaet Goettingen Stiftung Oeffentlichen Rechts
5    *
6    * Licensed under the EUPL, Version 1.1 or – as soon they will be approved by
7    * the European Commission - subsequent versions of the EUPL (the "Licence");
8    * You may not use this work except in compliance with the Licence.
9    * You may obtain a copy of the Licence at:
10   *
11   * https://joinup.ec.europa.eu/software/page/eupl
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the Licence is distributed on an "AS IS" basis,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the Licence for the specific language governing
17   * permissions and limitations under the Licence.
18   */
19  
20  package eu.ehri.project.core.impl;
21  
22  import com.google.common.base.Preconditions;
23  import com.google.common.collect.Iterables;
24  import com.google.common.collect.Lists;
25  import com.google.common.collect.Maps;
26  import com.tinkerpop.blueprints.CloseableIterable;
27  import com.tinkerpop.blueprints.Element;
28  import com.tinkerpop.blueprints.Graph;
29  import com.tinkerpop.blueprints.Vertex;
30  import com.tinkerpop.blueprints.util.WrappingCloseableIterable;
31  import com.tinkerpop.frames.FramedGraph;
32  import eu.ehri.project.core.GraphManager;
33  import eu.ehri.project.exceptions.IntegrityError;
34  import eu.ehri.project.exceptions.ItemNotFound;
35  import eu.ehri.project.models.EntityClass;
36  import eu.ehri.project.models.annotations.EntityType;
37  import eu.ehri.project.models.base.Entity;
38  
39  import java.util.List;
40  import java.util.Map;
41  import java.util.NoSuchElementException;
42  
43  /**
44   * Implementation of GraphManager that uses a single index to manage all nodes.
45   * <p>
46   * This class can be extended for when specific graph implementations (such
47   * as Neo4j) can provide more efficient implementations of certain methods.
48   */
49  public class BlueprintsGraphManager<T extends Graph> implements GraphManager {
50  
51      protected static final String METADATA_PREFIX = "_";
52  
53      protected final FramedGraph<T> graph;
54  
55      public FramedGraph<T> getGraph() {
56          return graph;
57      }
58  
59      public BlueprintsGraphManager(FramedGraph<T> graph) {
60          this.graph = graph;
61      }
62  
63      @Override
64      public String getId(Vertex vertex) {
65          return vertex.getProperty(EntityType.ID_KEY);
66      }
67  
68      @Override
69      public String getType(Vertex vertex) {
70          return vertex.getProperty(EntityType.TYPE_KEY);
71      }
72  
73      @Override
74      public Map<String, Object> getProperties(Vertex vertex) {
75          Map<String, Object> props = Maps.newHashMap();
76          for (String key : vertex.getPropertyKeys()) {
77              if (!key.startsWith(METADATA_PREFIX)) {
78                  props.put(key, vertex.getProperty(key));
79              }
80          }
81          return props;
82      }
83  
84      @Override
85      public EntityClass getEntityClass(Vertex vertex) {
86          Preconditions.checkNotNull(vertex);
87          return EntityClass.withName(getType(vertex));
88      }
89  
90      @Override
91      public EntityClass getEntityClass(Entity entity) {
92          Preconditions.checkNotNull(entity);
93          return EntityClass.withName(entity.getType());
94      }
95  
96      @Override
97      public boolean exists(String id) {
98          Preconditions.checkNotNull(id,
99                  "attempt determine existence of a vertex with a null id");
100         return graph.getVertices(EntityType.ID_KEY, id).iterator().hasNext();
101     }
102 
103     @Override
104     public <E> E getEntity(String id, Class<E> cls) throws ItemNotFound {
105         return graph.frame(getVertex(id), cls);
106     }
107 
108     @Override
109     public <E> E getEntity(String id, EntityClass type, Class<E> cls)
110             throws ItemNotFound {
111         return graph.frame(getVertex(id), cls);
112     }
113 
114     @Override
115     public <E> CloseableIterable<E> getEntities(EntityClass type, Class<E> cls) {
116         return new WrappingCloseableIterable<>(
117                 graph.frameVertices(getVertices(type), cls));
118     }
119 
120     @Override
121     public <E> CloseableIterable<E> getEntities(String key, Object value, EntityClass type, Class<E> cls) {
122         return new WrappingCloseableIterable<>(
123                 graph.frameVertices(getVertices(key, value, type), cls));
124     }
125 
126     @Override
127     public Vertex getVertex(String id) throws ItemNotFound {
128         Preconditions
129                 .checkNotNull(id, "attempt to fetch vertex with a null id");
130         try {
131             return graph.getVertices(EntityType.ID_KEY, id).iterator().next();
132         } catch (NoSuchElementException e) {
133             throw new ItemNotFound(id);
134         }
135     }
136 
137     @Override
138     public CloseableIterable<Vertex> getVertices(EntityClass type) {
139         return new WrappingCloseableIterable<>(
140                 graph.getVertices(EntityType.TYPE_KEY, type.getName()));
141     }
142 
143     @Override
144     public CloseableIterable<Vertex> getVertices(Iterable<String> ids) {
145         Iterable<Vertex> verts = Iterables.transform(ids, id -> {
146             try {
147                 return getVertex(id);
148             } catch (ItemNotFound e) {
149                 return null;
150             }
151         });
152         return new WrappingCloseableIterable<>(verts);
153     }
154 
155     @Override
156     public CloseableIterable<Vertex> getVertices(String key, Object value, EntityClass type) {
157         // NB: This is rather annoying.
158         List<Vertex> elems = Lists.newArrayList();
159         for (Vertex v : graph.getVertices(key, value)) {
160             if (getEntityClass(v).equals(type)) {
161                 elems.add(v);
162             }
163         }
164         return new WrappingCloseableIterable<>(elems);
165     }
166 
167     @Override
168     public Vertex createVertex(String id, EntityClass type,
169             Map<String, ?> data) throws IntegrityError {
170         Preconditions
171                 .checkNotNull(id, "null vertex ID given for item creation");
172         Map<String, ?> indexData = getVertexData(id, type, data);
173         checkExists(id);
174         Vertex node = graph.addVertex(null);
175         for (Map.Entry<String, ?> entry : indexData.entrySet()) {
176             if (entry.getValue() == null)
177                 continue;
178             node.setProperty(entry.getKey(), entry.getValue());
179         }
180         return node;
181     }
182 
183     @Override
184     public Vertex updateVertex(String id, EntityClass type,
185             Map<String, ?> data) throws ItemNotFound {
186         Preconditions.checkNotNull(id, "null vertex ID given for item update");
187         Map<String, ?> indexData = getVertexData(id, type, data);
188         try {
189             Vertex node = getVertex(id);
190             replaceProperties(node, indexData);
191             return node;
192         } catch (NoSuchElementException e) {
193             throw new ItemNotFound(id);
194         }
195     }
196 
197     @Override
198     public void setProperty(Vertex vertex, String key, Object value) {
199         Preconditions.checkNotNull(vertex);
200         Preconditions.checkNotNull(key);
201         Preconditions.checkArgument(
202                 !(key.trim().isEmpty() ||
203                         key.equals(EntityType.ID_KEY) || key.equals(EntityType.TYPE_KEY)),
204                 "Invalid property key: %s", key);
205         if (value == null) {
206             vertex.removeProperty(key);
207         } else {
208             vertex.setProperty(key, value);
209         }
210     }
211 
212     @Override
213     public void renameVertex(Vertex vertex, String oldId, String newId) {
214         Preconditions.checkNotNull(vertex);
215         Preconditions.checkNotNull(newId);
216         vertex.setProperty(EntityType.ID_KEY, newId);
217     }
218 
219     @Override
220     public void deleteVertex(String id) throws ItemNotFound {
221         deleteVertex(getVertex(id));
222     }
223 
224     @Override
225     public void deleteVertex(Vertex vertex) {
226         vertex.remove();
227     }
228 
229     @Override
230     public void initialize() {
231     }
232 
233     private <E extends Element> void replaceProperties(E item, Map<String, ?> data) {
234         // remove 'old' properties
235         for (String key : item.getPropertyKeys()) {
236             if (!key.startsWith(METADATA_PREFIX)) {
237                 item.removeProperty(key);
238             }
239         }
240 
241         // add all 'new' properties to the relationship and index
242         addProperties(item, data);
243     }
244 
245     private <E extends Element> void addProperties(E item, Map<String, ?> data) {
246         Preconditions.checkNotNull(data, "Data map cannot be null");
247         for (Map.Entry<String, ?> entry : data.entrySet()) {
248             if (entry.getValue() == null)
249                 continue;
250             item.setProperty(entry.getKey(), entry.getValue());
251         }
252     }
253 
254     private void checkExists(String id) throws IntegrityError {
255         if (exists(id)) {
256             throw new IntegrityError(id);
257         }
258     }
259 
260     private Map<String, ?> getVertexData(String id, EntityClass type,
261             Map<String, ?> data) {
262         Map<String, Object> vdata = Maps.newHashMap(data);
263         vdata.put(EntityType.ID_KEY, id);
264         vdata.put(EntityType.TYPE_KEY, type.getName());
265         return vdata;
266     }
267 }