View Javadoc

1   /*
2    * Copyright (C) 2007 ETH Zurich
3    *
4    * This file is part of Fosstrak (www.fosstrak.org).
5    *
6    * Fosstrak is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public
8    * License version 2.1, as published by the Free Software Foundation.
9    *
10   * Fosstrak is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13   * Lesser General Public License for more details.
14   *
15   * You should have received a copy of the GNU Lesser General Public
16   * License along with Fosstrak; if not, write to the Free
17   * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18   * Boston, MA  02110-1301  USA
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  * CaptureOperationsModule implements the core capture operations. Converts XML
108  * events delivered by HTTP POST into SQL and inserts them into the database.
109  * <p>
110  * TODO: the parsing of the xml inputstream should be done in the
111  * CaptureOperationsServlet; this class should implement EpcisCaptureInterface
112  * such that CaptureOperationsServlet can call its capture method and provide it
113  * with the parsed events.
114  * 
115  * @author David Gubler
116  * @author Alain Remund
117  * @author Marco Steybe
118  * @author Nikos Kefalakis (nkef)
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     // (nkef) Added to support the Master Data Capture I/F
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      * The XSD schema which validates the incoming messages.
170      */
171     private Schema schema;
172 
173     /**
174      * The XSD schema which validates the MasterData incoming messages.(nkef)
175      */
176     private Schema masterDataSchema;
177 
178     /**
179      * Whether we should insert new vocabulary or throw an error message.
180      */
181     private boolean insertMissingVoc = true;
182 
183     /**
184      * Whether the dbReset operation is allowed or not.
185      */
186     private boolean dbResetAllowed = false;
187 
188     /**
189      * The SQL files to be executed when the dbReset operation is invoked.
190      */
191     private List<File> dbResetScripts = null;
192 
193     /**
194      * Interface to the database.
195      */
196     private SessionFactory sessionFactory;
197 
198     /**
199      * Initializes the EPCIS schema used for validating incoming capture
200      * requests. Loads the WSDL and XSD files from the classpath (the schema is
201      * bundled with epcis-commons.jar).
202      * 
203      * @return An instantiated schema validation object.
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      * Resets the database.
227      * 
228      * @throws SQLException
229      *             If something goes wrong resetting the database.
230      * @throws IOException
231      *             If something goes wrong reading the reset script.
232      * @throws UnsupportedOperationsException
233      *             If database resets are not allowed.
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      * Implements the EPCIS capture operation. Takes an input stream, extracts
283      * the payload into an XML document, validates the document against the
284      * EPCIS schema, and captures the EPCIS events given in the document.
285      * 
286      * @throws IOException
287      *             If an error occurred while validating the request or writing
288      *             the response.
289      * @throws ParserConfigurationException
290      * @throws SAXException
291      *             If the XML document is malformed or invalid
292      * @throws InvalidFormatException
293      */
294     public void doCapture(InputStream in, Principal principal) throws SAXException, InternalBusinessException,
295             InvalidFormatException {
296         Document document = null;
297         try {
298             // parse the input into a DOM
299             document = parseInput(in, null);
300 
301             // validate incoming document against its schema
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         // start the capture operation
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                 // return OK
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                 // Hibernate throws RuntimeExceptions, so don't let them
341                 // (or anything else) escape without clean up
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             // sessionFactory.getStatistics().logSummary();
353             LOG.debug("DB connection closed");
354         }
355     }
356 
357     /**
358      * Validates the given document against the given schema.
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      * Parses the input into a DOM. If a schema is given, the input is also
372      * validated against this schema. The schema may be null.
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      * prints the given Document to the log file if it does not exceed a
411      * specified size.
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                 // too large, do not log
422                 xml = null;
423             } else {
424                 LOG.debug("Incoming contents:\n\n" + writer.toString() + "\n");
425             }
426         } catch (Throwable t) {
427             // never mind ... do not log
428         }
429     }
430 
431     /**
432      * @return <code>true</code> if the given Document is an
433      *         <i>EPCISDocument</i>.
434      */
435     private boolean isEPCISDocument(Document document) {
436         return document.getDocumentElement().getLocalName().equals("EPCISDocument");
437     }
438 
439     /**
440      * @return <code>true</code> if the given Document is an
441      *         <i>EPCISMasterDataDocument</i>.
442      */
443     private boolean isEPCISMasterDataDocument(Document document) {
444         return document.getDocumentElement().getLocalName().equals("EPCISMasterDataDocument");
445     }
446 
447     /**
448      * Processes the given document and stores the events to db.
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         // walk through all supplied events
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      * Takes an XML document node, parses it as EPCIS event and inserts the data
479      * into the database. The parse routine is generic for all event types; the
480      * query generation part has some if/elses to take care of different event
481      * parameters.
482      * 
483      * @param eventNode
484      *            The current event node.
485      * @param eventType
486      *            The current event type.
487      * @throws Exception
488      * @throws DOMException
489      */
490     private void handleEvent(Session session, final Node eventNode, final String eventType) throws DOMException,
491             SAXException, InvalidFormatException {
492         if (eventNode == null) {
493             // nothing to do
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         // A lot of the initialized variables have type URI. This type isn't to
501         // compare with the URI-Type of the standard. In fact, most of the
502         // variables having type URI are declared as Vocabularies in the
503         // Standard. Commonly, we use String for the Standard-Type URI.
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                 // ignore text or comments
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                 // ignore recordTime
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                     // String namespace =
580                     // document.getDocumentElement().getAttribute("xmlns:" +
581                     // prefix);
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                     // this is not a valid extension
588                     throw new SAXException("    encountered unknown event field: '" + nodeName + "'.");
589                 }
590             }
591         }
592         if (eventType.equals(EpcisConstants.AGGREGATION_EVENT)) {
593             // for AggregationEvents, the parentID is only optional for
594             // action=OBSERVE
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         // Changed by nkef (use "getOrEditVocabularyElement" instead of
601         // "getOrInsertVocabularyElement")
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      * Processes the given document and stores the masterdata to db.
664      */
665     private void processMasterData(Session session, Document document) throws DOMException, SAXException,
666             InvalidFormatException {
667 
668         // Handle Vocabulary List
669         NodeList vocabularyList = document.getElementsByTagName("VocabularyList");
670         if (vocabularyList.item(0).hasChildNodes()) {
671             NodeList vocabularys = vocabularyList.item(0).getChildNodes();
672 
673             // walk through all supplied vocabularies
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      * (nkef) Takes an XML document node, parses it as EPCIS Master Data and
702      * inserts the data into the database. The parse routine is generic for all
703      * Vocabulary types;
704      * 
705      * @param vocNode
706      *            The current vocabulary node.
707      * @param vocType
708      *            The current vocabulary type.
709      * @throws Exception
710      * @throws DOMException
711      */
712     private void handleVocabulary(Session session, final Node vocNode, final String vocType) throws DOMException,
713             SAXException, InvalidFormatException {
714         if (vocNode == null) {
715             // nothing to do
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                  * vocabularyElementEditMode 1: insert((it can be anything
735                  * except 2,3,4)) 2: alterURI 3: singleDelete 4: Delete element
736                  * with it's direct or indirect descendants
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                          * vocabularyAttributeEditMode 1: Insert (it can be
763                          * anything except 3)) 2: Alter Attribute Value (it can
764                          * be anything except 3) 3: Delete Attribute (required)
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      * Parses the attribute <b>value</b> of a VocabularyElement. The value can
788      * be null in which case an empty String is returned. Otherwise, the value
789      * is either given as XML attribute named 'value' or as inline text, see the
790      * sample below. <br>
791      * 
792      * <pre>
793      * {@code
794      * <VocabularyElement id="urn:epc:id:sgln:0037000.00729.0">
795      *   <attribute id="urn:epcglobal:fmcg:mda:slt:retail"/>
796      *   <attribute id="urn:epcglobal:fmcg:mda:location">Warehouse 1</attribute>
797      *   <attribute id="urn:epcglobal:fmcg:mda:room" value="22b"/>
798      *   <attribute id="urn:epcglobal:fmcg:mda:address">
799      *     <sample:Address xmlns:sample="http://sample.com/ComplexTypeExample">
800      *       <Street>100 Nowhere Street</Street>
801      *       <City>Fancy</City>
802      *     </sample:Address>
803      *   </attribute>
804      * </VocabularyElement>
805      * }
806      * </pre>
807      * 
808      * @return the attribute value as String.
809      */
810     private String parseVocAttributeValue(Node vocAttrNode) {
811         String vocAttrValue;
812         if (vocAttrNode.getAttributes().getNamedItem("value") != null) {
813             // the value is given as attribute 'value'
814             vocAttrValue = vocAttrNode.getAttributes().getNamedItem("value").getNodeValue();
815         } else if (vocAttrNode.getChildNodes().getLength() > 1) {
816             // the value is given as DOM-tree
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             // the value is given as text
831             vocAttrValue = vocAttrNode.getFirstChild().getNodeValue();
832         } else {
833             vocAttrValue = "";
834         }
835         return vocAttrValue;
836     }
837 
838     /**
839      * Parses the xml tree for epc nodes and returns a list of EPC URIs.
840      * 
841      * @param eventType
842      * @param epcNode
843      *            The parent Node from which EPC URIs should be extracted.
844      * @return An array of vocabularies containing all the URIs found in the
845      *         given node.
846      * @throws SAXException
847      *             If an unknown tag (no &lt;epc&gt;) is encountered.
848      * @throws InvalidFormatException
849      * @throws DOMException
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                     // if one of the values is an EPC, then all of them must be
864                     // valid EPCs
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      * @param epcOrUri
885      *            The EPC or URI to check.
886      * @param epcRequired
887      *            <code>true</code> if an EPC is required (will throw an
888      *            InvalidFormatException if the given <code>epcOrUri</code> is
889      *            an invalid EPC, but might be a valid URI), <code>false</code>
890      *            otherwise.
891      * @return <code>true</code> if the given <code>epcOrUri</code> is a valid
892      *         EPC, <code>false</code> otherwise.
893      * @throws InvalidFormatException
894      */
895     protected boolean checkEpcOrUri(String epcOrUri, boolean epcRequired) throws InvalidFormatException {
896         boolean isEpc = false;
897         if (epcOrUri.startsWith("urn:epc:id:")) {
898             // check if it is a valid EPC
899             checkEpc(epcOrUri);
900             isEpc = true;
901         } else {
902             // childEPCs in AggregationEvents, and epcList in
903             // TransactionEvents might also be simple URIs
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      * Parses the xml tree for epc nodes and returns a List of BizTransaction
915      * URIs with their corresponding type.
916      * 
917      * @param bizNode
918      *            The parent Node from which BizTransaction URIs should be
919      *            extracted.
920      * @return A List of BizTransaction.
921      * @throws SAXException
922      *             If an unknown tag (no &lt;epc&gt;) is encountered.
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                 // Changed by nkef (use "getOrEditVocabularyElement" instead of
932                 // "getOrInsertVocabularyElement")
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      * (depricated) Inserts vocabulary into the database by searching for
965      * already existing entries; if found, the corresponding ID is returned. If
966      * not found, the vocabulary is extended if "insertmissingvoc" is true;
967      * otherwise an SQLException is thrown
968      * 
969      * @param tableName
970      *            The name of the vocabulary table.
971      * @param uri
972      *            The vocabulary adapting the URI to be inserted into the
973      *            vocabulary table.
974      * @return The ID of an already existing vocabulary table with the given
975      *         uri.
976      * @throws UnsupportedOperationException
977      *             If we are not allowed to insert a missing vocabulary.
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             // the uri does not yet exist: insert it if allowed. According to
993             // the specs, some vocabulary is not allowed to be extended; this is
994             // currently ignored here
995             if (!insertMissingVoc) {
996                 throw new UnsupportedOperationException("Not allowed to add new vocabulary - use existing vocabulary");
997             } else {
998                 // VocabularyElement subclasses should always have public
999                 // zero-arg constructor to avoid problems here
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      * (changed by nkef to support MasterDataCapture. Previusly known as
1018      * "getOrInsertVocabularyElement") Inserts vocabulary into the database by
1019      * searching for already existing entries; if found, the corresponding ID is
1020      * returned. If not found, the vocabulary is extended if "insertmissingvoc"
1021      * is true; otherwise an SQLException is thrown
1022      * 
1023      * @param tableName
1024      *            The name of the vocabulary table.
1025      * @param uri
1026      *            The vocabulary adapting the URI to be inserted into the
1027      *            vocabulary table.
1028      * @return The ID of an already existing vocabulary table with the given
1029      *         uri.
1030      * @throws UnsupportedOperationException
1031      *             If we are not allowed to insert a missing vocabulary.
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             // the uri does not yet exist: insert it if allowed. According to
1064             // the specs, some vocabulary is not allowed to be extended; this is
1065             // currently ignored here
1066             if (!insertMissingVoc) {
1067                 throw new UnsupportedOperationException("Not allowed to add new vocabulary - use existing vocabulary");
1068             } else {
1069                 // VocabularyElement subclasses should always have public
1070                 // zero-arg constructor to avoid problems here
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      * (nkef) Delete the a vocabulary's Element Descendants and all of their
1114      * Attributes
1115      * 
1116      * @param session
1117      * @param vocabularyType
1118      * @param vocabularyElementURI
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      * (nkef) Delete selected id vocabulary elements attributes
1135      * 
1136      * @param session
1137      * @param vocabularyType
1138      * @param vocabularyElementID
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      * (nkef) Inserts vocabulary attribute into the database by searching for
1154      * already existing entries; if found, the corresponding ID is returned. If
1155      * not found, the vocabulary is extended if "insertmissingvoc" is true;
1156      * otherwise an SQLException is thrown
1157      * 
1158      * @param tableName
1159      *            The name of the vocabulary table.
1160      * @param uri
1161      *            The vocabulary adapting the URI to be inserted into the
1162      *            vocabulary table.
1163      * @return The ID of an already existing vocabulary table with the given
1164      *         uri.
1165      * @throws UnsupportedOperationException
1166      *             If we are not allowed to insert a missing vocabulary.
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             // the uri does not yet exist: insert it if allowed. According to
1199             // the specs, some vocabulary is not allowed to be extended; this is
1200             // currently ignored here
1201             if (!insertMissingVoc) {
1202                 throw new UnsupportedOperationException("Not allowed to add new vocabulary - use existing vocabulary");
1203             } else {
1204                 // VocabularyAttributeElement subclasses should always have
1205                 // public zero-arg constructor to avoid problems here
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      * TODO: javadoc!
1238      * 
1239      * @param textContent
1240      * @return
1241      * @throws InvalidFormatException
1242      */
1243     protected String checkEventTimeZoneOffset(String textContent) throws InvalidFormatException {
1244         // first check the provided String against the expected pattern
1245         Pattern p = Pattern.compile("[+-]\\d\\d:\\d\\d");
1246         Matcher m = p.matcher(textContent);
1247         boolean matches = m.matches();
1248         if (matches) {
1249             // second check the values (hours and minutes)
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      * TODO: javadoc!
1269      * 
1270      * @param textContent
1271      * @return
1272      * @throws InvalidFormatException
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      * Check EPC according to 'pure identity' URI as specified in Tag Data
1285      * Standard.
1286      * 
1287      * @param textContent
1288      * @throws InvalidFormatException
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         // check the patterns for the different EPC types
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         // check the number of digits for the different EPC types
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             // nothing to count
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 }