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.fasterxml.jackson.core.JsonFactory;
23 import com.fasterxml.jackson.core.JsonParser;
24 import com.fasterxml.jackson.core.JsonProcessingException;
25 import com.fasterxml.jackson.core.JsonToken;
26 import com.fasterxml.jackson.databind.JsonNode;
27 import com.fasterxml.jackson.databind.ObjectMapper;
28 import com.fasterxml.jackson.databind.ObjectWriter;
29 import com.fasterxml.jackson.databind.module.SimpleModule;
30 import com.flipkart.zjsonpatch.JsonDiff;
31 import com.google.common.base.Charsets;
32 import com.google.common.base.Preconditions;
33 import com.google.common.collect.ArrayListMultimap;
34 import com.google.common.collect.Lists;
35 import com.google.common.collect.Maps;
36 import com.google.common.collect.Multimap;
37 import com.google.common.collect.Ordering;
38 import com.tinkerpop.blueprints.CloseableIterable;
39 import eu.ehri.project.exceptions.DeserializationError;
40 import eu.ehri.project.exceptions.SerializationError;
41 import eu.ehri.project.models.EntityClass;
42
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.io.InputStreamReader;
46 import java.io.OutputStream;
47 import java.util.Collection;
48 import java.util.Collections;
49 import java.util.Iterator;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.Map.Entry;
53
54 class DataConverter {
55
56 private static final JsonFactory factory = new JsonFactory();
57 private static final ObjectMapper mapper = new ObjectMapper(factory);
58 private static final ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();
59
60 static {
61 SimpleModule bundleModule = new SimpleModule();
62 bundleModule.addDeserializer(Bundle.class, new BundleDeserializer());
63 mapper.registerModule(bundleModule);
64 }
65
66
67
68
69
70
71
72 public static Map<String, Object> errorSetToData(ErrorSet errorSet) {
73 Map<String, Object> data = Maps.newHashMap();
74 data.put(ErrorSet.ERROR_KEY, errorSet.getErrors().asMap());
75 Map<String, List<Map<String, Object>>> relations = Maps.newLinkedHashMap();
76 Multimap<String, ErrorSet> crelations = errorSet.getRelations();
77 for (String key : crelations.keySet()) {
78 List<Map<String, Object>> rels = Lists.newArrayList();
79 for (ErrorSet subbundle : crelations.get(key)) {
80 rels.add(errorSetToData(subbundle));
81 }
82 relations.put(key, rels);
83 }
84 data.put(ErrorSet.REL_KEY, relations);
85 return data;
86 }
87
88
89
90
91
92
93
94 public static String errorSetToJson(ErrorSet errorSet) throws SerializationError {
95 try {
96 Map<String, Object> data = errorSetToData(errorSet);
97 return writer.writeValueAsString(data);
98 } catch (JsonProcessingException e) {
99 throw new SerializationError("Error writing errorSet to JSON", e);
100 }
101 }
102
103
104
105
106
107
108
109 public static Map<String, Object> bundleToData(Bundle bundle) {
110 Map<String, Object> data = Maps.newLinkedHashMap();
111 data.put(Bundle.ID_KEY, bundle.getId());
112 data.put(Bundle.TYPE_KEY, bundle.getType().getName());
113 data.put(Bundle.DATA_KEY, bundle.getData());
114 if (bundle.hasMetaData()) {
115 data.put(Bundle.META_KEY, bundle.getMetaData());
116 }
117 Map<String, List<Map<String, Object>>> relations = Maps.newLinkedHashMap();
118 Multimap<String, Bundle> crelations = bundle.getRelations();
119 List<String> sortedKeys = Ordering.natural().sortedCopy(crelations.keySet());
120 for (String key : sortedKeys) {
121 List<Map<String, Object>> rels = Lists.newArrayList();
122 for (Bundle subbundle : crelations.get(key)) {
123 rels.add(bundleToData(subbundle));
124 }
125 relations.put(key, rels);
126 }
127 data.put(Bundle.REL_KEY, relations);
128 return data;
129 }
130
131
132
133
134
135
136
137 public static String bundleToJson(Bundle bundle) throws SerializationError {
138 try {
139 Map<String, Object> data = bundleToData(bundle);
140 return writer.writeValueAsString(data);
141 } catch (JsonProcessingException e) {
142 throw new SerializationError("Error writing bundle to JSON", e);
143 }
144 }
145
146
147
148
149
150
151
152
153
154 public static String diffBundles(Bundle source, Bundle target) {
155 try {
156 JsonNode diff = JsonDiff.asJson(
157 mapper.valueToTree(source), mapper.valueToTree(target));
158 return writer.writeValueAsString(diff);
159 } catch (JsonProcessingException e) {
160 throw new RuntimeException(e);
161 }
162 }
163
164
165
166
167
168
169
170
171 public static Bundle streamToBundle(InputStream inputStream) throws DeserializationError {
172 try {
173 return mapper.readValue(inputStream, Bundle.class);
174 } catch (IOException e) {
175 e.printStackTrace();
176 throw new DeserializationError("Error decoding JSON", e);
177 }
178 }
179
180
181
182
183
184
185
186 public static void bundleToStream(Bundle bundle, OutputStream outputStream) throws SerializationError {
187 try {
188 mapper.writeValue(outputStream, bundle);
189 } catch (IOException e) {
190 e.printStackTrace();
191 throw new SerializationError("Error encoding JSON", e);
192 }
193 }
194
195
196
197
198
199
200
201
202
203 public static CloseableIterable<Bundle> bundleStream(InputStream inputStream) throws DeserializationError {
204 Preconditions.checkNotNull(inputStream);
205 try {
206 final JsonParser parser = factory
207 .createParser(new InputStreamReader(inputStream, Charsets.UTF_8));
208 JsonToken jsonToken = parser.nextValue();
209 if (!parser.isExpectedStartArrayToken()) {
210 throw new DeserializationError("Stream should be an array of objects, was: " + jsonToken);
211 }
212 final Iterator<Bundle> iterator = parser.nextValue() == JsonToken.END_ARRAY
213 ? Collections.<Bundle>emptyIterator()
214 : parser.readValuesAs(Bundle.class);
215 return new CloseableIterable<Bundle>() {
216 @Override
217 public void close() {
218 try {
219 parser.close();
220 } catch (IOException e) {
221 throw new RuntimeException(e);
222 }
223 }
224
225 @Override
226 public Iterator<Bundle> iterator() {
227 return iterator;
228 }
229 };
230 } catch (IOException e) {
231 throw new DeserializationError("Error reading JSON", e);
232 }
233 }
234
235
236
237
238
239
240
241
242 public static Bundle jsonToBundle(String json) throws DeserializationError {
243 try {
244 return mapper.readValue(json, Bundle.class);
245 } catch (Exception e) {
246 e.printStackTrace();
247 throw new DeserializationError("Error decoding JSON", e);
248 }
249 }
250
251
252
253
254
255
256
257
258
259
260
261
262
263 public static Bundle dataToBundle(Object rawData)
264 throws DeserializationError {
265
266
267 if (!(rawData instanceof Map<?, ?>))
268 throw new DeserializationError("Bundle data must be a map value.");
269
270 Map<?, ?> data = (Map<?, ?>) rawData;
271 String id = (String) data.get(Bundle.ID_KEY);
272 EntityClass type = getType(data);
273
274
275
276
277
278 Map<String, Object> properties = getSanitisedProperties(data);
279 return Bundle.of(id, type, properties, getRelationships(data));
280 }
281
282
283
284
285
286
287
288
289 private static Multimap<String, Bundle> getRelationships(Map<?, ?> data)
290 throws DeserializationError {
291 Multimap<String, Bundle> relationBundles = ArrayListMultimap
292 .create();
293
294
295 Object relations = data.get(Bundle.REL_KEY);
296 if (relations == null)
297 return relationBundles;
298
299 if (relations instanceof Map) {
300 for (Entry<?, ?> entry : ((Map<?, ?>) relations).entrySet()) {
301 if (entry.getValue() instanceof List<?>) {
302 for (Object item : (List<?>) entry.getValue()) {
303 relationBundles.put((String) entry.getKey(),
304 dataToBundle(item));
305 }
306 }
307 }
308 } else {
309 throw new DeserializationError(
310 "Relationships value should be a map type");
311 }
312 return relationBundles;
313 }
314
315 private static Map<String, Object> getSanitisedProperties(Map<?, ?> data)
316 throws DeserializationError {
317 Object props = data.get(Bundle.DATA_KEY);
318 if (props != null) {
319 if (props instanceof Map) {
320 return sanitiseProperties((Map<?, ?>) props);
321 } else {
322 throw new DeserializationError(
323 "Data value not a map type! " + props.getClass().getSimpleName());
324 }
325 } else {
326 return Maps.newHashMap();
327 }
328 }
329
330 private static EntityClass getType(Map<?, ?> data)
331 throws DeserializationError {
332 try {
333 return EntityClass.withName((String) data.get(Bundle.TYPE_KEY));
334 } catch (IllegalArgumentException e) {
335 throw new DeserializationError("Bad or unknown type key: "
336 + data.get(Bundle.TYPE_KEY));
337 }
338 }
339
340 private static Map<String, Object> sanitiseProperties(Map<?, ?> data) {
341 Map<String, Object> cleaned = Maps.newHashMap();
342 for (Entry<?, ?> entry : data.entrySet()) {
343 Object value = entry.getValue();
344
345 if (!isEmptySequence(value)) {
346 cleaned.put((String) entry.getKey(), entry.getValue());
347 }
348 }
349 return cleaned;
350 }
351
352
353
354
355
356
357
358
359 static boolean isEmptySequence(Object value) {
360 if (value == null) {
361 return false;
362 } else if (value instanceof Object[]) {
363 return ((Object[]) value).length == 0;
364 } else if (value instanceof Collection<?>) {
365 return ((Collection) value).isEmpty();
366 } else if (value instanceof Iterable<?>) {
367 return !((Iterable) value).iterator().hasNext();
368 }
369 return false;
370 }
371 }