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.ArrayListMultimap;
23 import com.google.common.collect.ImmutableListMultimap;
24 import com.google.common.collect.ImmutableMap;
25 import com.google.common.collect.LinkedHashMultiset;
26 import com.google.common.collect.Lists;
27 import com.google.common.collect.Maps;
28 import com.google.common.collect.Multimap;
29 import com.google.common.collect.Sets;
30 import com.tinkerpop.blueprints.CloseableIterable;
31 import com.tinkerpop.blueprints.Direction;
32 import eu.ehri.project.exceptions.DeserializationError;
33 import eu.ehri.project.exceptions.SerializationError;
34 import eu.ehri.project.models.EntityClass;
35 import eu.ehri.project.models.idgen.IdGenerator;
36 import eu.ehri.project.models.utils.ClassUtils;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 import java.io.InputStream;
41 import java.io.OutputStream;
42 import java.util.Collection;
43 import java.util.Collections;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.Objects;
47 import java.util.Optional;
48 import java.util.Set;
49 import java.util.function.BiPredicate;
50 import java.util.function.Function;
51 import java.util.function.Predicate;
52
53 import static com.google.common.base.Preconditions.checkNotNull;
54
55
56
57
58
59
60
61
62
63
64
65 public final class Bundle implements NestableData<Bundle> {
66
67 private static final Logger logger = LoggerFactory.getLogger(Bundle.class);
68
69 private final boolean temp;
70 private final String id;
71 private final EntityClass type;
72 private final Map<String, Object> data;
73 private final ImmutableMap<String, Object> meta;
74 private final ImmutableListMultimap<String, Bundle> relations;
75
76
77
78
79 public static final String ID_KEY = "id";
80 public static final String REL_KEY = "relationships";
81 public static final String DATA_KEY = "data";
82 public static final String TYPE_KEY = "type";
83 public static final String META_KEY = "meta";
84
85
86
87
88
89
90 static final String MANAGED_PREFIX = "_";
91
92 public static class Builder {
93 private String id;
94 private final EntityClass type;
95 final Multimap<String, Bundle> relations = ArrayListMultimap.create();
96 final Map<String, Object> data = Maps.newHashMap();
97 final Map<String, Object> meta = Maps.newHashMap();
98
99 public Builder setId(String id) {
100 this.id = id;
101 return this;
102 }
103
104 private Builder(EntityClass cls) {
105 type = cls;
106 }
107
108 public static Builder withClass(EntityClass cls) {
109 return new Builder(cls);
110 }
111
112 public static Builder from(Bundle bundle) {
113 return withClass(bundle.getType())
114 .setId(bundle.getId())
115 .addMetaData(bundle.getMetaData())
116 .addData(bundle.getData())
117 .addRelations(bundle.getRelations());
118 }
119
120 public Builder addRelations(Multimap<String, Bundle> r) {
121 relations.putAll(r);
122 return this;
123 }
124
125 public Builder addRelation(String relation, Bundle bundle) {
126 relations.put(relation, bundle);
127 return this;
128 }
129
130 public Builder addData(Map<String, Object> d) {
131 data.putAll(d);
132 return this;
133 }
134
135 public Builder addDataValue(String key, Object value) {
136 data.put(key, value);
137 return this;
138 }
139
140 public Builder addDataMultiValue(String key, Object value) {
141 if (!data.containsKey(key)) {
142 data.put(key, value);
143 } else {
144 Object current = data.get(key);
145 if (current instanceof List) {
146 ((List)current).add(value);
147 data.put(key, current);
148 } else {
149 data.put(key, Lists.newArrayList(current, value));
150 }
151 }
152 return this;
153 }
154
155 public Builder addMetaData(Map<String, Object> d) {
156 meta.putAll(d);
157 return this;
158 }
159
160 public Builder addMetaDataValue(String key, Object value) {
161 meta.put(key, value);
162 return this;
163 }
164
165 public Bundle build() {
166 return of(id, type, data, relations, meta);
167 }
168 }
169
170
171
172
173
174
175
176
177
178
179
180 private Bundle(String id, EntityClass type, Map<String, Object> data,
181 Multimap<String, Bundle> relations, Map<String, Object> meta, boolean temp) {
182 this.id = id;
183 this.type = type;
184 this.data = filterData(data);
185 this.meta = ImmutableMap.copyOf(meta);
186 this.relations = ImmutableListMultimap.copyOf(relations);
187 this.temp = temp;
188 }
189
190
191
192
193
194
195
196
197
198 public static Bundle of(String id, EntityClass type, Map<String, Object> data,
199 Multimap<String, Bundle> relations) {
200 return of(id, type, data, relations, Maps.<String, Object>newHashMap());
201 }
202
203
204
205
206
207
208
209
210
211
212 public static Bundle of(String id, EntityClass type, Map<String, Object> data,
213 Multimap<String, Bundle> relations, Map<String, Object> meta) {
214 return new Bundle(id, type, data, relations, meta, false);
215 }
216
217
218
219
220
221
222
223
224 public static Bundle of(EntityClass type, Map<String, Object> data,
225 Multimap<String, Bundle> relations) {
226 return of(null, type, data, relations);
227 }
228
229
230
231
232
233
234 public static Bundle of(EntityClass type) {
235 return of(null, type, Maps.<String, Object>newHashMap(), ArrayListMultimap
236 .<String, Bundle>create(), Maps.<String, Object>newHashMap());
237 }
238
239
240
241
242
243
244
245 public static Bundle of(EntityClass type, Map<String, Object> data) {
246 return of(null, type, data, ArrayListMultimap.<String, Bundle>create(),
247 Maps.<String, Object>newHashMap());
248 }
249
250
251
252
253
254
255
256 public String getId() {
257 return id;
258 }
259
260
261
262
263
264
265 public Bundle withId(String id) {
266 checkNotNull(id);
267 return new Bundle(id, type, data, relations, meta, temp);
268 }
269
270
271
272
273
274
275
276 public EntityClass getType() {
277 return type;
278 }
279
280
281
282
283
284
285
286
287 @SuppressWarnings("unchecked")
288 @Override
289 public <T> T getDataValue(String key) throws ClassCastException {
290 checkNotNull(key);
291 return (T) data.get(key);
292 }
293
294
295
296
297
298
299
300
301
302 @Override
303 public Bundle withDataValue(String key, Object value) {
304 Map<String, Object> newData = Maps.newHashMap(data);
305 newData.put(key, value);
306 return withData(newData);
307 }
308
309
310
311
312
313
314
315
316 public Bundle withMetaDataValue(String key, Object value) {
317 if (value == null) {
318 return this;
319 } else {
320 Map<String, Object> newData = Maps.newHashMap(meta);
321 newData.put(key, value);
322 return withMetaData(newData);
323 }
324 }
325
326
327
328
329
330
331
332 @Override
333 public Bundle removeDataValue(String key) {
334 Map<String, Object> newData = Maps.newHashMap(data);
335 newData.remove(key);
336 return withData(newData);
337 }
338
339
340
341
342
343
344 public Map<String, Object> getData() {
345 return ImmutableMap.copyOf(Maps.filterValues(data, Objects::nonNull));
346 }
347
348
349
350
351
352
353 public Map<String, Object> getMetaData() {
354 return meta;
355 }
356
357
358
359
360
361
362
363 public boolean hasMetaData() {
364 return !meta.isEmpty();
365 }
366
367
368
369
370
371
372
373 public Bundle withData(Map<String, Object> data) {
374 return new Bundle(id, type, data, relations, meta, temp);
375 }
376
377
378
379
380
381
382
383 public Bundle withMetaData(Map<String, Object> meta) {
384 return new Bundle(id, type, data, relations, meta, temp);
385 }
386
387
388
389
390
391
392 @Override
393 public Multimap<String, Bundle> getRelations() {
394 return relations;
395 }
396
397
398
399
400
401
402
403 public Multimap<String, Bundle> getDependentRelations() {
404 Multimap<String, Bundle> dependentRelations = ArrayListMultimap.create();
405 Map<String, Direction> dependents = ClassUtils
406 .getDependentRelations(type.getJavaClass());
407 for (String relation : relations.keySet()) {
408 if (dependents.containsKey(relation)) {
409 for (Bundle child : relations.get(relation)) {
410 dependentRelations.put(relation, child);
411 }
412 }
413 }
414 return dependentRelations;
415 }
416
417
418
419
420
421
422
423 @Override
424 public Bundle replaceRelations(Multimap<String, Bundle> relations) {
425 return new Bundle(id, type, data, relations, meta, temp);
426 }
427
428
429
430
431
432
433
434 @Override
435 public Bundle withRelations(Multimap<String, Bundle> others) {
436 Multimap<String, Bundle> tmp = ArrayListMultimap
437 .create(relations);
438 tmp.putAll(others);
439 return new Bundle(id, type, data, tmp, meta, temp);
440 }
441
442
443
444
445
446
447
448 @Override
449 public List<Bundle> getRelations(String relation) {
450 return relations.get(relation);
451 }
452
453
454
455
456
457
458
459
460 @Override
461 public Bundle withRelations(String relation, List<Bundle> others) {
462 Multimap<String, Bundle> tmp = ArrayListMultimap
463 .create(relations);
464 tmp.putAll(relation, others);
465 return new Bundle(id, type, data, tmp, meta, temp);
466 }
467
468
469
470
471
472
473
474
475 @Override
476 public Bundle withRelation(String relation, Bundle other) {
477 Multimap<String, Bundle> tmp = ArrayListMultimap
478 .create(relations);
479 tmp.put(relation, other);
480 return new Bundle(id, type, data, tmp, meta, temp);
481 }
482
483
484
485
486
487
488
489 public boolean hasRelations(String relation) {
490 return relations.containsKey(relation);
491 }
492
493
494
495
496
497
498
499
500 public Bundle removeRelation(String relation, Bundle item) {
501 Multimap<String, Bundle> tmp = ArrayListMultimap.create(relations);
502 tmp.remove(relation, item);
503 return new Bundle(id, type, data, tmp, meta, temp);
504 }
505
506
507
508
509
510
511
512
513
514 public Bundle mergeDataWith(final Bundle otherBundle) {
515 Map<String, Object> mergeData = Maps.newHashMap(getData());
516
517
518
519 logger.trace("Merging data: {}", otherBundle.data);
520 for (Map.Entry<String, Object> entry : otherBundle.data.entrySet()) {
521 if (entry.getValue() != null) {
522 mergeData.put(entry.getKey(), entry.getValue());
523 } else {
524 logger.trace("Unset key in merge: {}", entry.getKey());
525 mergeData.remove(entry.getKey());
526 }
527 }
528 final Builder builder = Builder.withClass(getType()).setId(getId()).addMetaData(meta)
529 .addData(mergeData);
530
531
532
533 for (Map.Entry<String, Collection<Bundle>> entry : otherBundle.getRelations().asMap().entrySet()) {
534 String relName = entry.getKey();
535 if (relations.containsKey(relName)) {
536 List<Bundle> relations = getRelations(relName);
537 Collection<Bundle> otherRelations = entry.getValue();
538 Set<Bundle> updated = Sets.newHashSet();
539 for (final Bundle otherRel : otherRelations) {
540 Optional<Bundle> toUpdate = relations.stream().filter(
541 bundle -> bundle.getId() != null && bundle.getId().equals(otherRel.getId()))
542 .findFirst();
543 if (toUpdate.isPresent()) {
544 Bundle up = toUpdate.get();
545 updated.add(up);
546 builder.addRelation(relName, up.mergeDataWith(otherRel));
547 } else {
548 logger.warn("Ignoring nested bundle in PATCH update: {}", otherRel);
549 }
550 }
551 for (Bundle bundle : relations) {
552 if (!updated.contains(bundle)) {
553 builder.addRelation(relName, bundle);
554 }
555 }
556 }
557 }
558
559 for (Map.Entry<String, Bundle> entry : relations.entries()) {
560 if (!otherBundle.hasRelations(entry.getKey())) {
561 builder.addRelation(entry.getKey(), entry.getValue());
562 }
563 }
564
565 return builder.build();
566 }
567
568
569
570
571
572
573
574
575
576 public Bundle filterRelations(BiPredicate<String, Bundle> filter) {
577 final Multimap<String, Bundle> newRels = ArrayListMultimap.create();
578 for (Map.Entry<String, Bundle> rel : relations.entries()) {
579 if (!filter.test(rel.getKey(), rel.getValue())) {
580 newRels.put(rel.getKey(), rel.getValue()
581 .filterRelations(filter));
582 }
583 }
584 return replaceRelations(newRels);
585 }
586
587
588
589
590
591
592
593
594 public Bundle map(Function<Bundle, Bundle> f) {
595 Bundle me = f.apply(this);
596 final Multimap<String, Bundle> newRels = ArrayListMultimap.create();
597 for (Map.Entry<String, Bundle> rel : me.getRelations().entries()) {
598 newRels.put(rel.getKey(), rel.getValue().map(f));
599 }
600 return me.replaceRelations(newRels);
601 }
602
603
604
605
606
607
608
609
610 public boolean forAny(Predicate<Bundle> f) {
611 return find(f).isPresent();
612 }
613
614 public Optional<Bundle> find(Predicate<Bundle> f) {
615 if (f.test(this)) {
616 return Optional.of(this);
617 }
618 for (Map.Entry<String, Bundle> rel : relations.entries()) {
619 Optional<Bundle> findRel = rel.getValue().find(f);
620 if (findRel.isPresent()) {
621 return findRel;
622 }
623 }
624 return Optional.empty();
625 }
626
627
628
629
630
631
632 public Class<?> getBundleJavaClass() {
633 return type.getJavaClass();
634 }
635
636
637
638
639
640
641
642 public Collection<String> getPropertyKeys() {
643 return ClassUtils.getPropertyKeys(type.getJavaClass());
644 }
645
646
647
648
649
650
651 public Collection<String> getUniquePropertyKeys() {
652 return ClassUtils.getUniquePropertyKeys(type.getJavaClass());
653 }
654
655
656
657
658
659
660
661 public static Bundle fromData(Object data) throws DeserializationError {
662 return DataConverter.dataToBundle(data);
663 }
664
665
666
667
668
669
670 public Map<String, Object> toData() {
671 return DataConverter.bundleToData(this);
672 }
673
674
675
676
677
678
679
680 public static Bundle fromString(String json) throws DeserializationError {
681 return DataConverter.jsonToBundle(json);
682 }
683
684
685
686
687
688
689
690 public static Bundle fromStream(InputStream stream) throws DeserializationError {
691 return DataConverter.streamToBundle(stream);
692 }
693
694
695
696
697
698
699
700 public static void toStream(Bundle bundle, OutputStream stream) throws SerializationError {
701 DataConverter.bundleToStream(bundle, stream);
702 }
703
704 public static CloseableIterable<Bundle> bundleStream(InputStream inputStream) throws DeserializationError {
705 return DataConverter.bundleStream(inputStream);
706 }
707
708 @Override
709 public String toString() {
710 return "<" + getType() + ": '" + (id == null ? "?" : id) + "'> (" + getData() + " + Rels: " + relations + ")";
711 }
712
713
714
715
716
717
718 public String toJson() {
719 try {
720 return DataConverter.bundleToJson(this);
721 } catch (SerializationError e) {
722 return "Invalid Bundle: " + e.getMessage();
723 }
724 }
725
726
727
728
729
730
731 public boolean hasGeneratedId() {
732 return temp;
733 }
734
735
736
737
738
739
740
741 public int depth() {
742 int depth = 0;
743 for (Bundle rel : relations.values()) {
744 depth = Math.max(depth, 1 + rel.depth());
745 }
746 return depth;
747 }
748
749
750
751
752
753
754 public Bundle dependentsOnly() {
755 Map<String, Direction> dependents = ClassUtils
756 .getDependentRelations(type.getJavaClass());
757 Multimap<String, Bundle> tmp = ArrayListMultimap.create();
758 for (String relation : relations.keySet()) {
759 if (dependents.containsKey(relation)) {
760 for (Bundle bundle : relations.get(relation)) {
761 tmp.put(relation, bundle.dependentsOnly());
762 }
763 }
764 }
765 return new Bundle(id, type, data, tmp, meta, temp);
766 }
767
768
769
770
771
772
773
774 public Bundle generateIds(Collection<String> scopes) {
775 boolean isTemp = id == null;
776 IdGenerator idGen = getType().getIdGen();
777 String newId = isTemp ? idGen.generateId(scopes, this) : id;
778 Multimap<String, Bundle> idRels = ArrayListMultimap.create();
779 List<String> nextScopes = Lists.newArrayList(scopes);
780 nextScopes.add(idGen.getIdBase(this));
781 for (Map.Entry<String, Bundle> entry : relations.entries()) {
782 idRels.put(entry.getKey(), entry.getValue().generateIds(nextScopes));
783 }
784 return new Bundle(newId, type, data, idRels, meta, isTemp);
785 }
786
787 @Override
788 public boolean equals(Object o) {
789 if (this == o) return true;
790 if (o == null || getClass() != o.getClass()) return false;
791
792 Bundle bundle = (Bundle) o;
793
794 return type == bundle.type
795 && unmanagedData(data).equals(unmanagedData(bundle.data))
796 && unorderedRelations(relations).equals(unorderedRelations(bundle.relations));
797 }
798
799 @Override
800 public int hashCode() {
801 int result = type.hashCode();
802 result = 31 * result + unmanagedData(data).hashCode();
803 result = 31 * result + unorderedRelations(relations).hashCode();
804 return result;
805 }
806
807
808
809
810
811
812
813
814 public String diff(Bundle target) {
815 return DataConverter.diffBundles(
816 withMetaData(Collections.emptyMap()),
817 target.withMetaData(Collections.emptyMap()));
818 }
819
820
821
822
823
824
825 private Map<String, Object> filterData(Map<String, Object> data) {
826 Map<String, Object> filtered = Maps.newHashMap();
827 for (Map.Entry<? extends String, Object> entry : data.entrySet()) {
828 Object value = entry.getValue();
829 if (value instanceof Enum<?>) {
830 value = ((Enum) value).name();
831 }
832 filtered.put(entry.getKey(), value);
833 }
834 return filtered;
835 }
836
837
838
839
840
841 private Map<String, Object> unmanagedData(Map<String, Object> in) {
842 Map<String, Object> filtered = Maps.newHashMap();
843 for (Map.Entry<? extends String, Object> entry : in.entrySet()) {
844 if (!entry.getKey().startsWith(MANAGED_PREFIX)
845 && entry.getValue() != null) {
846 filtered.put(entry.getKey(), entry.getValue());
847 }
848 }
849 return filtered;
850 }
851
852
853
854
855 private Map<String, LinkedHashMultiset<Bundle>> unorderedRelations(Multimap<String, Bundle> rels) {
856 Map<String, LinkedHashMultiset<Bundle>> map = Maps.newHashMap();
857 for (Map.Entry<String, Collection<Bundle>> entry : rels.asMap().entrySet()) {
858 map.put(entry.getKey(), LinkedHashMultiset.create(entry.getValue()));
859 }
860 return map;
861 }
862 }