1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package eu.ehri.project.persistence;
21
22 import com.google.common.collect.Lists;
23 import com.google.common.collect.Multimap;
24 import com.google.common.collect.Sets;
25 import com.tinkerpop.blueprints.Direction;
26 import com.tinkerpop.blueprints.Vertex;
27 import com.tinkerpop.frames.FramedGraph;
28 import eu.ehri.project.core.GraphManager;
29 import eu.ehri.project.core.GraphManagerFactory;
30 import eu.ehri.project.exceptions.IntegrityError;
31 import eu.ehri.project.exceptions.ItemNotFound;
32 import eu.ehri.project.exceptions.SerializationError;
33 import eu.ehri.project.exceptions.ValidationError;
34 import eu.ehri.project.models.base.Entity;
35 import eu.ehri.project.models.utils.ClassUtils;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 import java.util.Collection;
40 import java.util.HashSet;
41 import java.util.Map;
42 import java.util.Map.Entry;
43 import java.util.Set;
44
45
46
47
48
49 public final class BundleManager {
50
51 private static final Logger logger = LoggerFactory.getLogger(BundleManager.class);
52
53 private final FramedGraph<?> graph;
54 private final GraphManager manager;
55 private final Serializer serializer;
56 private final BundleValidator validator;
57
58
59
60
61
62
63
64 public BundleManager(FramedGraph<?> graph, Collection<String> scopeIds) {
65 this.graph = graph;
66 manager = GraphManagerFactory.getInstance(graph);
67 serializer = new Serializer.Builder(graph).dependentOnly().build();
68 validator = new BundleValidator(manager, scopeIds);
69 }
70
71
72
73
74
75
76 public BundleManager(FramedGraph<?> graph) {
77 this(graph, Lists.<String>newArrayList());
78 }
79
80
81
82
83
84
85
86
87 public <T extends Entity> Mutation<T> update(Bundle bundle, Class<T> cls)
88 throws ValidationError, ItemNotFound {
89 Bundle bundleWithIds = validator.validateForUpdate(bundle);
90 Mutation<Vertex> mutation = updateInner(bundleWithIds);
91 return new Mutation<>(graph.frame(mutation.getNode(), cls),
92 mutation.getState(), mutation.getPrior());
93 }
94
95
96
97
98
99
100
101
102 public <T extends Entity> T create(Bundle bundle, Class<T> cls)
103 throws ValidationError {
104 Bundle bundleWithIds = validator.validateForCreate(bundle);
105 return graph.frame(createInner(bundleWithIds), cls);
106 }
107
108
109
110
111
112
113
114
115 public <T extends Entity> Mutation<T> createOrUpdate(Bundle bundle, Class<T> cls)
116 throws ValidationError {
117 Bundle bundleWithIds = validator.validateForUpdate(bundle);
118 Mutation<Vertex> vertexMutation = createOrUpdateInner(bundleWithIds);
119 return new Mutation<>(graph.frame(vertexMutation.getNode(), cls), vertexMutation.getState(),
120 vertexMutation.getPrior());
121 }
122
123
124
125
126
127
128
129 public int delete(Bundle bundle) {
130 try {
131 return deleteCount(bundle, 0);
132 } catch (Exception e) {
133 throw new RuntimeException(e);
134 }
135 }
136
137 public BundleManager withScopeIds(Collection<String> scopeIds) {
138 return new BundleManager(graph, scopeIds);
139 }
140
141
142 private int deleteCount(Bundle bundle, int count) throws Exception {
143 Integer c = count;
144
145 for (Bundle child : bundle.getDependentRelations().values()) {
146 c = deleteCount(child, c);
147 }
148 manager.deleteVertex(bundle.getId());
149 return c + 1;
150 }
151
152
153
154
155
156
157
158
159
160 private Mutation<Vertex> createOrUpdateInner(Bundle bundle) {
161 try {
162 if (manager.exists(bundle.getId())) {
163 return updateInner(bundle);
164 } else {
165 return new Mutation<>(createInner(bundle), MutationState.CREATED);
166 }
167 } catch (ItemNotFound e) {
168 throw new RuntimeException(
169 "Create or update failed because ItemNotFound was thrown even though exists() was true",
170 e);
171 }
172 }
173
174
175
176
177
178
179
180
181 private Vertex createInner(Bundle bundle) {
182 try {
183 Vertex node = manager.createVertex(bundle.getId(), bundle.getType(),
184 bundle.getData());
185 createDependents(node, bundle.getBundleJavaClass(), bundle.getRelations());
186 return node;
187 } catch (IntegrityError e) {
188
189
190 throw new RuntimeException(
191 "Unexpected state: ID generation error not handled by IdGenerator class " + e.getMessage());
192 }
193 }
194
195
196
197
198
199
200
201 private Mutation<Vertex> updateInner(Bundle bundle) throws ItemNotFound {
202 Vertex node = manager.getVertex(bundle.getId());
203 try {
204 Bundle currentBundle = serializer.vertexToBundle(node);
205 Bundle newBundle = bundle.dependentsOnly();
206 if (!currentBundle.equals(newBundle)) {
207 if (logger.isTraceEnabled()) {
208 logger.trace("Bundles differ: {}", bundle.getId());
209 logger.trace(currentBundle.diff(newBundle));
210 }
211 node = manager.updateVertex(bundle.getId(), bundle.getType(),
212 bundle.getData());
213 updateDependents(node, bundle.getBundleJavaClass(), bundle.getRelations());
214 return new Mutation<>(node, MutationState.UPDATED, currentBundle);
215 } else {
216 logger.debug("Not updating equivalent bundle: {}", bundle.getId());
217 return new Mutation<>(node, MutationState.UNCHANGED);
218 }
219 } catch (SerializationError serializationError) {
220 throw new RuntimeException("Unexpected serialization error " +
221 "checking bundle for equivalency", serializationError);
222 }
223 }
224
225
226
227
228
229
230
231
232 private void createDependents(Vertex master,
233 Class<?> cls, Multimap<String, Bundle> relations) {
234 Map<String, Direction> dependents = ClassUtils
235 .getDependentRelations(cls);
236 for (String relation : relations.keySet()) {
237 if (dependents.containsKey(relation)) {
238 for (Bundle bundle : relations.get(relation)) {
239 Vertex child = createInner(bundle);
240 createChildRelationship(master, child, relation,
241 dependents.get(relation));
242 }
243 } else {
244 logger.error("Nested data being ignored on creation because it is not a dependent relation: {}: {}", relation, relations.get(relation));
245 }
246 }
247 }
248
249
250
251
252
253
254
255
256 private void updateDependents(Vertex master, Class<?> cls, Multimap<String, Bundle> relations) {
257
258
259
260 Map<String, Direction> dependents = ClassUtils
261 .getDependentRelations(cls);
262
263
264 Set<String> updating = getUpdateSet(relations);
265
266 deleteMissingFromUpdateSet(master, dependents, updating);
267
268
269 for (String relation : relations.keySet()) {
270 if (dependents.containsKey(relation)) {
271 Direction direction = dependents.get(relation);
272
273
274
275
276
277 Set<Vertex> currentRels = getCurrentRelationships(master,
278 relation, direction);
279
280 for (Bundle bundle : relations.get(relation)) {
281 Vertex child = createOrUpdateInner(bundle).getNode();
282
283 if (!currentRels.contains(child)) {
284 createChildRelationship(master, child, relation,
285 direction);
286 }
287 }
288 } else {
289 logger.warn("Nested data being ignored on update because " +
290 "it is not a dependent relation: {}: {}",
291 relation, relations.get(relation));
292 }
293 }
294 }
295
296 private Set<String> getUpdateSet(Multimap<String, Bundle> relations) {
297 Set<String> updating = new HashSet<>();
298 for (String relation : relations.keySet()) {
299 for (Bundle child : relations.get(relation)) {
300 updating.add(child.getId());
301 }
302 }
303 return updating;
304 }
305
306 private void deleteMissingFromUpdateSet(Vertex master,
307 Map<String, Direction> dependents, Set<String> updating) {
308 for (Entry<String, Direction> relEntry : dependents.entrySet()) {
309 for (Vertex v : getCurrentRelationships(master,
310 relEntry.getKey(), relEntry.getValue())) {
311 if (!updating.contains(manager.getId(v))) {
312 try {
313 delete(serializer.entityToBundle(graph.frame(v,
314 manager.getEntityClass(v).getJavaClass())));
315 } catch (SerializationError e) {
316 throw new RuntimeException(e);
317 }
318 }
319 }
320 }
321 }
322
323
324
325
326
327
328
329
330
331
332 private Set<Vertex> getCurrentRelationships(Vertex src,
333 String label, Direction direction) {
334 HashSet<Vertex> out = Sets.newHashSet();
335 for (Vertex end : src.getVertices(direction, label)) {
336 out.add(end);
337 }
338 return out;
339 }
340
341
342
343
344
345
346
347
348
349 private void createChildRelationship(Vertex master, Vertex child,
350 String label, Direction direction) {
351 if (direction == Direction.OUT) {
352 graph.addEdge(null, master, child, label);
353 } else {
354 graph.addEdge(null, child, master, label);
355 }
356 }
357 }