Java tutorial
/* * Copyright (C) 2007 ETH Zurich * * This file is part of Accada (www.accada.org). * * Accada is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1, as published by the Free Software Foundation. * * Accada is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Accada; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA */ package org.accada.epcis.repository.query; import java.net.MalformedURLException; import java.net.URL; import java.sql.SQLException; import java.sql.Timestamp; import java.text.ParseException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.ServletContext; import javax.sql.DataSource; import javax.xml.datatype.XMLGregorianCalendar; import org.accada.epcis.model.ArrayOfString; import org.accada.epcis.model.DuplicateSubscriptionException; import org.accada.epcis.model.EPCISEventType; import org.accada.epcis.model.EventListType; import org.accada.epcis.model.ImplementationException; import org.accada.epcis.model.ImplementationExceptionSeverity; import org.accada.epcis.model.InvalidURIException; import org.accada.epcis.model.NoSuchNameException; import org.accada.epcis.model.NoSuchSubscriptionException; import org.accada.epcis.model.QueryParam; import org.accada.epcis.model.QueryParameterException; import org.accada.epcis.model.QueryParams; import org.accada.epcis.model.QueryResults; import org.accada.epcis.model.QueryResultsBody; import org.accada.epcis.model.QuerySchedule; import org.accada.epcis.model.QueryTooLargeException; import org.accada.epcis.model.SubscribeNotPermittedException; import org.accada.epcis.model.SubscriptionControls; import org.accada.epcis.model.SubscriptionControlsException; import org.accada.epcis.model.ValidationException; import org.accada.epcis.model.VocabularyListType; import org.accada.epcis.repository.EpcisConstants; import org.accada.epcis.repository.EpcisQueryControlInterface; import org.accada.epcis.repository.query.SimpleEventQueryDTO.Operation; import org.accada.epcis.repository.query.SimpleEventQueryDTO.OrderDirection; import org.accada.epcis.soap.DuplicateSubscriptionExceptionResponse; import org.accada.epcis.soap.ImplementationExceptionResponse; import org.accada.epcis.soap.InvalidURIExceptionResponse; import org.accada.epcis.soap.NoSuchNameExceptionResponse; import org.accada.epcis.soap.NoSuchSubscriptionExceptionResponse; import org.accada.epcis.soap.QueryParameterExceptionResponse; import org.accada.epcis.soap.QueryTooComplexExceptionResponse; import org.accada.epcis.soap.QueryTooLargeExceptionResponse; import org.accada.epcis.soap.SecurityExceptionResponse; import org.accada.epcis.soap.SubscribeNotPermittedExceptionResponse; import org.accada.epcis.soap.SubscriptionControlsExceptionResponse; import org.accada.epcis.soap.ValidationExceptionResponse; import org.accada.epcis.utils.TimeParser; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.Transformer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * EPCIS Query Operations Module implementing the SOAP/HTTP binding of the Query * Control Interface. The implementation converts invocations from Axis into SQL * queries and returns the results back to the requesting client through Axis. * * @author David Gubler * @author Alain Remund * @author Arthur van Dorp * @author Marco Steybe */ public class QueryOperationsModule implements EpcisQueryControlInterface { private static final Log LOG = LogFactory.getLog(QueryOperationsModule.class); /** * The version of the standard that this service is implementing. */ private static final String STD_VERSION = "1.0"; /** * The names of all the implemented queries. */ private static final List<String> QUERYNAMES; static { QUERYNAMES = new ArrayList<String>(2); QUERYNAMES.add("SimpleEventQuery"); QUERYNAMES.add("SimpleMasterDataQuery"); } /** * The version of this service implementation. The empty string indicates * that the implementation implements only standard functionality with no * vendor extensions. */ private String serviceVersion = ""; /** * The maximum number of rows a query can return. */ private int maxQueryRows; /** * The maximum timeout to wait for a query to return. */ private int maxQueryTime; // time to wait for checking trigger conditions private String triggerConditionSeconds; private String triggerConditionMinutes; private ServletContext servletContext; private DataSource dataSource; private QueryOperationsBackend backend; /** * Create an SQL query string from the given query parameters. * <p> * Note: the CXF framework always returns an instance of org.w3c.dom.Element * for the query parameter value given in the <code>queryParams</code> * argument, because the spec defines this value to be of type * <code>anyType</code>. CXF <i>does not</i> resolve the type of the * query parameter value from the query name as Axis does! However, if the * user specifies the XML type in the request, then CXF returns an instance * of the corresponding type. * <p> * Consider the following example of a query parameter: * * <pre> * <param> * <name>GE_eventTime</name> * <value>2007-07-07T07:07:07+02:00</value> * </param> * </pre> * * For the query parameter value, CXF will return an instance of * org.w3c.dom.Element containing the text value * "2007-07-07T07:07:07+02:00". However, if the user provides the following * instead, CXF will return an instance of * javax.xml.datatype.XMLGregorianCalendar. * * <pre> * <param> * <name>GE_eventTime</name> * <value * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" * xmlns:xs="http://www.w3.org/2001/XMLSchema" * xsi:type="xs:dateTime"> * 2007-07-07T07:07:07+02:00 * </value> * </param> * </pre> * * As a consequence, we always first need to check if the value is an * instance of Element, and if so, we need to parse it manually according to * the semantics of the parameter name. * * @param queryParams * The query parameters. * @param eventType * Has to be one of the four basic event types "ObjectEvent", * "AggregationEvent", "QuantityEvent", "TransactionEvent". * @return The prepared sql statement. * @throws SQLException * Whenever something goes wrong when querying the db. * @throws QueryParameterException * If one of the given QueryParam is invalid. * @throws ImplementationException * If an error in the implementation occurred. */ private List<SimpleEventQueryDTO> constructSimpleEventQueries(final QueryParams queryParams) throws SQLException, QueryParameterExceptionResponse { SimpleEventQueryDTO aggrEventQuery = new SimpleEventQueryDTO(EpcisConstants.AGGREGATION_EVENT); SimpleEventQueryDTO objEventQuery = new SimpleEventQueryDTO(EpcisConstants.OBJECT_EVENT); SimpleEventQueryDTO quantEventQuery = new SimpleEventQueryDTO(EpcisConstants.QUANTITY_EVENT); SimpleEventQueryDTO transEventQuery = new SimpleEventQueryDTO(EpcisConstants.TRANSACTION_EVENT); boolean includeAggrEvents = true; boolean includeObjEvents = true; boolean includeQuantEvents = true; boolean includeTransEvents = true; String orderBy = null; OrderDirection orderDirection = null; int eventCountLimit = -1; int maxEventCount = -1; // a sorted List of query parameter names - keeps track of the processed // names in order to cope with duplicates List<String> sortedParamNames = new ArrayList<String>(); for (QueryParam param : queryParams.getParam()) { String paramName = param.getName(); Object paramValue = param.getValue(); // check for null values if (paramName == null || "".equals(paramName)) { String msg = "Missing name for a query parameter"; throw queryParameterException(msg, null); } if (paramValue == null) { String msg = "Missing value for query parameter '" + paramName + "'"; throw queryParameterException(msg, null); } // check if the current query parameter has already been provided int index = Collections.binarySearch(sortedParamNames, paramName); if (index < 0) { // we have not yet seen this query parameter name - ok sortedParamNames.add(-index - 1, paramName); } else { // we have already handled this query parameter name - not ok String msg = "Query parameter '" + paramName + "' provided more than once"; throw queryParameterException(msg, null); } if (LOG.isDebugEnabled()) { LOG.debug("Handling query parameter: " + paramName); } try { if (paramName.equals("eventType")) { // by default all event types will be included List<String> eventTypes = parseAsArrayOfString(paramValue).getString(); if (!eventTypes.isEmpty()) { // check if valid event types are provided checkEventTypes(eventTypes); // check for excluded event types if (!eventTypes.contains(EpcisConstants.AGGREGATION_EVENT)) { includeAggrEvents = false; } if (!eventTypes.contains(EpcisConstants.OBJECT_EVENT)) { includeObjEvents = false; } if (!eventTypes.contains(EpcisConstants.QUANTITY_EVENT)) { includeQuantEvents = false; } if (!eventTypes.contains(EpcisConstants.TRANSACTION_EVENT)) { includeTransEvents = false; } } } else if (paramName.equals("GE_eventTime") || paramName.equals("LT_eventTime") || paramName.equals("GE_recordTime") || paramName.equals("LT_recordTime")) { Timestamp ts = parseAsTimestamp(paramValue, paramName); Operation op = Operation.valueOf(paramName.substring(0, 2)); String eventField = paramName.substring(3, paramName.length()); aggrEventQuery.addEventQueryParam(eventField, op, ts); objEventQuery.addEventQueryParam(eventField, op, ts); quantEventQuery.addEventQueryParam(eventField, op, ts); transEventQuery.addEventQueryParam(eventField, op, ts); } else if (paramName.equals("EQ_action")) { // QuantityEvents have no "action" field, thus exclude them includeQuantEvents = false; ArrayOfString aos = parseAsArrayOfString(paramValue); if (!aos.getString().isEmpty()) { checkActionValues(aos.getString()); aggrEventQuery.addEventQueryParam("action", Operation.EQ, aos.getString()); objEventQuery.addEventQueryParam("action", Operation.EQ, aos.getString()); transEventQuery.addEventQueryParam("action", Operation.EQ, aos.getString()); } } else if (paramName.equals("EQ_bizStep") || paramName.equals("EQ_disposition") || paramName.equals("EQ_readPoint") || paramName.equals("EQ_bizLocation")) { ArrayOfString aos = parseAsArrayOfString(paramValue); if (!aos.getString().isEmpty()) { String eventField = paramName.substring(3, paramName.length()); aggrEventQuery.addEventQueryParam(eventField, Operation.EQ, aos.getString()); objEventQuery.addEventQueryParam(eventField, Operation.EQ, aos.getString()); quantEventQuery.addEventQueryParam(eventField, Operation.EQ, aos.getString()); transEventQuery.addEventQueryParam(eventField, Operation.EQ, aos.getString()); } } else if (paramName.equals("WD_readPoint") || paramName.equals("WD_bizLocation")) { ArrayOfString aos = parseAsArrayOfString(paramValue); if (!aos.getString().isEmpty()) { // append a "*" to each of the parameter values - this // should implement the semantics of "With Descendant" // TODO: should??? CollectionUtils.transform(aos.getString(), new StringTransformer()); String eventField = paramName.substring(3, paramName.length()); aggrEventQuery.addEventQueryParam(eventField, Operation.WD, aos.getString()); objEventQuery.addEventQueryParam(eventField, Operation.WD, aos.getString()); quantEventQuery.addEventQueryParam(eventField, Operation.WD, aos.getString()); transEventQuery.addEventQueryParam(eventField, Operation.WD, aos.getString()); } } else if (paramName.startsWith("EQ_bizTransaction_")) { // type extracted from parameter name String bizTransType = paramName.substring(18); ArrayOfString aos = parseAsArrayOfString(paramValue); if (!aos.getString().isEmpty()) { aggrEventQuery.addEventQueryParam("bizTransList.type", Operation.EQ, bizTransType); objEventQuery.addEventQueryParam("bizTransList.type", Operation.EQ, bizTransType); quantEventQuery.addEventQueryParam("bizTransList.type", Operation.EQ, bizTransType); transEventQuery.addEventQueryParam("bizTransList.type", Operation.EQ, bizTransType); aggrEventQuery.addEventQueryParam("bizTransList.bizTrans", Operation.EQ, aos.getString()); objEventQuery.addEventQueryParam("bizTransList.bizTrans", Operation.EQ, aos.getString()); quantEventQuery.addEventQueryParam("bizTransList.bizTrans", Operation.EQ, aos.getString()); transEventQuery.addEventQueryParam("bizTransList.bizTrans", Operation.EQ, aos.getString()); } } else if (paramName.equals("MATCH_epc") || paramName.equals("MATCH_anyEPC")) { // QuantityEvents have no field for EPCs, thus exclude them includeQuantEvents = false; ArrayOfString aos = parseAsArrayOfString(paramValue); if (!aos.getString().isEmpty()) { aggrEventQuery.addEventQueryParam("childEPCs", Operation.MATCH, aos.getString()); objEventQuery.addEventQueryParam("epcList", Operation.MATCH, aos.getString()); transEventQuery.addEventQueryParam("epcList", Operation.MATCH, aos.getString()); if (paramName.equals("MATCH_anyEPC")) { // AggregationEvent and TransactionEvent need // special treatment ("parentID" field) aggrEventQuery.setIsAnyEpc(true); transEventQuery.setIsAnyEpc(true); } } } else if (paramName.equals("MATCH_parentID")) { includeQuantEvents = false; includeObjEvents = false; ArrayOfString aos = parseAsArrayOfString(paramValue); if (!aos.getString().isEmpty()) { aggrEventQuery.addEventQueryParam("parentID", Operation.MATCH, aos.getString()); transEventQuery.addEventQueryParam("parentID", Operation.MATCH, aos.getString()); } } else if (paramName.equals("MATCH_epcClass")) { includeAggrEvents = false; includeObjEvents = false; includeTransEvents = false; ArrayOfString aos = parseAsArrayOfString(paramValue); if (!aos.getString().isEmpty()) { quantEventQuery.addEventQueryParam("epcClass", Operation.MATCH, aos.getString()); } } else if (paramName.endsWith("_quantity")) { includeAggrEvents = false; includeObjEvents = false; includeTransEvents = false; Operation op = Operation.valueOf(paramName.substring(0, paramName.indexOf('_'))); quantEventQuery.addEventQueryParam("quantity", op, parseAsInteger(paramValue)); } else if (paramName.startsWith("GT_") || paramName.startsWith("GE_") || paramName.startsWith("EQ_") || paramName.startsWith("LE_") || paramName.startsWith("LT_")) { // must be an event field extension String fieldname = paramName.substring(3); String[] parts = fieldname.split("#"); if (parts.length != 2) { String msg = "Invalid parameter " + paramName; throw queryParameterException(msg, null); } Operation op = Operation.valueOf(paramName.substring(0, 2)); String eventField; Object value; try { value = parseAsInteger(paramValue); eventField = "extension.intValue"; } catch (NumberFormatException e1) { try { value = parseAsFloat(paramValue); eventField = "extension.floatValue"; } catch (NumberFormatException e2) { try { value = parseAsTimestamp(paramValue, paramName); eventField = "extension.dateValue"; } catch (QueryParameterExceptionResponse e) { value = parseAsString(paramValue); eventField = "extension.strValue"; } } } aggrEventQuery.addEventQueryParam(eventField, op, value); objEventQuery.addEventQueryParam(eventField, op, value); quantEventQuery.addEventQueryParam(eventField, op, value); transEventQuery.addEventQueryParam(eventField, op, value); aggrEventQuery.addEventQueryParam("extension.fieldname", Operation.EQ, fieldname); objEventQuery.addEventQueryParam("extension.fieldname", Operation.EQ, fieldname); quantEventQuery.addEventQueryParam("extension.fieldname", Operation.EQ, fieldname); transEventQuery.addEventQueryParam("extension.fieldname", Operation.EQ, fieldname); } else if (paramName.startsWith("EXISTS_")) { String fieldname = paramName.substring(7); if (fieldname.equals("childEPCs")) { includeObjEvents = false; includeQuantEvents = false; includeTransEvents = false; aggrEventQuery.addEventQueryParam("childEPCs", Operation.EXISTS, null); } else if (fieldname.equals("epcList")) { includeAggrEvents = false; includeQuantEvents = false; objEventQuery.addEventQueryParam("epcList", Operation.EXISTS, null); transEventQuery.addEventQueryParam("epcList", Operation.EXISTS, null); } else if (fieldname.equals("action")) { includeQuantEvents = false; aggrEventQuery.addEventQueryParam("action", Operation.EXISTS, null); objEventQuery.addEventQueryParam("action", Operation.EXISTS, null); transEventQuery.addEventQueryParam("action", Operation.EXISTS, null); } else if (fieldname.equals("parentID")) { includeObjEvents = false; includeQuantEvents = false; aggrEventQuery.addEventQueryParam("parentID", Operation.EXISTS, null); transEventQuery.addEventQueryParam("parentID", Operation.EXISTS, null); } else if (fieldname.equals("quantity") || fieldname.equals("epcClass")) { includeAggrEvents = false; includeObjEvents = false; includeTransEvents = false; quantEventQuery.addEventQueryParam(fieldname, Operation.EXISTS, null); } else if (fieldname.equals("eventTime") || fieldname.equals("recordTime") || fieldname.equals("eventTimeZoneOffset") || fieldname.equals("bizStep") || fieldname.equals("disposition") || fieldname.equals("readPoint") || fieldname.equals("bizLocation") || fieldname.equals("bizTransList")) { aggrEventQuery.addEventQueryParam(fieldname, Operation.EXISTS, null); objEventQuery.addEventQueryParam(fieldname, Operation.EXISTS, null); quantEventQuery.addEventQueryParam(fieldname, Operation.EXISTS, null); transEventQuery.addEventQueryParam(fieldname, Operation.EXISTS, null); } else { // lets see if we have an extension fieldname String[] parts = fieldname.split("#"); if (parts.length != 2) { String msg = "Invalid parameter " + paramName; throw queryParameterException(msg, null); } aggrEventQuery.addEventQueryParam("extension.fieldname", Operation.EQ, fieldname); objEventQuery.addEventQueryParam("extension.fieldname", Operation.EQ, fieldname); quantEventQuery.addEventQueryParam("extension.fieldname", Operation.EQ, fieldname); transEventQuery.addEventQueryParam("extension.fieldname", Operation.EQ, fieldname); } } else if (paramName.startsWith("HASATTR_")) { // restrict by attribute name String fieldname = paramName.substring(8); ArrayOfString aos = parseAsArrayOfString(paramValue); String eventField = fieldname + ".attribute"; aggrEventQuery.addEventQueryParam(eventField, Operation.EQ, aos.getString()); objEventQuery.addEventQueryParam(eventField, Operation.EQ, aos.getString()); quantEventQuery.addEventQueryParam(eventField, Operation.EQ, aos.getString()); transEventQuery.addEventQueryParam(eventField, Operation.EQ, aos.getString()); } else if (paramName.startsWith("EQATTR_")) { String fieldname = paramName.substring(7); String attrname = null; String[] parts = fieldname.split("_"); if (parts.length > 2) { String msg = "Query parameter has invalid format: " + paramName + ". Expected: EQATTR_fieldname_attrname"; throw queryParameterException(msg, null); } else if (parts.length == 2) { fieldname = parts[0]; attrname = parts[1]; } // restrict by attribute name String eventField = fieldname + ".attribute"; aggrEventQuery.addEventQueryParam(eventField, Operation.EQ, attrname); objEventQuery.addEventQueryParam(eventField, Operation.EQ, attrname); quantEventQuery.addEventQueryParam(eventField, Operation.EQ, attrname); transEventQuery.addEventQueryParam(eventField, Operation.EQ, attrname); // restrict by attribute value ArrayOfString aos = parseAsArrayOfString(paramValue); eventField = eventField + ".value"; aggrEventQuery.addEventQueryParam(eventField, Operation.EQ, aos.getString()); objEventQuery.addEventQueryParam(eventField, Operation.EQ, aos.getString()); quantEventQuery.addEventQueryParam(eventField, Operation.EQ, aos.getString()); transEventQuery.addEventQueryParam(eventField, Operation.EQ, aos.getString()); } else if (paramName.equals("orderBy")) { orderBy = parseAsString(paramValue); if (!"eventTime".equals(orderBy) && !"recordTime".equals(orderBy) && !"quantity".equals(orderBy)) { String[] parts = orderBy.split("#"); if (parts.length != 2) { String msg = "orderBy must be one of eventTime, recordTime, quantity, or an extension field"; throw queryParameterException(msg, null); } } } else if (paramName.equals("orderDirection")) { orderDirection = OrderDirection.valueOf(parseAsString(paramValue)); } else if (paramName.equals("eventCountLimit")) { eventCountLimit = parseAsInteger(paramValue).intValue(); } else if (paramName.equals("maxEventCount")) { maxEventCount = parseAsInteger(paramValue).intValue(); } else { String msg = "Unknown query parameter: " + paramName; throw queryParameterException(msg, null); } } catch (ClassCastException e) { String msg = "Type of value invalid for query parameter '" + paramName + "': " + paramValue; throw queryParameterException(msg, e); } catch (IllegalArgumentException e) { String msg = "Unparseable value for query parameter '" + paramName + "'. " + e.getMessage(); throw queryParameterException(msg, e); } } // some more user input checks if (maxEventCount > -1 && eventCountLimit > -1) { String msg = "Paramters 'maxEventCount' and 'eventCountLimit' are mutually exclusive"; throw queryParameterException(msg, null); } if (orderBy == null && eventCountLimit > -1) { String msg = "'eventCountLimit' may only be used when 'orderBy' is specified"; throw queryParameterException(msg, null); } if (orderBy == null && orderDirection != null) { String msg = "'orderDirection' may only be used when 'orderBy' is specified"; throw queryParameterException(msg, null); } if (orderBy != null) { aggrEventQuery.setOrderBy(orderBy); objEventQuery.setOrderBy(orderBy); quantEventQuery.setOrderBy(orderBy); transEventQuery.setOrderBy(orderBy); if (orderDirection != null) { aggrEventQuery.setOrderDirection(orderDirection); objEventQuery.setOrderDirection(orderDirection); quantEventQuery.setOrderDirection(orderDirection); transEventQuery.setOrderDirection(orderDirection); } } if (eventCountLimit > -1) { aggrEventQuery.setLimit(eventCountLimit); objEventQuery.setLimit(eventCountLimit); quantEventQuery.setLimit(eventCountLimit); transEventQuery.setLimit(eventCountLimit); } if (maxEventCount > -1) { aggrEventQuery.setMaxEventCount(maxEventCount); objEventQuery.setMaxEventCount(maxEventCount); quantEventQuery.setMaxEventCount(maxEventCount); transEventQuery.setMaxEventCount(maxEventCount); } List<SimpleEventQueryDTO> eventQueries = new ArrayList<SimpleEventQueryDTO>(4); if (includeAggrEvents) { eventQueries.add(aggrEventQuery); } if (includeObjEvents) { eventQueries.add(objEventQuery); } if (includeQuantEvents) { eventQueries.add(quantEventQuery); } if (includeTransEvents) { eventQueries.add(transEventQuery); } return eventQueries; } /** * Checks if the given List contains valid event type strings, i.e., * AggregationEvent, ObjectEvent, QuantityEvent, or TransactionEvent * * @param eventTypes * The List of Strings to check. * @throws QueryParameterExceptionResponse * If one of the values in the given List is not one of the * valid event types. */ private void checkEventTypes(List<String> eventTypes) throws QueryParameterExceptionResponse { for (String eventType : eventTypes) { if (!EpcisConstants.EVENT_TYPES.contains(eventType)) { String msg = "Unsupported eventType: " + eventType; throw queryParameterException(msg, null); } } } /** * Parses the given query parameter value as String. * * @param queryParamValue * The query parameter value to be parsed as String. * @return The Float holding the value of the query parameter. */ private String parseAsString(Object queryParamValue) throws ClassCastException { if (queryParamValue instanceof String) { return (String) queryParamValue; } else if (queryParamValue instanceof Element) { Element elem = (Element) queryParamValue; return elem.getTextContent().trim(); } else { return queryParamValue.toString(); } } /** * Parses the given query parameter value as Float. * * @param queryParamValue * The query parameter value to be parsed as Float. * @return The Float holding the value of the query parameter. * @throws NumberFormatException * If the query parameter value cannot be parsed as Float. */ private Float parseAsFloat(Object queryParamValue) throws NumberFormatException { if (queryParamValue instanceof Float) { return (Float) queryParamValue; } else if (queryParamValue instanceof Element) { Element elem = (Element) queryParamValue; return Float.valueOf(elem.getTextContent().trim()); } else { return Float.valueOf(queryParamValue.toString()); } } /** * Parses the given query parameter value as Timestamp. * * @param queryParamValue * The query parameter value to be parsed as Timestamp. * @param queryParamName * The query parameter name. * @return The Timestamp holding the value of the query parameter. * @throws QueryParameterExceptionResponse * If the query parameter value cannot be parsed as Timestamp. */ private Timestamp parseAsTimestamp(Object queryParamValue, String queryParamName) throws QueryParameterExceptionResponse { Timestamp ts; if (queryParamValue instanceof Calendar) { // Axis returns a Calendar instance ts = TimeParser.convert((Calendar) queryParamValue); } else if (queryParamValue instanceof XMLGregorianCalendar) { // CXF returns an XMLGregorianCalendar instance if the // XML type is specified ts = TimeParser.convert(((XMLGregorianCalendar) queryParamValue).toGregorianCalendar()); } else { // try to parse the value manually String date = null; if (queryParamValue instanceof Element) { // CXF returns an Element instance if no XML type // was specified in the request Element elem = (Element) queryParamValue; date = elem.getTextContent().trim(); } else { date = queryParamValue.toString(); } if (LOG.isDebugEnabled()) { LOG.debug("Trying to parse the value (" + date + ") for parameter " + queryParamName + " as date/time"); } try { ts = TimeParser.parseAsTimestamp(date); } catch (ParseException e) { String msg = "Unable to parse the value for query parameter '" + queryParamName + "' as date/time"; throw queryParameterException(msg, e); } } return ts; } /** * Parses the given query parameter value as Integer. * * @param queryParamValue * The query parameter value to be parsed as Integer. * @return The Integer holding the value of the query parameter. * @throws NumberFormatException * If the query parameter value cannot be parsed as Integer. */ private Integer parseAsInteger(Object queryParamValue) throws NumberFormatException { if (queryParamValue instanceof Integer) { return (Integer) queryParamValue; } else if (queryParamValue instanceof Element) { Element elem = (Element) queryParamValue; return Integer.valueOf(elem.getTextContent().trim()); } else { return Integer.valueOf(queryParamValue.toString()); } } /** * Parses the given query parameter value into an ArrayOfString object. * * @param queryParamValue * The value of the query parameter to be parsed. * @return The ArrayOfString object representing the list of strings from * the given parameter value. * @throws ClassCastException * If the given paramValue instance cannot be cast to either an * ArrayOfString or Element class. * @throws QueryParameterExceptionResponse * If the given value does not correspond to valid ArrayOfString * syntax, i.e., a list of matching * <code><string></code> <code></string></code> * tags are expected. */ private ArrayOfString parseAsArrayOfString(Object queryParamValue) throws ClassCastException, QueryParameterExceptionResponse { try { return (ArrayOfString) queryParamValue; } catch (ClassCastException e) { // trying to parse manually Element elem = (Element) queryParamValue; NodeList strings = elem.getChildNodes(); ArrayOfString aos = new ArrayOfString(); for (int i = 0; i < strings.getLength(); i++) { if (strings.item(i).getNodeType() == Node.ELEMENT_NODE) { if ("string".equalsIgnoreCase(strings.item(i).getNodeName())) { String s = strings.item(i).getTextContent().trim(); aos.getString().add(s); } else { String msg = "Invalid ArrayOfString syntax: matching <string> </string> tags expected"; throw queryParameterException(msg, null); } } } if (LOG.isDebugEnabled()) { LOG.debug("ArrayOfString parsed to: " + aos.getString()); } return aos; } } /** * Runs a MasterDataQuery for the given QueryParam array and returns the * QueryResults. * * @param queryParams * The parameters for running the MasterDataQuery. * @return The QueryResults. * @throws SQLException * If an error accessing the database occurred. * @throws QueryParameterException * If one of the provided QueryParam is invalid. * @throws ImplementationException * If a service implementation error occurred. * @throws QueryTooLargeException * If the query is too large to be executed. */ private MasterDataQueryDTO constructMasterDataQuery(final QueryParams queryParams) throws QueryParameterExceptionResponse { MasterDataQueryDTO mdQuery = new MasterDataQueryDTO(); // a sorted List of query parameter names - keeps track of the processed // names in order to cope with duplicates List<String> sortedParamNames = new ArrayList<String>(); Boolean includeAttributes = null; Boolean includeChildren = null; List<String> vocabularyTypes = null; List<String> includedAttributeNames = null; for (QueryParam param : queryParams.getParam()) { String paramName = param.getName(); Object paramValue = param.getValue(); // check for null value if (paramName == null || "".equals(paramName)) { String msg = "Missing name for a query parameter"; throw queryParameterException(msg, null); } if (paramValue == null) { String msg = "Missing value for query parameter '" + paramName + "'"; throw queryParameterException(msg, null); } // check if the current query parameter has already been provided int index = Collections.binarySearch(sortedParamNames, paramName); if (index < 0) { // we have not yet seen this query parameter name - ok sortedParamNames.add(-index - 1, paramName); } else { // we have already handled this query parameter name - not ok String msg = "Query parameter '" + paramName + "' provided more than once"; throw queryParameterException(msg, null); } try { if (paramName.equals("includeAttributes")) { // defaults to 'false' if an invalid value is provided! includeAttributes = Boolean.valueOf(parseAsString(paramValue)); mdQuery.setIncludeAttributes(includeAttributes.booleanValue()); } else if (paramName.equals("includeChildren")) { // defaults to 'false' if an invalid value is provided! includeChildren = Boolean.valueOf(parseAsString(paramValue)); mdQuery.setIncludeChildren(includeChildren.booleanValue()); } else if (paramName.equals("maxElementCount")) { int maxElementCount = parseAsInteger(paramValue).intValue(); mdQuery.setMaxElementCount(maxElementCount); } else if (paramName.equals("vocabularyName")) { ArrayOfString aos = parseAsArrayOfString(paramValue); vocabularyTypes = aos.getString(); } else if (paramName.equals("attributeNames")) { ArrayOfString aos = parseAsArrayOfString(paramValue); includedAttributeNames = aos.getString(); } else if (paramName.equals("EQ_name")) { ArrayOfString aos = parseAsArrayOfString(paramValue); mdQuery.setVocabularyEqNames(aos.getString()); } else if (paramName.equals("WD_name")) { ArrayOfString aos = parseAsArrayOfString(paramValue); mdQuery.setVocabularyWdNames(aos.getString()); } else if (paramName.equals("HASATTR")) { ArrayOfString aos = parseAsArrayOfString(paramValue); mdQuery.setAttributeNames(aos.getString()); } else if (paramName.startsWith("EQATTR_")) { String attrName = paramName.substring(7); ArrayOfString aos = parseAsArrayOfString(paramValue); mdQuery.addAttributeNameAndValues(attrName, aos.getString()); } } catch (ClassCastException e) { String msg = "The type of the value for query parameter '" + paramName + "': " + paramValue + " is invalid"; throw queryParameterException(msg, e); } } // check for missing parameters if (includeAttributes == null || includeChildren == null) { String missing = (includeAttributes == null) ? " includeAttributes" : ""; missing += (includeChildren == null) ? " includeChildren" : ""; String msg = "Missing required masterdata query parameter(s):" + missing; throw queryParameterException(msg, null); } if (includeAttributes.booleanValue() && includedAttributeNames != null) { mdQuery.setIncludedAttributeNames(includedAttributeNames); } if (vocabularyTypes == null) { // include all vocabularies vocabularyTypes = EpcisConstants.VOCABULARY_TYPES; } mdQuery.setVocabularyTypes(vocabularyTypes); return mdQuery; } /** * Writes the given message and exception to the application's log file, * creates a QueryParameterException from the given message, and returns a * new QueryParameterExceptionResponse. Use this method to conveniently * return a user error message back to the requesting service caller, e.g.: * * <pre> * String msg = "unable to parse query parameter" * throw new queryParameterException(msg, null); * </pre> * * @param msg * A user error message. * @param e * An internal exception - this exception will not be delivered * back to the service caller as it contains application specific * information. It will be used to print some details about the * user error to the log file (useful for debugging). * @return A new QueryParameterExceptionResponse containing the given user * error message. */ private QueryParameterExceptionResponse queryParameterException(String msg, Exception e) { LOG.info("QueryParameterException: " + msg); if (LOG.isTraceEnabled() && e != null) { LOG.trace("Exception details: " + e.getMessage(), e); } QueryParameterException qpe = new QueryParameterException(); qpe.setReason(msg); return new QueryParameterExceptionResponse(msg, qpe); } /** * Checks if the given action values are valid, i.e. all values must be one * of ADD, OBSERVE, or DELETE. Throws an exception if one of the values is * invalid. * * @param actions * The action values to be checked. * @throws QueryParameterException * If one of the action values are invalid. */ private void checkActionValues(final List<String> actions) throws QueryParameterExceptionResponse { for (String action : actions) { if (!(action.equalsIgnoreCase("ADD") || action.equalsIgnoreCase("OBSERVE") || action.equalsIgnoreCase("DELETE"))) { String msg = "Invalid value for parameter EQ_action: " + action + " - must be one of ADD, OBSERVE, or DELETE"; throw queryParameterException(msg, null); } } } /** * Saves the map with the subscriptions to the message context. * * @param subscriptions * The map with the subscriptions. */ private void saveSubscriptions(final Map<String, QuerySubscriptionScheduled> subscriptions) { servletContext.setAttribute("subscribedMap", subscriptions); } /** * Retrieves the map with the subscriptions from the servlet context. * * @return The map with the subscriptions. * @throws ImplementationException * If the map could not be reloaded. * @throws SQLException * If a database error occurred. */ @SuppressWarnings("unchecked") private Map<String, QuerySubscriptionScheduled> loadSubscriptions(QueryOperationsSession session) throws ImplementationExceptionResponse, SQLException { LOG.debug("Retrieving subscriptions from application context"); Object subscribedMap = servletContext.getAttribute("subscribedMap"); Map<String, QuerySubscriptionScheduled> subscriptions = (HashMap<String, QuerySubscriptionScheduled>) subscribedMap; if (subscriptions == null) { LOG.debug("Subscriptions not found - retrieving subscriptions from database"); subscriptions = backend.fetchSubscriptions(session); } return subscriptions; } /** * {@inheritDoc} */ public List<String> getQueryNames() throws SecurityExceptionResponse, ValidationExceptionResponse, ImplementationExceptionResponse { LOG.info("Invoking 'getQueryNames'"); return QUERYNAMES; } /** * {@inheritDoc} */ public String getStandardVersion() throws SecurityExceptionResponse, ValidationExceptionResponse, ImplementationExceptionResponse { LOG.info("Invoking 'getStandardVersion'"); return STD_VERSION; } /** * {@inheritDoc} */ public List<String> getSubscriptionIDs(String queryName) throws NoSuchNameExceptionResponse, SecurityExceptionResponse, ValidationExceptionResponse, ImplementationExceptionResponse { try { LOG.info("Invoking 'getSubscriptionIDs'"); QueryOperationsSession session = null; try { session = backend.openSession(dataSource); // TODO: filter by queryName?! Map<String, QuerySubscriptionScheduled> subscribedMap = loadSubscriptions(session); Set<String> temp = subscribedMap.keySet(); return new ArrayList<String>(temp); } finally { if (session != null) { session.close(); } LOG.debug("DB connection closed"); } } catch (SQLException e) { ImplementationException iex = new ImplementationException(); String msg = "SQL error during query execution: " + e.getMessage(); LOG.error(msg, e); iex.setReason(msg); iex.setSeverity(ImplementationExceptionSeverity.ERROR); throw new ImplementationExceptionResponse(msg, iex, e); } } /** * {@inheritDoc} */ public String getVendorVersion() throws SecurityExceptionResponse, ValidationExceptionResponse, ImplementationExceptionResponse { LOG.info("Invoking 'getVendorVersion'"); return serviceVersion; } /** * {@inheritDoc} */ public QueryResults poll(String queryName, QueryParams queryParams) throws NoSuchNameExceptionResponse, QueryParameterExceptionResponse, QueryTooComplexExceptionResponse, QueryTooLargeExceptionResponse, SecurityExceptionResponse, ValidationExceptionResponse, ImplementationExceptionResponse { try { LOG.info("Invoking 'poll'"); QueryOperationsSession session = null; try { session = backend.openSession(dataSource); QueryResultsBody resultsBody = null; if (queryName.equals("SimpleEventQuery")) { LOG.info("This is a SimpleEventQuery"); EventListType eventList = new EventListType(); List<SimpleEventQueryDTO> eventQueries = constructSimpleEventQueries(queryParams); // run queries sequentially // TODO: might want to run them in parallel! String orderBy = null; OrderDirection orderDirection = null; int limit = -1; for (SimpleEventQueryDTO eventQuery : eventQueries) { if (eventQuery.getOrderBy() != null) { orderBy = eventQuery.getOrderBy(); orderDirection = eventQuery.getOrderDirection(); limit = eventQuery.getLimit(); } backend.runSimpleEventQuery(session, eventQuery, eventList.getObjectEventOrAggregationEventOrQuantityEvent()); } eventList = checkOrdering(eventList, orderBy, orderDirection, limit); resultsBody = new QueryResultsBody(); resultsBody.setEventList(eventList); } else if (queryName.equals("SimpleMasterDataQuery")) { LOG.info("This is a SimpleMasterDataQuery"); VocabularyListType vocList = new VocabularyListType(); MasterDataQueryDTO mdQuery = constructMasterDataQuery(queryParams); backend.runMasterDataQuery(session, mdQuery, vocList.getVocabulary()); resultsBody = new QueryResultsBody(); resultsBody.setVocabularyList(vocList); } else { session.close(); String msg = "Unsupported query name '" + queryName + "' provided"; LOG.info("NoSuchNameException: " + msg); NoSuchNameException e = new NoSuchNameException(); e.setReason(msg); throw new NoSuchNameExceptionResponse(msg, e); } QueryResults results = new QueryResults(); results.setResultsBody(resultsBody); results.setQueryName(queryName); LOG.info("poll request for '" + queryName + "' succeeded"); return results; } finally { if (session != null) { session.close(); } LOG.debug("DB connection closed"); } } catch (SQLException e) { ImplementationException iex = new ImplementationException(); String msg = "SQL error during query execution: " + e.getMessage(); LOG.error(msg, e); iex.setReason(msg); iex.setSeverity(ImplementationExceptionSeverity.ERROR); throw new ImplementationExceptionResponse(msg, iex, e); } } /** * @param eventList * @param limit * @param orderDirection * @param orderBy * @return */ private EventListType checkOrdering(EventListType eventList, String orderBy, OrderDirection orderDirection, int limit) { if (orderBy == null) { // no ordering specified return eventList; } if ("quantity".equals(orderBy)) { // order by quantity can only return QuantityEvents return eventList; } int size = eventList.getObjectEventOrAggregationEventOrQuantityEvent().size(); if (limit > -1 && size == limit) { // there was only a single event type to be ordered - this has been // taken care of appropriately by the previous query return eventList; } LOG.debug("Need to apply sorting across the different event types (sortBy=" + orderBy + ")"); boolean orderByEventTime = "eventTime".equals(orderBy); Comparator<Object> comparator = new EventComparator(orderByEventTime, orderDirection); Collections.sort(eventList.getObjectEventOrAggregationEventOrQuantityEvent(), comparator); if (limit > -1 && size > limit) { LOG.debug("Need to apply global limit to events (limit=" + limit + ")"); // clear everything beyond limit eventList.getObjectEventOrAggregationEventOrQuantityEvent().subList(limit, size).clear(); } return eventList; } /** * {@inheritDoc} */ public void subscribe(String queryName, QueryParams params, String dest, SubscriptionControls controls, String subscriptionID) throws NoSuchNameExceptionResponse, InvalidURIExceptionResponse, DuplicateSubscriptionExceptionResponse, QueryParameterExceptionResponse, QueryTooComplexExceptionResponse, SubscriptionControlsExceptionResponse, SubscribeNotPermittedExceptionResponse, SecurityExceptionResponse, ValidationExceptionResponse, ImplementationExceptionResponse { try { LOG.info("Invoking 'subscribe'"); QueryOperationsSession session = null; try { session = backend.openSession(dataSource); String triggerURI = controls.getTrigger(); QuerySubscriptionScheduled newSubscription = null; Schedule schedule = null; GregorianCalendar initialRecordTime = controls.getInitialRecordTime().toGregorianCalendar(); if (initialRecordTime == null) { initialRecordTime = new GregorianCalendar(); } // a few input sanity checks // dest may be null or empty. But we don't support pre-arranged // destinations and throw an InvalidURIException according to // the // standard. if (dest == null || dest.toString().equals("")) { String msg = "Destination URI is empty. This implementation doesn't support pre-arranged destinations."; LOG.info("QueryParameterException: " + msg); InvalidURIException e = new InvalidURIException(); e.setReason(msg); throw new InvalidURIExceptionResponse(msg, e); } try { new URL(dest.toString()); } catch (MalformedURLException ex) { String msg = "Destination URI is invalid: " + ex.getMessage(); LOG.info("InvalidURIException: " + msg); InvalidURIException e = new InvalidURIException(); e.setReason(msg); throw new InvalidURIExceptionResponse(msg, e, ex); } // check query name if (!QUERYNAMES.contains(queryName)) { String msg = "Illegal query name '" + queryName + "'"; LOG.info("NoSuchNameException: " + msg); NoSuchNameException e = new NoSuchNameException(); e.setReason(msg); throw new NoSuchNameExceptionResponse(msg, e); } // SimpleMasterDataQuery only valid for polling if (queryName.equals("SimpleMasterDataQuery")) { String msg = "Subscription not allowed for SimpleMasterDataQuery"; LOG.info("SubscribeNotPermittedException: " + msg); SubscribeNotPermittedException e = new SubscribeNotPermittedException(); e.setReason(msg); throw new SubscribeNotPermittedExceptionResponse(msg, e); } // subscriptionID cannot be empty if (subscriptionID == null || subscriptionID.equals("")) { String msg = "SubscriptionID is empty. Choose a valid subscriptionID"; LOG.info(msg); ValidationException e = new ValidationException(); e.setReason(msg); throw new ValidationExceptionResponse(msg, e); } // check for already existing subscriptionID if (backend.fetchExistsSubscriptionId(session, subscriptionID)) { String msg = "SubscriptionID '" + subscriptionID + "' already exists. Choose a different subscriptionID"; LOG.info("DuplicateSubscriptionException: " + msg); DuplicateSubscriptionException e = new DuplicateSubscriptionException(); e.setReason(msg); throw new DuplicateSubscriptionExceptionResponse(msg, e); } // trigger and schedule may no be used together, but one of them // must be set if (controls.getSchedule() != null && controls.getTrigger() != null) { String msg = "Schedule and trigger cannot be used together"; LOG.info("SubscriptionControlsException: " + msg); SubscriptionControlsException e = new SubscriptionControlsException(); e.setReason(msg); throw new SubscriptionControlsExceptionResponse(msg, e); } if (controls.getSchedule() == null && controls.getTrigger() == null) { String msg = "Either schedule or trigger has to be provided"; LOG.info("SubscriptionControlsException: " + msg); SubscriptionControlsException e = new SubscriptionControlsException(); e.setReason(msg); throw new SubscriptionControlsExceptionResponse(msg, e); } if (controls.getSchedule() != null) { // Scheduled Query -> parse schedule schedule = new Schedule(controls.getSchedule()); newSubscription = new QuerySubscriptionScheduled(subscriptionID, params, dest, Boolean.valueOf(controls.isReportIfEmpty()), initialRecordTime, initialRecordTime, schedule, queryName); } else { // -> Trigger // need to set schedule which says how often the trigger // condition is checked. QuerySchedule qSchedule = new QuerySchedule(); qSchedule.setSecond(triggerConditionSeconds); if (triggerConditionMinutes != null) { qSchedule.setMinute(triggerConditionMinutes); } schedule = new Schedule(qSchedule); QuerySubscriptionTriggered trigger = new QuerySubscriptionTriggered(subscriptionID, params, dest, Boolean.valueOf(controls.isReportIfEmpty()), initialRecordTime, initialRecordTime, queryName, triggerURI, schedule); newSubscription = trigger; } // load subscriptions Map<String, QuerySubscriptionScheduled> subscribedMap = loadSubscriptions(session); // store the Query to the database, the local hash map, and the // application context backend.storeSupscriptions(session, params, dest, subscriptionID, controls, triggerURI, newSubscription, queryName, schedule); subscribedMap.put(subscriptionID, newSubscription); saveSubscriptions(subscribedMap); } finally { if (session != null) { session.close(); } LOG.debug("DB connection closed"); } } catch (SQLException e) { String msg = "SQL error during query execution: " + e.getMessage(); LOG.error(msg, e); ImplementationException iex = new ImplementationException(); iex.setReason(msg); iex.setSeverity(ImplementationExceptionSeverity.ERROR); throw new ImplementationExceptionResponse(msg, iex, e); } } /** * {@inheritDoc} */ public void unsubscribe(String subscriptionID) throws NoSuchSubscriptionExceptionResponse, SecurityExceptionResponse, ValidationExceptionResponse, ImplementationExceptionResponse { try { LOG.info("Invoking 'unsubscribe'"); QueryOperationsSession session = null; try { session = backend.openSession(dataSource); Map<String, QuerySubscriptionScheduled> subscribedMap = loadSubscriptions(session); if (subscribedMap.containsKey(subscriptionID)) { // remove subscription from local hash map QuerySubscriptionScheduled toDelete = subscribedMap.get(subscriptionID); toDelete.stopSubscription(); subscribedMap.remove(subscriptionID); saveSubscriptions(subscribedMap); // delete subscription from database backend.deleteSubscription(session, subscriptionID); } else { String msg = "There is no subscription with ID '" + subscriptionID + "'"; LOG.info("NoSuchSubscriptionException: " + msg); NoSuchSubscriptionException e = new NoSuchSubscriptionException(); e.setReason(msg); throw new NoSuchSubscriptionExceptionResponse(msg, e); } } finally { if (session != null) { session.close(); } LOG.debug("DB connection closed"); } } catch (SQLException e) { ImplementationException iex = new ImplementationException(); String msg = "SQL error during query execution: " + e.getMessage(); LOG.error(msg, e); iex.setReason(msg); iex.setSeverity(ImplementationExceptionSeverity.ERROR); throw new ImplementationExceptionResponse(msg, iex, e); } } /** * A Transformer which expects a String instance, appends a "*" to the end * of the String, and returns the new String. * * @author Marco Steybe */ private static class StringTransformer implements Transformer { public Object transform(Object o) { if (o instanceof String) { o = ((String) o).concat("*"); } return o; } } /** * @return the dataSource */ public DataSource getDataSource() { return dataSource; } /** * @param dataSource * the dataSource to set */ public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * @return the maxQueryRows */ public int getMaxQueryRows() { return maxQueryRows; } /** * @param maxQueryRows * the maxQueryRows to set */ public void setMaxQueryRows(int maxQueryRows) { this.maxQueryRows = maxQueryRows; } /** * @return the maxQueryTime */ public int getMaxQueryTime() { return maxQueryTime; } /** * @param maxQueryTime * the maxQueryTime to set */ public void setMaxQueryTime(int maxQueryTime) { this.maxQueryTime = maxQueryTime; } /** * @return the triggerConditionSeconds */ public String getTriggerConditionSeconds() { return triggerConditionSeconds; } /** * @param triggerConditionSeconds * the triggerConditionSeconds to set */ public void setTriggerConditionSeconds(String triggerConditionSeconds) { this.triggerConditionSeconds = triggerConditionSeconds; } /** * @return the triggerConditionMinutes */ public String getTriggerConditionMinutes() { return triggerConditionMinutes; } /** * @param triggerConditionMinutes * the triggerConditionMinutes to set */ public void setTriggerConditionMinutes(String triggerConditionMinutes) { this.triggerConditionMinutes = triggerConditionMinutes; } /** * @param servletContext * the servletContextservletContext to set */ public void setServletContext(ServletContext servletContext) { this.servletContext = servletContext; } /** * @return the serviceVersion */ public String getServiceVersion() { return serviceVersion; } /** * @param serviceVersion * the serviceVersion to set */ public void setServiceVersion(String serviceVersion) { if (!"".equals(serviceVersion)) { // serviceVersion must be a valid URL try { new URL(serviceVersion); } catch (MalformedURLException e) { serviceVersion = "http://www.accada.org/epcis/" + serviceVersion; } } this.serviceVersion = serviceVersion; } /** * @return the backend */ public QueryOperationsBackend getBackend() { return backend; } /** * @param backend * the backend to set */ public void setBackend(QueryOperationsBackend backend) { this.backend = backend; } /** * Compares two EPCIS events according to their eventTime or recordTime. * Careful: the objects to be compared are instances of EPCISEvent, * otherwise a ClassCastException will be thrown. * * @author Marco Steybe */ public class EventComparator implements Comparator<Object> { private boolean orderByEventTime = false; private OrderDirection orderDirection = null; public EventComparator(boolean orderByEventTime, OrderDirection orderDirection) { this.orderByEventTime = orderByEventTime; this.orderDirection = orderDirection; } public int compare(Object o1, Object o2) { EPCISEventType event1 = (EPCISEventType) o1; EPCISEventType event2 = (EPCISEventType) o2; if (orderByEventTime) { if (orderDirection == OrderDirection.ASC) { return event1.getEventTime().compare(event2.getEventTime()); } else { return event2.getEventTime().compare(event1.getEventTime()); } } else { // order by recordTime if (orderDirection == OrderDirection.ASC) { return event1.getRecordTime().compare(event2.getRecordTime()); } else { return event2.getEventTime().compare(event1.getEventTime()); } } } } }