1 package com.trendmicro.grid.acl.ds.jpa.util;
2
3 import com.trendmicro.grid.acl.commons.SoftReferenceThreadLocal;
4 import com.trendmicro.grid.acl.l0.datatypes.AbstractListPage;
5 import com.trendmicro.grid.acl.l0.datatypes.MetadataOwner;
6 import com.trendmicro.grid.acl.l0.datatypes.NameListPage;
7 import com.trendmicro.grid.acl.l0.datatypes.Tagged;
8 import com.trendmicro.grid.acl.metadata.Metadata;
9 import com.trendmicro.grid.acl.metadata.ValidationContext;
10
11 import javax.persistence.NonUniqueResultException;
12 import javax.persistence.Query;
13 import javax.persistence.TypedQuery;
14 import javax.xml.bind.JAXBException;
15 import javax.xml.bind.Marshaller;
16 import javax.xml.bind.PropertyException;
17 import javax.xml.bind.Unmarshaller;
18 import javax.xml.transform.Source;
19 import javax.xml.transform.stax.StAXSource;
20 import javax.xml.transform.stream.StreamSource;
21 import java.io.StringReader;
22 import java.io.StringWriter;
23 import java.nio.ByteBuffer;
24 import java.util.*;
25 import java.util.concurrent.Callable;
26
27 import static com.trendmicro.grid.acl.Limits.SERIALIZED_METADATA_LENGTH;
28 import static com.trendmicro.grid.acl.Limits.TAG_STRING_LENGTH;
29 import static net.sf.tinyjee.util.Assert.assertEquals;
30
31
32
33
34
35
36
37 public class JpaUtils {
38
39
40
41
42
43
44
45
46
47
48 public static final boolean EMPTY_METADATA_EQUALS_NULL = Boolean.parseBoolean(
49 System.getProperty("gacl.empty.metadata.equals.null", Boolean.TRUE.toString()));
50
51
52
53
54 private static final TagsSerializedCache TAGS_SERIALIZED_CACHE = new TagsSerializedCache();
55
56
57
58
59 private static final SoftReferenceThreadLocal<Unmarshaller> META_UNMARSHALLERS =
60 new SoftReferenceThreadLocal<Unmarshaller>() {
61 @Override
62 protected Unmarshaller createInitialValue() throws Exception {
63 return Metadata.getXmlSerializer().getJaxbContext().createUnmarshaller();
64 }
65 };
66
67
68
69
70 private static final SoftReferenceThreadLocal<Marshaller> META_MARSHALLERS = new SoftReferenceThreadLocal<Marshaller>() {
71 @Override
72 protected Marshaller createInitialValue() throws Exception {
73 Marshaller marshaller = Metadata.getXmlSerializer().getJaxbContext().createMarshaller();
74 try {
75 marshaller.setProperty("com.sun.xml.bind.xmlDeclaration", Boolean.FALSE);
76 } catch (PropertyException ignored) {
77 marshaller.setProperty("com.sun.xml.internal.bind.xmlDeclaration", Boolean.FALSE);
78 }
79 return marshaller;
80 }
81 };
82
83
84
85
86
87
88
89 public interface Callback<E, V> {
90 V call(E element);
91 }
92
93
94
95
96 public static final Callback SINGLE_ELEMENT_CALLBACK = new Callback<Object, Object>() {
97 @Override
98 public Object call(Object element) {
99 if (element instanceof Object[]) {
100 Object[] array = (Object[]) element;
101 return array[array.length - 1];
102 }
103 return element;
104 }
105 };
106
107
108
109
110
111
112
113 public static byte[] toBytes(UUID guid) {
114 if (guid == null)
115 return null;
116 return ByteBuffer.allocate(16).putLong(guid.getMostSignificantBits()).putLong(guid.getLeastSignificantBits()).array();
117 }
118
119
120
121
122
123
124
125 public static UUID fromBytes(byte[] publicGUID) {
126 if (publicGUID == null)
127 return null;
128
129 assertEquals("publicGUID#length", 16, publicGUID.length);
130
131 ByteBuffer buffer = ByteBuffer.wrap(publicGUID);
132 return new UUID(buffer.getLong(), buffer.getLong());
133 }
134
135
136
137
138
139
140
141 public static String serializeTags(String[] tags) {
142 if (tags == null || tags.length == 0) return null;
143
144
145
146 String result = TAGS_SERIALIZED_CACHE.get(tags);
147
148 if (result == null) {
149
150 tags = tags.clone();
151
152 final Locale locale = Locale.getDefault();
153 int len = 1;
154 for (int i = 0; i < tags.length; i++) {
155 final String tag = tags[i];
156 len += tag.length() + 1;
157 tags[i] = tag.replace(' ', '_').toLowerCase(locale);
158 }
159
160 result = TAGS_SERIALIZED_CACHE.get(tags);
161
162 if (result == null) {
163
164 Arrays.sort(tags);
165
166 final StringBuilder builder = new StringBuilder(len).append(' ');
167 for (String tag : tags)
168 builder.append(tag).append(' ');
169
170 result = builder.toString();
171 if (result.length() > TAG_STRING_LENGTH) {
172 throw new IllegalArgumentException("The tag list '" + result + "' exceeds the maximum length of '" +
173 TAG_STRING_LENGTH + "' characters.");
174 }
175
176 TAGS_SERIALIZED_CACHE.put(tags, result);
177 }
178 }
179
180 return result;
181 }
182
183
184
185
186
187
188
189 public static String[] deserializeTags(String tags) {
190 if (tags == null || tags.isEmpty()) return null;
191
192 String[] result = TAGS_SERIALIZED_CACHE.getReverse(tags);
193
194 if (result == null) {
195 List<String> tagList = new ArrayList<String>(5 + (tags.length() / 8));
196
197 final Locale locale = Locale.getDefault();
198 final StringTokenizer tok = new StringTokenizer(tags, " ");
199 while (tok.hasMoreTokens()) {
200 tagList.add(tok.nextToken().toLowerCase(locale));
201 }
202
203 result = tagList.toArray(new String[tagList.size()]);
204
205
206
207 Arrays.sort(result);
208
209 TAGS_SERIALIZED_CACHE.put(tags, result);
210 }
211
212
213 return result.clone();
214 }
215
216
217
218
219
220
221
222 public static String serializeMetadata(Metadata metadata) {
223 if (metadata == null || (EMPTY_METADATA_EQUALS_NULL && metadata.getMetaElements().isEmpty())) return null;
224
225
226 metadata.assertValuesAreValidForWrite();
227
228 final StringWriter w = new StringWriter(2048);
229 try {
230 META_MARSHALLERS.getValue().marshal(metadata, new LimitedWriter(w, SERIALIZED_METADATA_LENGTH));
231 return w.toString();
232 } catch (JAXBException e) {
233 if (e.getCause() instanceof LimitedWriter.OutOfBounds) {
234 throw new IllegalArgumentException("The metadata element exceeds the maximum length of '" +
235 SERIALIZED_METADATA_LENGTH + "' characters.\n\n" + w.toString(), e);
236 } else
237 throw new RuntimeException(e);
238 }
239 }
240
241
242
243
244
245
246
247 public static Metadata deserializeMetadata(final String rawData) {
248 if (rawData == null || rawData.isEmpty()) return null;
249
250 return deserializeMetadata(new StreamSource(new StringReader(rawData)));
251 }
252
253
254
255
256
257
258
259 public static Metadata deserializeMetadata(final Source rawData) {
260 if (rawData == null) return null;
261
262 try {
263 return ValidationContext.getInstance().doUnvalidated(new Callable<Metadata>() {
264 @Override
265 public Metadata call() throws Exception {
266 final Unmarshaller um = META_UNMARSHALLERS.getValue();
267 final Metadata metadata;
268 if (rawData instanceof StAXSource) {
269 final StAXSource source = (StAXSource) rawData;
270 metadata = source.getXMLEventReader() == null ?
271 (Metadata) um.unmarshal(source.getXMLStreamReader()) :
272 (Metadata) um.unmarshal(source.getXMLEventReader());
273 } else
274 metadata = (Metadata) um.unmarshal(rawData);
275
276 if (EMPTY_METADATA_EQUALS_NULL && metadata.getMetaElements().isEmpty())
277 return null;
278
279 return metadata;
280 }
281 });
282 } catch (Exception e) {
283 throw new RuntimeException(e);
284 }
285 }
286
287
288
289
290
291
292
293
294 public static boolean metadataDiffers(MetadataOwner a, MetadataOwner b) {
295 final Metadata ma = a.getMetadata(), mb = b.getMetadata();
296 if (ma == null)
297 return mb != null;
298 else
299 return mb == null || !mb.equals(ma);
300 }
301
302
303
304
305
306
307
308
309 public static boolean tagsDiffer(Tagged a, Tagged b) {
310 Collection<String> ta = a.getTags(), tb = b.getTags();
311
312 if (ta.size() != tb.size()) return true;
313
314 ta = new HashSet<String>(ta);
315 for (String s : tb) {
316 if (!ta.contains(s)) return true;
317 }
318
319 return false;
320 }
321
322
323
324
325
326
327
328
329 public static Boolean isTaggedWith(Tagged element, String[] tags) {
330 if (element == null)
331 return null;
332 else {
333 for (String tag : tags) {
334 boolean invert = tag.startsWith("-");
335 boolean containsTag = element.containsTag(invert ? tag.substring(1) : tag);
336 if (invert ? containsTag : !containsTag) return false;
337 }
338 return true;
339 }
340 }
341
342
343
344
345
346
347
348
349 public static String extractName(String name, int delimiterCount) {
350 int endIdx = -1;
351 for (int i = 0; i < delimiterCount; i++)
352 endIdx = name.indexOf(':', endIdx + 1);
353
354 return endIdx == -1 ? name : name.substring(0, endIdx);
355 }
356
357
358
359
360
361
362
363
364
365 public static <T> void appendSingleElementToList(final Collection<T> result, final TypedQuery<? extends T> query) {
366 final List<? extends T> ts = query.getResultList();
367
368 if (ts.isEmpty())
369 result.add(null);
370 else if (ts.size() > 1)
371 throw new NonUniqueResultException("More than one result returned for query " + query);
372 else
373 result.add(ts.get(0));
374 }
375
376
377
378
379
380
381
382
383
384 public static <Q extends Query> Q applyPage(Q query, int pageNumber, int pageSize) {
385 query.setFirstResult(pageNumber * pageSize);
386 query.setMaxResults(pageSize + 1);
387 return query;
388 }
389
390
391
392
393
394
395
396
397 @SuppressWarnings("unchecked")
398 public static <E, T extends AbstractListPage<E>> T toListPage(TypedQuery<E> query, T emptyListPage) {
399 return toListPage(query, (Callback<E, E>) SINGLE_ELEMENT_CALLBACK, emptyListPage);
400 }
401
402
403
404
405
406
407
408
409
410
411 public static <E, V, T extends AbstractListPage<E>> T toListPage(
412 TypedQuery<V> query, Callback<V, E> converterCallback, T emptyListPage) {
413 return toListPage((Query) query, converterCallback, emptyListPage);
414 }
415
416
417
418
419
420
421
422
423
424
425 @SuppressWarnings("unchecked")
426 public static <E, V, T extends AbstractListPage<E>> T toListPage(Query query, Callback<V, E> converterCallback, T emptyListPage) {
427
428 final int pageSize = query.getMaxResults() - 1, pageNumber = query.getFirstResult() / pageSize;
429
430 final List<V> results = (List<V>) query.getResultList();
431 final List<E> pageElements = new ArrayList<E>(results.size());
432
433 int count = 0;
434 for (V result : results) {
435 if (++count > pageSize) break;
436
437 pageElements.add(converterCallback.call(result));
438 }
439
440 if (pageElements.isEmpty()) return null;
441
442 emptyListPage.setPageNumber(pageNumber);
443 emptyListPage.setLastPage(count <= pageSize);
444 emptyListPage.setElements(pageElements);
445 return emptyListPage;
446 }
447
448
449
450
451
452
453
454
455
456 @SuppressWarnings("unchecked")
457 public static NameListPage toNameListPage(Query query, int pageNumber, int pageSize) {
458 applyPage(query, pageNumber, pageSize);
459 return toListPage(query, (Callback<String, String>) SINGLE_ELEMENT_CALLBACK, new NameListPage());
460 }
461
462
463
464
465
466
467
468
469
470 public static NameListPage toNameListPage(TypedQuery<String> query, int pageNumber, int pageSize) {
471 return toNameListPage((Query) query, pageNumber, pageSize);
472 }
473
474 private JpaUtils() {
475 }
476 }