1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.fosstrak.epcis.repository.capture;
22
23 import java.io.BufferedReader;
24 import java.io.File;
25 import java.io.FileReader;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.StringWriter;
29 import java.net.URI;
30 import java.net.URISyntaxException;
31 import java.net.URL;
32 import java.security.Principal;
33 import java.sql.SQLException;
34 import java.text.ParseException;
35 import java.util.ArrayList;
36 import java.util.Calendar;
37 import java.util.GregorianCalendar;
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.regex.Matcher;
42 import java.util.regex.Pattern;
43
44 import javax.xml.XMLConstants;
45 import javax.xml.parsers.DocumentBuilder;
46 import javax.xml.parsers.DocumentBuilderFactory;
47 import javax.xml.parsers.ParserConfigurationException;
48 import javax.xml.transform.OutputKeys;
49 import javax.xml.transform.Source;
50 import javax.xml.transform.Transformer;
51 import javax.xml.transform.TransformerFactory;
52 import javax.xml.transform.dom.DOMSource;
53 import javax.xml.transform.stream.StreamResult;
54 import javax.xml.transform.stream.StreamSource;
55 import javax.xml.validation.Schema;
56 import javax.xml.validation.SchemaFactory;
57 import javax.xml.validation.Validator;
58
59 import org.apache.commons.lang.StringUtils;
60 import org.apache.commons.logging.Log;
61 import org.apache.commons.logging.LogFactory;
62 import org.fosstrak.epcis.repository.EpcisConstants;
63 import org.fosstrak.epcis.repository.InternalBusinessException;
64 import org.fosstrak.epcis.repository.InvalidFormatException;
65 import org.fosstrak.epcis.repository.model.Action;
66 import org.fosstrak.epcis.repository.model.AggregationEvent;
67 import org.fosstrak.epcis.repository.model.BaseEvent;
68 import org.fosstrak.epcis.repository.model.BusinessLocationAttrId;
69 import org.fosstrak.epcis.repository.model.BusinessLocationId;
70 import org.fosstrak.epcis.repository.model.BusinessStepAttrId;
71 import org.fosstrak.epcis.repository.model.BusinessStepId;
72 import org.fosstrak.epcis.repository.model.BusinessTransaction;
73 import org.fosstrak.epcis.repository.model.BusinessTransactionAttrId;
74 import org.fosstrak.epcis.repository.model.BusinessTransactionId;
75 import org.fosstrak.epcis.repository.model.BusinessTransactionTypeAttrId;
76 import org.fosstrak.epcis.repository.model.BusinessTransactionTypeId;
77 import org.fosstrak.epcis.repository.model.DispositionAttrId;
78 import org.fosstrak.epcis.repository.model.DispositionId;
79 import org.fosstrak.epcis.repository.model.EPCClass;
80 import org.fosstrak.epcis.repository.model.EPCClassAttrId;
81 import org.fosstrak.epcis.repository.model.EventFieldExtension;
82 import org.fosstrak.epcis.repository.model.ObjectEvent;
83 import org.fosstrak.epcis.repository.model.QuantityEvent;
84 import org.fosstrak.epcis.repository.model.ReadPointAttrId;
85 import org.fosstrak.epcis.repository.model.ReadPointId;
86 import org.fosstrak.epcis.repository.model.TransactionEvent;
87 import org.fosstrak.epcis.repository.model.VocabularyAttrCiD;
88 import org.fosstrak.epcis.repository.model.VocabularyAttributeElement;
89 import org.fosstrak.epcis.repository.model.VocabularyElement;
90 import org.fosstrak.epcis.utils.TimeParser;
91 import org.hibernate.Criteria;
92 import org.hibernate.ObjectNotFoundException;
93 import org.hibernate.Session;
94 import org.hibernate.SessionFactory;
95 import org.hibernate.Transaction;
96 import org.hibernate.criterion.Restrictions;
97 import org.w3c.dom.DOMException;
98 import org.w3c.dom.Document;
99 import org.w3c.dom.Element;
100 import org.w3c.dom.Node;
101 import org.w3c.dom.NodeList;
102 import org.xml.sax.ErrorHandler;
103 import org.xml.sax.SAXException;
104 import org.xml.sax.SAXParseException;
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120 public class CaptureOperationsModule {
121
122 private static final Log LOG = LogFactory.getLog(CaptureOperationsModule.class);
123
124 private static final Map<String, Class<?>> vocClassMap = new HashMap<String, Class<?>>();
125
126 static {
127 vocClassMap.put(EpcisConstants.BUSINESS_LOCATION_ID, BusinessLocationId.class);
128 vocClassMap.put(EpcisConstants.BUSINESS_STEP_ID, BusinessStepId.class);
129 vocClassMap.put(EpcisConstants.BUSINESS_TRANSACTION_ID, BusinessTransactionId.class);
130 vocClassMap.put(EpcisConstants.BUSINESS_TRANSACTION_TYPE_ID, BusinessTransactionTypeId.class);
131 vocClassMap.put(EpcisConstants.DISPOSITION_ID, DispositionId.class);
132 vocClassMap.put(EpcisConstants.EPC_CLASS_ID, EPCClass.class);
133 vocClassMap.put(EpcisConstants.READ_POINT_ID, ReadPointId.class);
134 }
135
136
137 private static final Map<String, Class<?>> vocAttributeClassMap = new HashMap<String, Class<?>>();
138 private static final Map<String, String> vocAttributeTablesMap = new HashMap<String, String>();
139 private static Map<String, String> vocabularyTablenameMap = new HashMap<String, String>();
140
141 static {
142 vocAttributeClassMap.put(EpcisConstants.BUSINESS_LOCATION_ID, BusinessLocationAttrId.class);
143 vocAttributeClassMap.put(EpcisConstants.BUSINESS_STEP_ID, BusinessStepAttrId.class);
144 vocAttributeClassMap.put(EpcisConstants.BUSINESS_TRANSACTION_ID, BusinessTransactionAttrId.class);
145 vocAttributeClassMap.put(EpcisConstants.BUSINESS_TRANSACTION_TYPE_ID, BusinessTransactionTypeAttrId.class);
146 vocAttributeClassMap.put(EpcisConstants.DISPOSITION_ID, DispositionAttrId.class);
147 vocAttributeClassMap.put(EpcisConstants.EPC_CLASS_ID, EPCClassAttrId.class);
148 vocAttributeClassMap.put(EpcisConstants.READ_POINT_ID, ReadPointAttrId.class);
149
150 vocAttributeTablesMap.put(EpcisConstants.BUSINESS_LOCATION_ID, "voc_BizLoc_attr");
151 vocAttributeTablesMap.put(EpcisConstants.BUSINESS_STEP_ID, "voc_BizStep_attr");
152 vocAttributeTablesMap.put(EpcisConstants.BUSINESS_TRANSACTION_ID, "voc_BizTrans_attr");
153 vocAttributeTablesMap.put(EpcisConstants.BUSINESS_TRANSACTION_TYPE_ID, "voc_BizTransType_attr");
154 vocAttributeTablesMap.put(EpcisConstants.DISPOSITION_ID, "voc_Disposition_attr");
155 vocAttributeTablesMap.put(EpcisConstants.EPC_CLASS_ID, "voc_EPCClass_attr");
156 vocAttributeTablesMap.put(EpcisConstants.READ_POINT_ID, "voc_ReadPoint_attr");
157
158 vocabularyTablenameMap.put(EpcisConstants.BUSINESS_STEP_ID, "voc_BizStep");
159 vocabularyTablenameMap.put(EpcisConstants.BUSINESS_LOCATION_ID, "voc_BizLoc");
160 vocabularyTablenameMap.put(EpcisConstants.BUSINESS_TRANSACTION_ID, "voc_BizTrans");
161 vocabularyTablenameMap.put(EpcisConstants.BUSINESS_TRANSACTION_TYPE_ID, "voc_BizTransType");
162 vocabularyTablenameMap.put(EpcisConstants.DISPOSITION_ID, "voc_Disposition");
163 vocabularyTablenameMap.put(EpcisConstants.EPC_CLASS_ID, "voc_EPCClass");
164 vocabularyTablenameMap.put(EpcisConstants.READ_POINT_ID, "voc_ReadPoint");
165
166 }
167
168
169
170
171 private Schema schema;
172
173
174
175
176 private Schema masterDataSchema;
177
178
179
180
181 private boolean insertMissingVoc = true;
182
183
184
185
186 private boolean dbResetAllowed = false;
187
188
189
190
191 private List<File> dbResetScripts = null;
192
193
194
195
196 private SessionFactory sessionFactory;
197
198
199
200
201
202
203
204
205 private Schema initEpcisSchema(String xsdFile) {
206 InputStream is = this.getClass().getResourceAsStream(xsdFile);
207 if (is != null) {
208 try {
209 SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
210 Source schemaSrc = new StreamSource(is);
211 schemaSrc.setSystemId(CaptureOperationsServlet.class.getResource(xsdFile).toString());
212 Schema schema = schemaFactory.newSchema(schemaSrc);
213 LOG.debug("EPCIS schema file initialized and loaded successfully");
214 return schema;
215 } catch (Exception e) {
216 LOG.warn("Unable to load or parse the EPCIS schema", e);
217 }
218 } else {
219 LOG.error("Unable to load the EPCIS schema file from classpath: cannot find resource " + xsdFile);
220 }
221 LOG.warn("Schema validation will not be available!");
222 return null;
223 }
224
225
226
227
228
229
230
231
232
233
234
235 public void doDbReset() throws SQLException, IOException, UnsupportedOperationException {
236 if (dbResetAllowed) {
237 if (dbResetScripts == null || dbResetScripts.isEmpty()) {
238 LOG.warn("dbReset operation invoked but no dbReset script is configured!");
239 } else {
240 Session session = null;
241 try {
242 session = sessionFactory.openSession();
243 Transaction tx = null;
244 for (File file : dbResetScripts) {
245 try {
246 tx = session.beginTransaction();
247 LOG.info("Running db reset script from file " + file);
248 BufferedReader reader = new BufferedReader(new FileReader(file));
249 String line;
250 String sql = "";
251 while ((line = reader.readLine()) != null) {
252 if (!line.startsWith("--") && StringUtils.isNotBlank(line)) {
253 sql += line;
254 if (sql.endsWith(";")) {
255 LOG.debug("SQL: " + sql);
256 session.createSQLQuery(sql).executeUpdate();
257 sql = "";
258 }
259 }
260 }
261 tx.commit();
262 } catch (Throwable e) {
263 LOG.error("dbReset failed for " + file + ": " + e.toString(), e);
264 if (tx != null) {
265 tx.rollback();
266 }
267 throw new SQLException(e.toString());
268 }
269 }
270 } finally {
271 if (session != null) {
272 session.close();
273 }
274 }
275 }
276 } else {
277 throw new UnsupportedOperationException();
278 }
279 }
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294 public void doCapture(InputStream in, Principal principal) throws SAXException, InternalBusinessException,
295 InvalidFormatException {
296 Document document = null;
297 try {
298
299 document = parseInput(in, null);
300
301
302 if (isEPCISDocument(document)) {
303 validateDocument(document, getSchema());
304 } else if (isEPCISMasterDataDocument(document)) {
305 validateDocument(document, getMasterDataSchema());
306 }
307 } catch (IOException e) {
308 throw new InternalBusinessException("unable to read from input: " + e.getMessage(), e);
309 }
310
311
312 Session session = null;
313 try {
314 session = sessionFactory.openSession();
315 Transaction tx = null;
316 try {
317 tx = session.beginTransaction();
318 LOG.debug("DB connection opened.");
319 if (isEPCISDocument(document)) {
320 processEvents(session, document);
321 } else if (isEPCISMasterDataDocument(document)) {
322 processMasterData(session, document);
323 }
324 tx.commit();
325
326 LOG.info("EPCIS Capture Interface request succeeded");
327 } catch (SAXException e) {
328 LOG.error("EPCIS Capture Interface request failed: " + e.toString());
329 if (tx != null) {
330 tx.rollback();
331 }
332 throw e;
333 } catch (InvalidFormatException e) {
334 LOG.error("EPCIS Capture Interface request failed: " + e.toString());
335 if (tx != null) {
336 tx.rollback();
337 }
338 throw e;
339 } catch (Exception e) {
340
341
342 LOG.error("EPCIS Capture Interface request failed: " + e.toString(), e);
343 if (tx != null) {
344 tx.rollback();
345 }
346 throw new InternalBusinessException(e.toString());
347 }
348 } finally {
349 if (session != null) {
350 session.close();
351 }
352
353 LOG.debug("DB connection closed");
354 }
355 }
356
357
358
359
360 private void validateDocument(Document document, Schema schema) throws SAXException, IOException {
361 if (schema != null) {
362 Validator validator = schema.newValidator();
363 validator.validate(new DOMSource(document));
364 LOG.info("Incoming capture request was successfully validated against the EPCISDocument schema");
365 } else {
366 LOG.warn("Schema validator unavailable. Unable to validate EPCIS capture event against schema!");
367 }
368 }
369
370
371
372
373
374 private Document parseInput(InputStream in, Schema schema) throws InternalBusinessException, SAXException,
375 IOException {
376 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
377 factory.setNamespaceAware(true);
378 factory.setIgnoringComments(true);
379 factory.setIgnoringElementContentWhitespace(true);
380 factory.setSchema(schema);
381 try {
382 DocumentBuilder builder = factory.newDocumentBuilder();
383 builder.setErrorHandler(new ErrorHandler() {
384 public void warning(SAXParseException e) throws SAXException {
385 LOG.warn("warning while parsing XML input: " + e.getMessage());
386 }
387
388 public void fatalError(SAXParseException e) throws SAXException {
389 LOG.error("non-recovarable error while parsing XML input: " + e.getMessage());
390 throw e;
391 }
392
393 public void error(SAXParseException e) throws SAXException {
394 LOG.error("error while parsing XML input: " + e.getMessage());
395 throw e;
396 }
397 });
398 Document document = builder.parse(in);
399 LOG.debug("payload successfully parsed as XML document");
400 if (LOG.isDebugEnabled()) {
401 logDocument(document);
402 }
403 return document;
404 } catch (ParserConfigurationException e) {
405 throw new InternalBusinessException("unable to configure document builder to parse XML input", e);
406 }
407 }
408
409
410
411
412
413 private void logDocument(Document document) {
414 try {
415 TransformerFactory tfFactory = TransformerFactory.newInstance();
416 Transformer transformer = tfFactory.newTransformer();
417 StringWriter writer = new StringWriter();
418 transformer.transform(new DOMSource(document), new StreamResult(writer));
419 String xml = writer.toString();
420 if (xml.length() > 100 * 1024) {
421
422 xml = null;
423 } else {
424 LOG.debug("Incoming contents:\n\n" + writer.toString() + "\n");
425 }
426 } catch (Throwable t) {
427
428 }
429 }
430
431
432
433
434
435 private boolean isEPCISDocument(Document document) {
436 return document.getDocumentElement().getLocalName().equals("EPCISDocument");
437 }
438
439
440
441
442
443 private boolean isEPCISMasterDataDocument(Document document) {
444 return document.getDocumentElement().getLocalName().equals("EPCISMasterDataDocument");
445 }
446
447
448
449
450 private void processEvents(Session session, Document document) throws DOMException, SAXException,
451 InvalidFormatException {
452 NodeList eventList = document.getElementsByTagName("EventList");
453 NodeList events = eventList.item(0).getChildNodes();
454
455
456 int eventCount = 0;
457 for (int i = 0; i < events.getLength(); i++) {
458 Node eventNode = events.item(i);
459 String nodeName = eventNode.getNodeName();
460
461 if (nodeName.equals(EpcisConstants.OBJECT_EVENT) || nodeName.equals(EpcisConstants.AGGREGATION_EVENT)
462 || nodeName.equals(EpcisConstants.QUANTITY_EVENT)
463 || nodeName.equals(EpcisConstants.TRANSACTION_EVENT)) {
464 LOG.debug("processing event " + i + ": '" + nodeName + "'.");
465 handleEvent(session, eventNode, nodeName);
466 eventCount++;
467 if (eventCount % 50 == 0) {
468 session.flush();
469 session.clear();
470 }
471 } else if (!nodeName.equals("#text") && !nodeName.equals("#comment")) {
472 throw new SAXException("Encountered unknown event '" + nodeName + "'.");
473 }
474 }
475 }
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490 private void handleEvent(Session session, final Node eventNode, final String eventType) throws DOMException,
491 SAXException, InvalidFormatException {
492 if (eventNode == null) {
493
494 return;
495 } else if (eventNode.getChildNodes().getLength() == 0) {
496 throw new SAXException("Event element '" + eventNode.getNodeName() + "' has no children elements.");
497 }
498 Node curEventNode = null;
499
500
501
502
503
504
505 Calendar eventTime = null;
506 Calendar recordTime = GregorianCalendar.getInstance();
507 String eventTimeZoneOffset = null;
508 String action = null;
509 String parentId = null;
510 Long quantity = null;
511 String bizStepUri = null;
512 String dispositionUri = null;
513 String readPointUri = null;
514 String bizLocationUri = null;
515 String epcClassUri = null;
516
517 List<String> epcs = null;
518 List<BusinessTransaction> bizTransList = null;
519 List<EventFieldExtension> fieldNameExtList = new ArrayList<EventFieldExtension>();
520
521 for (int i = 0; i < eventNode.getChildNodes().getLength(); i++) {
522 curEventNode = eventNode.getChildNodes().item(i);
523 String nodeName = curEventNode.getNodeName();
524
525 if (nodeName.equals("#text") || nodeName.equals("#comment")) {
526
527 LOG.debug(" ignoring text or comment: '" + curEventNode.getTextContent().trim() + "'");
528 continue;
529 }
530
531 LOG.debug(" handling event field: '" + nodeName + "'");
532 if (nodeName.equals("eventTime")) {
533 String xmlTime = curEventNode.getTextContent();
534 LOG.debug(" eventTime in xml is '" + xmlTime + "'");
535 try {
536 eventTime = TimeParser.parseAsCalendar(xmlTime);
537 } catch (ParseException e) {
538 throw new SAXException("Invalid date/time (must be ISO8601).", e);
539 }
540 LOG.debug(" eventTime parsed as '" + eventTime.getTime() + "'");
541 } else if (nodeName.equals("recordTime")) {
542
543 } else if (nodeName.equals("eventTimeZoneOffset")) {
544 eventTimeZoneOffset = checkEventTimeZoneOffset(curEventNode.getTextContent());
545 } else if (nodeName.equals("epcList") || nodeName.equals("childEPCs")) {
546 epcs = handleEpcs(eventType, curEventNode);
547 } else if (nodeName.equals("bizTransactionList")) {
548 bizTransList = handleBizTransactions(session, curEventNode);
549 } else if (nodeName.equals("action")) {
550 action = curEventNode.getTextContent();
551 if (!action.equals("ADD") && !action.equals("OBSERVE") && !action.equals("DELETE")) {
552 throw new SAXException("Encountered illegal 'action' value: " + action);
553 }
554 } else if (nodeName.equals("bizStep")) {
555 bizStepUri = curEventNode.getTextContent();
556 } else if (nodeName.equals("disposition")) {
557 dispositionUri = curEventNode.getTextContent();
558 } else if (nodeName.equals("readPoint")) {
559 Element attrElem = (Element) curEventNode;
560 Node id = attrElem.getElementsByTagName("id").item(0);
561 readPointUri = id.getTextContent();
562 } else if (nodeName.equals("bizLocation")) {
563 Element attrElem = (Element) curEventNode;
564 Node id = attrElem.getElementsByTagName("id").item(0);
565 bizLocationUri = id.getTextContent();
566 } else if (nodeName.equals("epcClass")) {
567 epcClassUri = curEventNode.getTextContent();
568 } else if (nodeName.equals("quantity")) {
569 quantity = Long.valueOf(curEventNode.getTextContent());
570 } else if (nodeName.equals("parentID")) {
571 checkEpcOrUri(curEventNode.getTextContent(), false);
572 parentId = curEventNode.getTextContent();
573 } else {
574 String[] parts = nodeName.split(":");
575 if (parts.length == 2) {
576 LOG.debug(" treating unknown event field as extension.");
577 String prefix = parts[0];
578 String localname = parts[1];
579
580
581
582 String namespace = curEventNode.lookupNamespaceURI(prefix);
583 String value = curEventNode.getTextContent();
584 EventFieldExtension evf = new EventFieldExtension(prefix, namespace, localname, value);
585 fieldNameExtList.add(evf);
586 } else {
587
588 throw new SAXException(" encountered unknown event field: '" + nodeName + "'.");
589 }
590 }
591 }
592 if (eventType.equals(EpcisConstants.AGGREGATION_EVENT)) {
593
594
595 if (parentId == null && ("ADD".equals(action) || "DELETE".equals(action))) {
596 throw new InvalidFormatException("'parentID' is required if 'action' is ADD or DELETE");
597 }
598 }
599
600
601
602 String nodeName = eventNode.getNodeName();
603 VocabularyElement bizStep = bizStepUri != null ? getOrEditVocabularyElement(session,
604 EpcisConstants.BUSINESS_STEP_ID, String.valueOf(bizStepUri), "1") : null;
605 VocabularyElement disposition = dispositionUri != null ? getOrEditVocabularyElement(session,
606 EpcisConstants.DISPOSITION_ID, String.valueOf(dispositionUri), "1") : null;
607 VocabularyElement readPoint = readPointUri != null ? getOrEditVocabularyElement(session,
608 EpcisConstants.READ_POINT_ID, String.valueOf(readPointUri), "1") : null;
609 VocabularyElement bizLocation = bizLocationUri != null ? getOrEditVocabularyElement(session,
610 EpcisConstants.BUSINESS_LOCATION_ID, String.valueOf(bizLocationUri), "1") : null;
611 VocabularyElement epcClass = epcClassUri != null ? getOrEditVocabularyElement(session,
612 EpcisConstants.EPC_CLASS_ID, String.valueOf(epcClassUri), "1") : null;
613
614 BaseEvent be;
615 if (nodeName.equals(EpcisConstants.AGGREGATION_EVENT)) {
616 AggregationEvent ae = new AggregationEvent();
617 ae.setParentId(parentId);
618 ae.setChildEpcs(epcs);
619 ae.setAction(Action.valueOf(action));
620 be = ae;
621 } else if (nodeName.equals(EpcisConstants.OBJECT_EVENT)) {
622 ObjectEvent oe = new ObjectEvent();
623 oe.setAction(Action.valueOf(action));
624 if (epcs != null && epcs.size() > 0) {
625 oe.setEpcList(epcs);
626 }
627 be = oe;
628 } else if (nodeName.equals(EpcisConstants.QUANTITY_EVENT)) {
629 QuantityEvent qe = new QuantityEvent();
630 qe.setEpcClass((EPCClass) epcClass);
631 if (quantity != null) {
632 qe.setQuantity(quantity.longValue());
633 }
634 be = qe;
635 } else if (nodeName.equals(EpcisConstants.TRANSACTION_EVENT)) {
636 TransactionEvent te = new TransactionEvent();
637 te.setParentId(parentId);
638 te.setEpcList(epcs);
639 te.setAction(Action.valueOf(action));
640 be = te;
641 } else {
642 throw new SAXException("Encountered unknown event element '" + nodeName + "'.");
643 }
644
645 be.setEventTime(eventTime);
646 be.setRecordTime(recordTime);
647 be.setEventTimeZoneOffset(eventTimeZoneOffset);
648 be.setBizStep((BusinessStepId) bizStep);
649 be.setDisposition((DispositionId) disposition);
650 be.setBizLocation((BusinessLocationId) bizLocation);
651 be.setReadPoint((ReadPointId) readPoint);
652 if (bizTransList != null && bizTransList.size() > 0) {
653 be.setBizTransList(bizTransList);
654 }
655 if (!fieldNameExtList.isEmpty()) {
656 be.setExtensions(fieldNameExtList);
657 }
658
659 session.save(be);
660 }
661
662
663
664
665 private void processMasterData(Session session, Document document) throws DOMException, SAXException,
666 InvalidFormatException {
667
668
669 NodeList vocabularyList = document.getElementsByTagName("VocabularyList");
670 if (vocabularyList.item(0).hasChildNodes()) {
671 NodeList vocabularys = vocabularyList.item(0).getChildNodes();
672
673
674 int vocabularyCount = 0;
675 for (int i = 0; i < vocabularys.getLength(); i++) {
676 Node vocabularyNode = vocabularys.item(i);
677 String nodeName = vocabularyNode.getNodeName();
678 if (nodeName.equals("Vocabulary")) {
679
680 String vocabularyType = vocabularyNode.getAttributes().getNamedItem("type").getNodeValue();
681
682 if (EpcisConstants.VOCABULARY_TYPES.contains(vocabularyType)) {
683
684 LOG.debug("processing " + i + ": '" + nodeName + "':" + vocabularyType + ".");
685 handleVocabulary(session, vocabularyNode, vocabularyType);
686 vocabularyCount++;
687 if (vocabularyCount % 50 == 0) {
688 session.flush();
689 session.clear();
690 }
691 }
692 } else if (!nodeName.equals("#text") && !nodeName.equals("#comment")) {
693 throw new SAXException("Encountered unknown vocabulary '" + nodeName + "'.");
694 }
695 }
696 }
697
698 }
699
700
701
702
703
704
705
706
707
708
709
710
711
712 private void handleVocabulary(Session session, final Node vocNode, final String vocType) throws DOMException,
713 SAXException, InvalidFormatException {
714 if (vocNode == null) {
715
716 return;
717 } else if (vocNode.getChildNodes().getLength() == 0) {
718 throw new SAXException("Vocabulary element '" + vocNode.getNodeName() + "' has no children elements.");
719 }
720
721 for (int i = 0; i < vocNode.getChildNodes().getLength(); i++) {
722 Node curVocNode = vocNode.getChildNodes().item(i);
723 if (isTextOrComment(curVocNode)) {
724 continue;
725 }
726 for (int j = 0; j < curVocNode.getChildNodes().getLength(); j++) {
727 Node curVocElemNode = curVocNode.getChildNodes().item(j);
728 if (isTextOrComment(curVocElemNode)) {
729 continue;
730 }
731 LOG.debug(" processing vocabulary '" + curVocElemNode.getNodeName() + "'");
732 String curVocElemId = curVocElemNode.getAttributes().getNamedItem("id").getNodeValue();
733
734
735
736
737
738 String vocElemEditMode = "";
739
740 if (!(curVocElemNode.getAttributes().getNamedItem("mode") == null)) {
741 vocElemEditMode = curVocElemNode.getAttributes().getNamedItem("mode").getNodeValue();
742 } else {
743 vocElemEditMode = "1";
744 }
745
746 VocabularyElement curVocElem = getOrEditVocabularyElement(session, vocType, curVocElemId,
747 vocElemEditMode);
748
749
750 if (curVocElem != null) {
751 for (int k = 0; k < curVocElemNode.getChildNodes().getLength(); k++) {
752 Node curVocAttrNode = curVocElemNode.getChildNodes().item(k);
753 if (isTextOrComment(curVocAttrNode)) {
754 continue;
755 }
756
757 LOG.debug(" processing vocabulary attribute '" + curVocAttrNode.getNodeName() + "'");
758 String curVocAttrId = curVocAttrNode.getAttributes().getNamedItem("id").getNodeValue();
759 String curVocAttrValue = parseVocAttributeValue(curVocAttrNode);
760
761
762
763
764
765
766 String vocabularyAttributeEditMode = "";
767 if (!(curVocAttrNode.getAttributes().getNamedItem("mode") == null)) {
768 vocabularyAttributeEditMode = curVocAttrNode.getAttributes().getNamedItem("mode").getNodeValue();
769 } else {
770 vocabularyAttributeEditMode = "add/alter";
771 }
772
773 getOrEditVocabularyAttributeElement(session, vocType, curVocElem.getId(), curVocAttrId,
774 curVocAttrValue, vocabularyAttributeEditMode);
775 }
776 }
777
778 }
779 }
780 }
781
782 private boolean isTextOrComment(Node node) {
783 return node.getNodeType() == Node.TEXT_NODE || node.getNodeType() == Node.COMMENT_NODE;
784 }
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810 private String parseVocAttributeValue(Node vocAttrNode) {
811 String vocAttrValue;
812 if (vocAttrNode.getAttributes().getNamedItem("value") != null) {
813
814 vocAttrValue = vocAttrNode.getAttributes().getNamedItem("value").getNodeValue();
815 } else if (vocAttrNode.getChildNodes().getLength() > 1) {
816
817 TransformerFactory transFactory = TransformerFactory.newInstance();
818 StringWriter buffer = new StringWriter();
819 try {
820 Transformer transformer = transFactory.newTransformer();
821 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
822 transformer.setOutputProperty(OutputKeys.INDENT, "no");
823 transformer.transform(new DOMSource(vocAttrNode.getChildNodes().item(1)), new StreamResult(buffer));
824 vocAttrValue = buffer.toString();
825 } catch (Throwable t) {
826 LOG.warn("unable to transform vocabulary attribute value (XML) into a string", t);
827 vocAttrValue = vocAttrNode.getTextContent();
828 }
829 } else if (vocAttrNode.getFirstChild() != null) {
830
831 vocAttrValue = vocAttrNode.getFirstChild().getNodeValue();
832 } else {
833 vocAttrValue = "";
834 }
835 return vocAttrValue;
836 }
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851 private List<String> handleEpcs(final String eventType, final Node epcNode) throws SAXException, DOMException,
852 InvalidFormatException {
853 List<String> epcList = new ArrayList<String>();
854
855 boolean isEpc = false;
856 boolean epcRequired = false;
857 boolean atLeastOneNonEpc = false;
858 for (int i = 0; i < epcNode.getChildNodes().getLength(); i++) {
859 Node curNode = epcNode.getChildNodes().item(i);
860 if (curNode.getNodeName().equals("epc")) {
861 isEpc = checkEpcOrUri(curNode.getTextContent(), epcRequired);
862 if (isEpc) {
863
864
865 epcRequired = true;
866 } else {
867 atLeastOneNonEpc = true;
868 }
869 epcList.add(curNode.getTextContent());
870 } else {
871 if (curNode.getNodeName() != "#text" && curNode.getNodeName() != "#comment") {
872 throw new SAXException("Unknown XML tag: " + curNode.getNodeName(), null);
873 }
874 }
875 }
876 if (atLeastOneNonEpc && isEpc) {
877 throw new InvalidFormatException(
878 "One of the provided EPCs was a 'pure identity' EPC, so all of them must be 'pure identity' EPCs");
879 }
880 return epcList;
881 }
882
883
884
885
886
887
888
889
890
891
892
893
894
895 protected boolean checkEpcOrUri(String epcOrUri, boolean epcRequired) throws InvalidFormatException {
896 boolean isEpc = false;
897 if (epcOrUri.startsWith("urn:epc:id:")) {
898
899 checkEpc(epcOrUri);
900 isEpc = true;
901 } else {
902
903
904 if (epcRequired) {
905 throw new InvalidFormatException(
906 "One of the provided EPCs was a 'pure identity' EPC, so all of them must be 'pure identity' EPCs");
907 }
908 checkUri(epcOrUri);
909 }
910 return isEpc;
911 }
912
913
914
915
916
917
918
919
920
921
922
923
924 private List<BusinessTransaction> handleBizTransactions(Session session, Node bizNode) throws SAXException {
925 List<BusinessTransaction> bizTransactionList = new ArrayList<BusinessTransaction>();
926
927 for (int i = 0; i < bizNode.getChildNodes().getLength(); i++) {
928 Node curNode = bizNode.getChildNodes().item(i);
929 if (curNode.getNodeName().equals("bizTransaction")) {
930
931
932
933 String bizTransTypeUri = curNode.getAttributes().item(0).getTextContent();
934 String bizTransUri = curNode.getTextContent();
935 BusinessTransactionId bizTrans = (BusinessTransactionId) getOrEditVocabularyElement(session,
936 EpcisConstants.BUSINESS_TRANSACTION_ID, bizTransUri.toString(), "1");
937 BusinessTransactionTypeId type = (BusinessTransactionTypeId) getOrEditVocabularyElement(session,
938 EpcisConstants.BUSINESS_TRANSACTION_TYPE_ID, bizTransTypeUri.toString(), "1");
939
940 Criteria c0 = session.createCriteria(BusinessTransaction.class);
941 c0.add(Restrictions.eq("bizTransaction", bizTrans));
942 c0.add(Restrictions.eq("type", type));
943 BusinessTransaction bizTransaction = (BusinessTransaction) c0.uniqueResult();
944
945 if (bizTransaction == null) {
946 bizTransaction = new BusinessTransaction();
947 bizTransaction.setBizTransaction(bizTrans);
948 bizTransaction.setType(type);
949 session.save(bizTransaction);
950 }
951
952 bizTransactionList.add(bizTransaction);
953
954 } else {
955 if (!curNode.getNodeName().equals("#text") && !curNode.getNodeName().equals("#comment")) {
956 throw new SAXException("Unknown XML tag: " + curNode.getNodeName(), null);
957 }
958 }
959 }
960 return bizTransactionList;
961 }
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979 public VocabularyElement getOrInsertVocabularyElement(Session session, String vocabularyType,
980 String vocabularyElement) throws SAXException {
981 Class<?> c = vocClassMap.get(vocabularyType);
982 Criteria c0 = session.createCriteria(c);
983 c0.setCacheable(true);
984 c0.add(Restrictions.eq("uri", vocabularyElement));
985 VocabularyElement ve;
986 try {
987 ve = (VocabularyElement) c0.uniqueResult();
988 } catch (ObjectNotFoundException e) {
989 ve = null;
990 }
991 if (ve == null) {
992
993
994
995 if (!insertMissingVoc) {
996 throw new UnsupportedOperationException("Not allowed to add new vocabulary - use existing vocabulary");
997 } else {
998
999
1000 try {
1001 ve = (VocabularyElement) c.newInstance();
1002 } catch (InstantiationException e) {
1003 throw new RuntimeException(e);
1004 } catch (IllegalAccessException e) {
1005 throw new RuntimeException(e);
1006 }
1007
1008 ve.setUri(vocabularyElement);
1009 session.save(ve);
1010 session.flush();
1011 }
1012 }
1013 return ve;
1014 }
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033 public VocabularyElement getOrEditVocabularyElement(Session session, String vocabularyType,
1034 String vocabularyElementURI, String mode) throws SAXException {
1035 boolean alterURI = false;
1036 boolean singleDelete = false;
1037 boolean wdDelete = false;
1038 Long vocabularyElementID = null;
1039
1040 if (mode.equals("2")) {
1041 alterURI = true;
1042 } else if (mode.equals("3")) {
1043 singleDelete = true;
1044 } else if (mode.equals("4")) {
1045 wdDelete = true;
1046 }
1047
1048 Class<?> c = vocClassMap.get(vocabularyType);
1049 Criteria c0 = session.createCriteria(c);
1050 c0.setCacheable(true);
1051 c0.add(Restrictions.eq("uri", alterURI ? vocabularyElementURI.split("#")[0] : vocabularyElementURI));
1052 VocabularyElement ve;
1053 try {
1054 ve = (VocabularyElement) c0.uniqueResult();
1055 } catch (ObjectNotFoundException e) {
1056 ve = null;
1057 }
1058 if (ve != null) {
1059 vocabularyElementID = ve.getId();
1060 }
1061
1062 if (ve == null || ((singleDelete || alterURI || wdDelete) && ve != null)) {
1063
1064
1065
1066 if (!insertMissingVoc) {
1067 throw new UnsupportedOperationException("Not allowed to add new vocabulary - use existing vocabulary");
1068 } else {
1069
1070
1071
1072 if (alterURI) {
1073 ve.setUri(vocabularyElementURI.split("#")[1]);
1074 session.update(ve);
1075 session.flush();
1076 return ve;
1077
1078 } else if (singleDelete) {
1079 Object vocabularyElementObject = session.get(c, vocabularyElementID);
1080 if (vocabularyElementObject != null) session.delete(vocabularyElementObject);
1081 deleteVocabularyElementAttributes(session, vocabularyType, vocabularyElementID);
1082 session.flush();
1083 return null;
1084 } else if (wdDelete) {
1085 Object vocabularyElementObject = session.get(c, vocabularyElementID);
1086 if (vocabularyElementObject != null) session.delete(vocabularyElementObject);
1087 deleteVocabularyElementAttributes(session, vocabularyType, vocabularyElementID);
1088 deleteVocabularyElementDescendants(session, vocabularyType, vocabularyElementURI);
1089 session.flush();
1090 return null;
1091
1092 } else {
1093
1094 try {
1095 ve = (VocabularyElement) c.newInstance();
1096 } catch (InstantiationException e) {
1097 throw new RuntimeException(e);
1098 } catch (IllegalAccessException e) {
1099 throw new RuntimeException(e);
1100 }
1101
1102 ve.setUri(vocabularyElementURI);
1103 session.save(ve);
1104 }
1105
1106 session.flush();
1107 }
1108 }
1109 return ve;
1110 }
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120 private void deleteVocabularyElementDescendants(Session session, String vocabularyType, String vocabularyElementURI) {
1121 Class<?> c = vocClassMap.get(vocabularyType);
1122 List<?> vocElementChildrens = session.createSQLQuery(
1123 "SELECT * FROM " + vocabularyTablenameMap.get(vocabularyType) + " WHERE uri LIKE '"
1124 + vocabularyElementURI + ",%'").addEntity(c).list();
1125 for (int i = 0; i < vocElementChildrens.size(); i++) {
1126 session.delete((VocabularyElement) vocElementChildrens.get(i));
1127 deleteVocabularyElementAttributes(session, vocabularyType,
1128 ((VocabularyElement) vocElementChildrens.get(i)).getId());
1129 }
1130 session.flush();
1131 }
1132
1133
1134
1135
1136
1137
1138
1139
1140 private void deleteVocabularyElementAttributes(Session session, String vocabularyType, Long vocabularyElementID) {
1141 Class<?> c = vocAttributeClassMap.get(vocabularyType);
1142 List<?> vocAttributeElements = session.createSQLQuery(
1143 "select * FROM " + vocAttributeTablesMap.get(vocabularyType) + " where id = '" + vocabularyElementID
1144 + "'").addEntity(c).list();
1145 for (int i = 0; i < vocAttributeElements.size(); i++) {
1146 session.delete((VocabularyAttributeElement) vocAttributeElements.get(i));
1147 }
1148 session.flush();
1149
1150 }
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168 public VocabularyAttributeElement getOrEditVocabularyAttributeElement(Session session, String vocabularyType,
1169 Long vocabularyElementID, String vocabularyAttributeElement, String vocabularyAttributeElementValue,
1170 String mode) throws SAXException {
1171
1172 boolean deleteAttribute = false;
1173
1174 if (mode.equals("3")) {
1175 deleteAttribute = true;
1176 }
1177 Class<?> c = vocAttributeClassMap.get(vocabularyType);
1178 Criteria c0 = session.createCriteria(c);
1179 c0.setCacheable(true);
1180
1181 VocabularyAttrCiD vocabularyAttrCiD = new VocabularyAttrCiD();
1182 vocabularyAttrCiD.setAttribute(vocabularyAttributeElement);
1183 vocabularyAttrCiD.setId(vocabularyElementID);
1184
1185 c0.add(Restrictions.idEq(vocabularyAttrCiD));
1186
1187 VocabularyAttributeElement vocAttributeElement = null;
1188
1189 try {
1190 vocAttributeElement = (VocabularyAttributeElement) c0.uniqueResult();
1191 } catch (ObjectNotFoundException e) {
1192 vocAttributeElement = null;
1193 e.printStackTrace();
1194 }
1195
1196 if (vocAttributeElement == null || (deleteAttribute && (vocAttributeElement != null))
1197 || vocAttributeElement != null) {
1198
1199
1200
1201 if (!insertMissingVoc) {
1202 throw new UnsupportedOperationException("Not allowed to add new vocabulary - use existing vocabulary");
1203 } else {
1204
1205
1206 try {
1207 vocAttributeElement = (VocabularyAttributeElement) c.newInstance();
1208 } catch (InstantiationException e) {
1209 throw new RuntimeException(e);
1210 } catch (IllegalAccessException e) {
1211 throw new RuntimeException(e);
1212 }
1213 vocAttributeElement.setVocabularyAttrCiD(vocabularyAttrCiD);
1214 vocAttributeElement.setValue(vocabularyAttributeElementValue);
1215
1216 if (vocAttributeElement == null) {
1217 session.save(vocAttributeElement);
1218 }
1219
1220 else if (deleteAttribute) {
1221 Object vocabularyAttr = session.get(c, vocabularyAttrCiD);
1222 if (vocabularyAttr != null) session.delete(vocabularyAttr);
1223 session.flush();
1224 return null;
1225 } else {
1226 session.merge(vocAttributeElement);
1227 }
1228
1229 session.flush();
1230 }
1231 }
1232
1233 return vocAttributeElement;
1234 }
1235
1236
1237
1238
1239
1240
1241
1242
1243 protected String checkEventTimeZoneOffset(String textContent) throws InvalidFormatException {
1244
1245 Pattern p = Pattern.compile("[+-]\\d\\d:\\d\\d");
1246 Matcher m = p.matcher(textContent);
1247 boolean matches = m.matches();
1248 if (matches) {
1249
1250 int h = Integer.parseInt(textContent.substring(1, 3));
1251 int min = Integer.parseInt(textContent.substring(4, 6));
1252 if ((h < 0) || (h > 14)) {
1253 matches = false;
1254 } else if (h == 14 && min != 0) {
1255 matches = false;
1256 } else if ((min < 0) || (min > 59)) {
1257 matches = false;
1258 }
1259 }
1260 if (matches) {
1261 return textContent;
1262 } else {
1263 throw new InvalidFormatException("'eventTimeZoneOffset' has invalid format: " + textContent);
1264 }
1265 }
1266
1267
1268
1269
1270
1271
1272
1273
1274 private boolean checkUri(String textContent) throws InvalidFormatException {
1275 try {
1276 new URI(textContent);
1277 } catch (URISyntaxException e) {
1278 throw new InvalidFormatException(e.getMessage(), e);
1279 }
1280 return true;
1281 }
1282
1283
1284
1285
1286
1287
1288
1289
1290 protected void checkEpc(String textContent) throws InvalidFormatException {
1291 String uri = textContent;
1292 if (!uri.startsWith("urn:epc:id:")) {
1293 throw new InvalidFormatException("Invalid 'pure identity' EPC format: must start with \"urn:epc:id:\"");
1294 }
1295 uri = uri.substring("urn:epc:id:".length());
1296
1297
1298 String epcType = uri.substring(0, uri.indexOf(":"));
1299 uri = uri.substring(epcType.length() + 1);
1300 LOG.debug("Checking pattern for EPC type " + epcType + ": " + uri);
1301 Pattern p;
1302 if ("gid".equals(epcType)) {
1303 p = Pattern.compile("((0|[1-9][0-9]*)\\.){2}(0|[1-9][0-9]*)");
1304 } else if ("sgtin".equals(epcType) || "sgln".equals(epcType) || "grai".equals(epcType)) {
1305 p = Pattern.compile("([0-9]+\\.){2}([0-9]|[A-Z]|[a-z]|[\\!\\(\\)\\*\\+\\-',:;=_]|(%(([0-9]|[A-F])|[a-f]){2}))+");
1306 } else if ("sscc".equals(epcType)) {
1307 p = Pattern.compile("[0-9]+\\.[0-9]+");
1308 } else if ("giai".equals(epcType)) {
1309 p = Pattern.compile("[0-9]+\\.([0-9]|[A-Z]|[a-z]|[\\!\\(\\)\\*\\+\\-',:;=_]|(%(([0-9]|[A-F])|[a-f]){2}))+");
1310 } else {
1311 throw new InvalidFormatException("Invalid 'pure identity' EPC format: unknown EPC type: " + epcType);
1312 }
1313 Matcher m = p.matcher(uri);
1314 if (!m.matches()) {
1315 throw new InvalidFormatException("Invalid 'pure identity' EPC format: pattern \"" + uri
1316 + "\" is invalid for EPC type \"" + epcType + "\" - check with Tag Data Standard");
1317 }
1318
1319
1320 boolean exceeded = false;
1321 int count1 = uri.indexOf(".");
1322 if ("sgtin".equals(epcType)) {
1323 int count2 = uri.indexOf(".", count1 + 1) - (count1 + 1);
1324 if (count1 + count2 > 13) {
1325 exceeded = true;
1326 }
1327 } else if ("sgln".equals(epcType)) {
1328 int count2 = uri.indexOf(".", count1 + 1) - (count1 + 1);
1329 if (count1 + count2 > 12) {
1330 exceeded = true;
1331 }
1332 } else if ("grai".equals(epcType)) {
1333 int count2 = uri.indexOf(".", count1 + 1) - (count1 + 1);
1334 if (count1 + count2 > 12) {
1335 exceeded = true;
1336 }
1337 } else if ("sscc".equals(epcType)) {
1338 int count2 = uri.length() - (count1 + 1);
1339 if (count1 + count2 > 17) {
1340 exceeded = true;
1341 }
1342 } else if ("giai".equals(epcType)) {
1343 int count2 = uri.length() - (count1 + 1);
1344 if (count1 + count2 > 30) {
1345 exceeded = true;
1346 }
1347 } else {
1348
1349 }
1350 if (exceeded) {
1351 throw new InvalidFormatException(
1352 "Invalid 'pure identity' EPC format: check allowed number of characters for EPC type '" + epcType
1353 + "'");
1354 }
1355 }
1356
1357 public SessionFactory getSessionFactory() {
1358 return sessionFactory;
1359 }
1360
1361 public void setSessionFactory(SessionFactory sessionFactory) {
1362 this.sessionFactory = sessionFactory;
1363 }
1364
1365 public boolean isDbResetAllowed() {
1366 return dbResetAllowed;
1367 }
1368
1369 public void setDbResetAllowed(boolean dbResetAllowed) {
1370 this.dbResetAllowed = dbResetAllowed;
1371 }
1372
1373 public void setDbResetScript(String dbResetScript) {
1374 if (dbResetScript != null) {
1375 String[] scripts = dbResetScript.split(",");
1376 List<File> scriptList = new ArrayList<File>(scripts.length);
1377 for (String script : scripts) {
1378 if (!StringUtils.isBlank(script)) {
1379 script = "/" + script.trim();
1380 URL url = ClassLoader.getSystemResource(script);
1381 if (url == null) {
1382 url = this.getClass().getResource(script);
1383 }
1384 if (url == null) {
1385 LOG.warn("unable to find sql script " + script + " in classpath");
1386 } else {
1387 LOG.debug("found dbReset sql script at " + url);
1388 scriptList.add(new File(url.getFile()));
1389 }
1390 }
1391 }
1392 this.dbResetScripts = scriptList;
1393 }
1394 }
1395
1396 public boolean isInsertMissingVoc() {
1397 return insertMissingVoc;
1398 }
1399
1400 public void setInsertMissingVoc(boolean insertMissingVoc) {
1401 this.insertMissingVoc = insertMissingVoc;
1402 }
1403
1404 public Schema getSchema() {
1405 return schema;
1406 }
1407
1408 public void setSchema(Schema schema) {
1409 this.schema = schema;
1410 }
1411
1412 public void setEpcisSchemaFile(String epcisSchemaFile) {
1413 Schema schema = initEpcisSchema(epcisSchemaFile);
1414 setSchema(schema);
1415 }
1416
1417 public void setEpcisMasterdataSchemaFile(String epcisMasterdataSchemaFile) {
1418 Schema schema = initEpcisSchema(epcisMasterdataSchemaFile);
1419 setMasterDataSchema(schema);
1420 }
1421
1422 public Schema getMasterDataSchema() {
1423 return masterDataSchema;
1424 }
1425
1426 public void setMasterDataSchema(Schema masterDataSchema) {
1427 this.masterDataSchema = masterDataSchema;
1428 }
1429 }