/** * Copyright (C) 2012 52°North Initiative for Geospatial Open Source Software GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.n52.sos.db.impl; import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; import org.n52.gml.Identifier; import org.n52.om.observation.MultiValueObservation; import org.n52.om.result.MeasureResult; import org.n52.ows.InvalidRequestException; import org.n52.ows.ResponseExceedsSizeLimitException; import org.n52.oxf.valueDomains.time.ITimePosition; import org.n52.oxf.valueDomains.time.TimeConverter; import org.n52.sos.Constants; import org.n52.sos.db.AccessGdbForObservations; import org.n52.sos.handler.GetObservationOperationHandler; import org.n52.util.CommonUtilities; import org.n52.util.logging.Logger; import com.esri.arcgis.geodatabase.ICursor; import com.esri.arcgis.geodatabase.IRow; import com.esri.arcgis.interop.AutomationException; /** * @author <a href="mailto:broering@52north.org">Arne Broering</a> */ public class AccessGdbForObservationsImpl implements AccessGdbForObservations { static Logger LOGGER = Logger.getLogger(AccessGdbForObservationsImpl.class.getName()); static String[][] aggregationTypesCandidates = new String[][] { new String[] {Constants.GETOBSERVATION_DEFAULT_AGGREGATIONTYPE, Constants.GETOBSERVATION_DEFAULT_AGGREGATIONTYPE_ALT}, new String[] {Constants.GETOBSERVATION_DEFAULT_AGGREGATIONTYPE_SECOND, Constants.GETOBSERVATION_DEFAULT_AGGREGATIONTYPE_SECOND_ALT}}; private AccessGDBImpl gdb; public AccessGdbForObservationsImpl(AccessGDBImpl accessGDB) { this.gdb = accessGDB; } /** * @return all observations with the specified identifiers. * @throws IOException * @throws AutomationException * @throws ResponseExceedsSizeLimitException */ public Map<String, MultiValueObservation> getObservations(String[] observationIdentifiers) throws ResponseExceedsSizeLimitException, AutomationException, IOException { return getObservations(new StringBuilder(AccessGDBImpl.createOrClause(AccessGDBImpl.concatTableAndField(Table.OBSERVATION, SubField.OBSERVATION_ID), observationIdentifiers)), null, true); } /** * This method can be used to retrieve all observations complying to the * filter as specified by the parameters. The method basically reflects the * SOS:GetObservation() operation on Java level. * * If one of the method parameters is <b>null</b>, it shall not be * considered in the query. * * @return all observations from the database which comply to the * specified parameters. * @throws IOException * @throws ResponseExceedsSizeLimitException * @throws InvalidRequestException * @throws Exception */ @Override public Map<String, MultiValueObservation> getObservations( String[] offerings, String[] featuresOfInterest, String[] observedProperties, String[] procedures, String spatialFilter, String temporalFilter, String[] aggregationTypes, String where) throws IOException, ResponseExceedsSizeLimitException, InvalidRequestException { StringBuilder whereClauseParameterAppend = new StringBuilder(); boolean isFirst = true; // build query for offerings if (offerings != null) { isFirst = ifIsFirstAppendAND (whereClauseParameterAppend, isFirst); whereClauseParameterAppend.append(AccessGDBImpl.createOrClause(AccessGDBImpl.concatTableAndField(Table.NETWORK, SubField.NETWORK_ID), offerings)); } // build query for feature of interest if (featuresOfInterest != null) { isFirst = ifIsFirstAppendAND (whereClauseParameterAppend, isFirst); whereClauseParameterAppend.append(AccessGDBImpl.createOrClause(AccessGDBImpl.concatTableAndField(Table.FEATUREOFINTEREST, SubField.FEATUREOFINTEREST_RESOURCE), featuresOfInterest)); } // build query for observed property if (observedProperties != null) { isFirst = ifIsFirstAppendAND (whereClauseParameterAppend, isFirst); whereClauseParameterAppend.append(AccessGDBImpl.createOrClause(AccessGDBImpl.concatTableAndField(Table.PROPERTY, SubField.PROPERTY_ID), observedProperties)); } // build query for procedure if (procedures != null) { isFirst = ifIsFirstAppendAND (whereClauseParameterAppend, isFirst); whereClauseParameterAppend.append(AccessGDBImpl.createOrClause(AccessGDBImpl.concatTableAndField(Table.PROCEDURE, SubField.PROCEDURE_RESOURCE), procedures)); } // build query for spatial filter if (spatialFilter != null) { // get the IDs of all features which are within the specified // spatialFilter: Collection<String> featureList = gdb.queryFeatureIDsForSpatialFilter(spatialFilter); String[] featureArray = CommonUtilities.toArray(featureList); if (featureList.size() > 0) { isFirst = ifIsFirstAppendAND (whereClauseParameterAppend, isFirst); // append the list of feature IDs: whereClauseParameterAppend.append(AccessGDBImpl.createOrClause(AccessGDBImpl.concatTableAndField(Table.FEATUREOFINTEREST, SubField.FEATUREOFINTEREST_RESOURCE), featureArray)); } else { LOGGER.warn("The defined spatialFilter '" + spatialFilter + "' did not match any features in the database."); } } // build query for temporal filter boolean firstOrLatest = false; if (temporalFilter != null) { if (temporalFilter.equals(GetObservationOperationHandler.OM_PHENOMENON_TIME_FIRST) || temporalFilter.equals(GetObservationOperationHandler.OM_PHENOMENON_TIME_LATEST)) { LOGGER.debug("Temporal filter special case: ".concat(temporalFilter)); firstOrLatest = true; } else { isFirst = ifIsFirstAppendAND (whereClauseParameterAppend, isFirst); whereClauseParameterAppend.append(createTemporalClauseSDE(temporalFilter)); } } // build query for the where clause if (where != null) { isFirst = ifIsFirstAppendAND (whereClauseParameterAppend, isFirst); whereClauseParameterAppend.append(where); } if (firstOrLatest) { return getFirstOrLatestObservation(whereClauseParameterAppend, temporalFilter.equals(GetObservationOperationHandler.OM_PHENOMENON_TIME_FIRST), aggregationTypes); } return getObservations(whereClauseParameterAppend, aggregationTypes, true); } private Map<String, MultiValueObservation> getFirstOrLatestObservation( StringBuilder whereClauseParameterAppend, boolean first, String[] aggregationTypes) throws InvalidRequestException, IOException { if (whereClauseParameterAppend.toString().trim().isEmpty()) { throw new InvalidRequestException("No filter of any kind was defined. Rejecting request."); } String tables = createFromClause(); List<String> subFields = createSubFieldsForQuery(); /* * if no aggregation type was defined in the query use * hourly as this should be there always */ if (aggregationTypes == null) { aggregationTypes = new String[] { Constants.GETOBSERVATION_DEFAULT_AGGREGATIONTYPE, Constants.GETOBSERVATION_DEFAULT_AGGREGATIONTYPE_ALT }; } ifIsFirstAppendAND(whereClauseParameterAppend, false); whereClauseParameterAppend.append(AccessGDBImpl.createOrClause(AccessGDBImpl.concatTableAndField(Table.AGGREGATIONTYPE, SubField.AGGREGATIONTYPE_ID), aggregationTypes)); String originalWhereClause = whereClauseParameterAppend.toString(); /* * SubQuery using MIN/MAX as ArcObject does not support ORDER BY */ whereClauseParameterAppend.append(" AND "); whereClauseParameterAppend.append(AccessGDBImpl.concatTableAndField(Table.VALUE, SubField.VALUE_DATETIME_END)); whereClauseParameterAppend.append(" = ("); if (first) { whereClauseParameterAppend.append("SELECT MIN("); } else { whereClauseParameterAppend.append("SELECT MAX("); } whereClauseParameterAppend.append(AccessGDBImpl.concatTableAndField(Table.VALUE, SubField.VALUE_DATETIME_END)); whereClauseParameterAppend.append(")"); whereClauseParameterAppend.append(" FROM "); whereClauseParameterAppend.append(tables); whereClauseParameterAppend.append(" WHERE "); whereClauseParameterAppend.append(originalWhereClause); whereClauseParameterAppend.append(" )"); String whereClause = whereClauseParameterAppend.toString(); LOGGER.debug("WHERE "+ whereClause); ICursor cursor = DatabaseUtils.evaluateQuery(tables, whereClause, " DISTINCT TOP 1 ".concat(AccessGDBImpl.createCommaSeparatedList(subFields)), gdb); Map<String, MultiValueObservation> idObsMap = createObservationsFromCursor(cursor, subFields); return idObsMap; } /** * This method serves as a skeleton for the other 2 methods above and * expects a WHERE clause that parameterizes the database query. * @param aggregationTypes * @throws ResponseExceedsSizeLimitException * @throws IOException * @throws AutomationException */ private Map<String, MultiValueObservation> getObservations(StringBuilder whereClauseParameterAppend, String[] aggregationTypes, boolean checkForMaxRecords) throws ResponseExceedsSizeLimitException, AutomationException, IOException { String tables = createFromClause(); List<String> subFields = createSubFieldsForQuery(); /* * if there are values for aggregationTypes, then it * is defined via the request. otherwise try the default * values */ boolean alreadyAssertedMaxRecords = false; if (aggregationTypes != null) { ifIsFirstAppendAND(whereClauseParameterAppend, whereClauseParameterAppend.toString().trim().isEmpty()); whereClauseParameterAppend.append(AccessGDBImpl.createOrClause(AccessGDBImpl.concatTableAndField(Table.AGGREGATIONTYPE, SubField.AGGREGATIONTYPE_ID), aggregationTypes)); } else { alreadyAssertedMaxRecords = determineBestAggregationType(whereClauseParameterAppend, tables, checkForMaxRecords); } String whereClause = whereClauseParameterAppend.toString(); if (checkForMaxRecords && !alreadyAssertedMaxRecords) { DatabaseUtils.assertMaximumRecordCount(tables, whereClause, gdb); } ICursor cursor = DatabaseUtils.evaluateQuery(tables, whereClause, " DISTINCT " + AccessGDBImpl.createCommaSeparatedList(subFields), gdb); Map<String, MultiValueObservation> idObsMap = createObservationsFromCursor(cursor, subFields); return idObsMap; } private Map<String, MultiValueObservation> createObservationsFromCursor( ICursor cursor, List<String> fields) throws IOException { // convert cursor entries to abstract observations // map that associates an observation-ID with an observation: Map<String, MultiValueObservation> idObsMap = new HashMap<String, MultiValueObservation>(); IRow row; while ((row = cursor.nextRow()) != null) { String obsID = row.getValue(fields.indexOf(AccessGDBImpl.concatTableAndField(Table.OBSERVATION, SubField.OBSERVATION_ID))).toString(); if (!idObsMap.containsKey(obsID)) { MultiValueObservation multiValObs = createMultiValueObservation(row, fields); multiValObs.getResult().addResultValue(createResultValue(row, fields)); idObsMap.put(obsID, multiValObs); } else { idObsMap.get(obsID).getResult().addResultValue(createResultValue(row, fields)); } } return idObsMap; } private boolean determineBestAggregationType( StringBuilder whereClauseParameterAppend, String tables, boolean checkForMaxRecords) { int lengthBefore = whereClauseParameterAppend.length(); int c; for (String[] aggregationTypes : aggregationTypesCandidates) { ifIsFirstAppendAND(whereClauseParameterAppend, whereClauseParameterAppend.toString().trim().isEmpty()); whereClauseParameterAppend.append(AccessGDBImpl.createOrClause(AccessGDBImpl.concatTableAndField(Table.AGGREGATIONTYPE, SubField.AGGREGATIONTYPE_ID), aggregationTypes)); c = DatabaseUtils.resolveRecordCount(tables, whereClauseParameterAppend.toString(), gdb); if (c > 0 && (!checkForMaxRecords || c < gdb.getMaxNumberOfResults())) { return true; } whereClauseParameterAppend.setLength(lengthBefore); } return false; } private List<String> createSubFieldsForQuery() { List<String> subFields = new ArrayList<String>(); subFields.add(AccessGDBImpl.concatTableAndField(Table.OBSERVATION, SubField.OBSERVATION_PK_OBSERVATION)); //this field is only needed so that DISTINCT works subFields.add(AccessGDBImpl.concatTableAndField(Table.OBSERVATION, SubField.OBSERVATION_ID)); subFields.add(AccessGDBImpl.concatTableAndField(Table.PROCEDURE, SubField.PROCEDURE_RESOURCE)); subFields.add(AccessGDBImpl.concatTableAndField(Table.SAMPLINGPOINT, SubField.SAMPLINGPOINT_RESOURCE)); subFields.add(AccessGDBImpl.concatTableAndField(Table.SAMPLINGPOINT, SubField.SAMPLINGPOINT_ID)); subFields.add(AccessGDBImpl.concatTableAndField(Table.FEATUREOFINTEREST, SubField.FEATUREOFINTEREST_RESOURCE)); subFields.add(AccessGDBImpl.concatTableAndField(Table.PROPERTY, SubField.PROPERTY_ID)); subFields.add(AccessGDBImpl.concatTableAndField(Table.UNIT, SubField.UNIT_NOTATION)); subFields.add(AccessGDBImpl.concatTableAndField(Table.UNIT, SubField.UNIT_ID)); subFields.add(AccessGDBImpl.concatTableAndField(Table.UNIT, SubField.UNIT_LABEL)); subFields.add(AccessGDBImpl.concatTableAndField(Table.VALUE, SubField.VALUE_DATETIME_BEGIN)); subFields.add(AccessGDBImpl.concatTableAndField(Table.VALUE, SubField.VALUE_DATETIME_END)); subFields.add(AccessGDBImpl.concatTableAndField(Table.VALUE, SubField.VALUE_VALUE_NUMERIC)); subFields.add(AccessGDBImpl.concatTableAndField(Table.VALUE, SubField.VALUE_RESULTTIME)); subFields.add(AccessGDBImpl.concatTableAndField(Table.VALIDITY, SubField.VALIDITY_NOTATION)); subFields.add(AccessGDBImpl.concatTableAndField(Table.VERIFICATION, SubField.VERIFICATION_NOTATION)); subFields.add(AccessGDBImpl.concatTableAndField(Table.AGGREGATIONTYPE, SubField.AGGREGATIONTYPE_DEFINITION)); subFields.add(AccessGDBImpl.concatTableAndField(Table.AGGREGATIONTYPE, SubField.AGGREGATIONTYPE_NOTATION)); return subFields; } private String createFromClause() { String fromClause = Table.OBSERVATION + " LEFT JOIN " + Table.FEATUREOFINTEREST + " ON " + Table.OBSERVATION + "." + SubField.OBSERVATION_FK_FEATUREOFINTEREST + " = " + Table.FEATUREOFINTEREST + "." + SubField.FEATUREOFINTEREST_PK_FEATUREOFINTEREST + " LEFT JOIN " + Table.PROCEDURE + " ON " + Table.OBSERVATION + "." + SubField.OBSERVATION_FK_PROCEDURE + " = " + Table.PROCEDURE + "." + SubField.PROCEDURE_PK_PROCEDURE + " LEFT JOIN " + Table.PROPERTY + " ON " + Table.OBSERVATION + "." + SubField.OBSERVATION_FK_PROPERTY + " = " + Table.PROPERTY + "." + SubField.PROPERTY_PK_PROPERTY + " LEFT JOIN " + Table.SAMPLINGPOINT + " ON " + Table.OBSERVATION + "." + SubField.OBSERVATION_FK_SAMPLINGPOINT + " = " + Table.SAMPLINGPOINT + "." + SubField.SAMPLINGPOINT_PK_SAMPLINGPOINT + " LEFT JOIN " + Table.VALUE + " ON " + Table.VALUE + "." + SubField.VALUE_FK_OBSERVATION + " = " + Table.OBSERVATION + "." + SubField.OBSERVATION_PK_OBSERVATION + " LEFT JOIN " + Table.VALIDITY + " ON " + Table.VALUE + "." + SubField.VALUE_FK_VALIDITY + " = " + Table.VALIDITY + "." + SubField.VALIDITY_PK_VALIDITY + " LEFT JOIN " + Table.VERIFICATION + " ON " + Table.VALUE + "." + SubField.VALUE_FK_VERIFICATION + " = " + Table.VERIFICATION + "." + SubField.VERIFICATION_PK_VERIFICATION + " LEFT JOIN " + Table.AGGREGATIONTYPE + " ON " + Table.VALUE + "." + SubField.VALUE_FK_AGGREGATIONTYPE + " = " + Table.AGGREGATIONTYPE + "." + SubField.AGGREGATIONTYPE_PK_AGGREGATIONTYPE + " LEFT JOIN " + Table.STATION + " ON " + Table.SAMPLINGPOINT + "." + SubField.SAMPLINGPOINT_FK_STATION + " = " + Table.STATION + "." + SubField.STATION_PK_STATION + " LEFT JOIN " + Table.UNIT + " ON " + Table.UNIT + "." + SubField.UNIT_PK_UNIT + " = " + Table.VALUE + "." + SubField.VALUE_FK_UNIT + " LEFT JOIN " + Table.NETWORK + " ON " + Table.NETWORK + "." + SubField.NETWORK_PK_NETWOK + " = " + Table.STATION + "." + SubField.STATION_FK_NETWORK_GID; return fromClause; } // ///////////////////////////// // //////////////////////////// Helper Methods: // ///////////////////////////// protected MultiValueObservation createMultiValueObservation(IRow row, List<String> fields) throws IOException { // Identifier String obsID = row.getValue(fields.indexOf(AccessGDBImpl.concatTableAndField(Table.OBSERVATION, SubField.OBSERVATION_ID))).toString(); Identifier obsIdentifier = new Identifier(null, obsID); // procedure String procID = (String) row.getValue(fields.indexOf(AccessGDBImpl.concatTableAndField(Table.PROCEDURE, SubField.PROCEDURE_RESOURCE))); if (procID == null) { procID = Constants.NULL_VALUE; } // observed property String obsPropID = (String) row.getValue(fields.indexOf(AccessGDBImpl.concatTableAndField(Table.PROPERTY, SubField.PROPERTY_ID))); if (obsPropID == null) { obsPropID = Constants.NULL_VALUE; } // featureOfInterest String featureID = (String) row.getValue(fields.indexOf(AccessGDBImpl.concatTableAndField(Table.FEATUREOFINTEREST, SubField.FEATUREOFINTEREST_RESOURCE))); if (featureID == null) { featureID = Constants.NULL_VALUE; } // samplingFeature String samplingPointID = (String) row.getValue(fields.indexOf(AccessGDBImpl.concatTableAndField(Table.SAMPLINGPOINT, SubField.SAMPLINGPOINT_RESOURCE))); // in case "resource" field is null, "id" field is used: if (samplingPointID == null || samplingPointID.equals("")) { samplingPointID = (String) row.getValue(fields.indexOf(AccessGDBImpl.concatTableAndField(Table.SAMPLINGPOINT, SubField.SAMPLINGPOINT_ID))); } // unit ID String unitID = (String) row.getValue(fields.indexOf(AccessGDBImpl.concatTableAndField(Table.UNIT, SubField.UNIT_ID))); if (unitID == null) { unitID = Constants.NULL_VALUE; } // unit notation String unitNotation = (String) row.getValue(fields.indexOf(AccessGDBImpl.concatTableAndField(Table.UNIT, SubField.UNIT_NOTATION))); if (unitNotation == null) { unitNotation = Constants.NULL_VALUE; } // unit notation String unitLabel = (String) row.getValue(fields.indexOf(AccessGDBImpl.concatTableAndField(Table.UNIT, SubField.UNIT_LABEL))); if (unitLabel == null) { unitLabel = Constants.NULL_VALUE; } // aggregation type String aggregationType = (String) row.getValue(fields.indexOf(AccessGDBImpl.concatTableAndField(Table.AGGREGATIONTYPE, SubField.AGGREGATIONTYPE_DEFINITION))); if (aggregationType == null) { aggregationType = Constants.NULL_VALUE; } // result time Date resultDate = (Date) row.getValue(fields.indexOf(AccessGDBImpl.concatTableAndField(Table.VALUE, SubField.VALUE_RESULTTIME))); ITimePosition resultTimePos = TimeConverter.createTimeFromDate(resultDate, null); return new MultiValueObservation(obsIdentifier, procID, obsPropID, featureID, samplingPointID, unitID, unitNotation, unitLabel, aggregationType, resultTimePos); } /** * * @param row * @param fields * @return * @throws IOException * @throws AutomationException */ protected MeasureResult createResultValue(IRow row, List<String> fields) throws AutomationException, IOException { // start time Date startDate = (Date) row.getValue(fields.indexOf(AccessGDBImpl.concatTableAndField(Table.VALUE, SubField.VALUE_DATETIME_BEGIN))); ITimePosition startTimePos = TimeConverter.createTimeFromDate(startDate, null); // end time Date endDate = (Date) row.getValue(fields.indexOf(AccessGDBImpl.concatTableAndField(Table.VALUE, SubField.VALUE_DATETIME_END))); ITimePosition endTimePos = TimeConverter.createTimeFromDate(endDate, null); // validity String validity = (String) row.getValue(fields.indexOf(AccessGDBImpl.concatTableAndField(Table.VALIDITY, SubField.VALIDITY_NOTATION))); if (validity == null) { validity = Constants.NULL_VALUE; } // verification String verification = (String) row.getValue(fields.indexOf(AccessGDBImpl.concatTableAndField(Table.VERIFICATION, SubField.VERIFICATION_NOTATION))); if (verification == null) { verification = Constants.NULL_VALUE; } //aggregationType String aggregationType = (String) row.getValue(fields.indexOf(AccessGDBImpl.concatTableAndField(Table.AGGREGATIONTYPE, SubField.AGGREGATIONTYPE_NOTATION))); if (aggregationType == null) { aggregationType = Constants.NULL_VALUE; } // result Object numValue = row.getValue(fields.indexOf(AccessGDBImpl.concatTableAndField(Table.VALUE, SubField.VALUE_VALUE_NUMERIC))); Double value = (Double) numValue; return new MeasureResult(startTimePos, endTimePos, validity, verification, aggregationType, value); } /** * This method creates a temporal database clause out of a given temporal filter as * String. * * options of temporal filters:<br> * 1.) equals:yyyy-MM-ddTHH:mm:ss+HH:mm<br> * 2.) during:yyyy-MM-ddTHH:mm:ss+HH:mm,yyyy-MM-dd HH:mm:ss+HH:mm<br> * 3.) after:yyyy-MM-ddTHH:mm:ss+HH:mm<br> * 4.) before:yyyy-MM-ddTHH:mm:ss+HH:mm<br> * 5.) last:milliseconds,+HH:mm<br> * * @param temporalFilter * The temporal filter as specified. * @return A database conform request for the temporal filter. * @throws IllegalArgumentException */ public String createTemporalClauseSDE(String temporalFilter) throws IllegalArgumentException { String clause = null; String tempOperand = TimeConverter.extractTemporalOperandAfterKeyWord(temporalFilter); if (temporalFilter.contains("during:")) { String timeStart = TimeConverter.convertLocalToUTC(tempOperand.split(",")[0]); String timeEnd = TimeConverter.convertLocalToUTC(tempOperand.split(",")[1]); clause = SubField.VALUE_DATETIME_END + " BETWEEN '" + timeStart + "' AND '" + timeEnd + "'"; } else if (temporalFilter.contains("equals:")) { String timeInstant = TimeConverter.convertLocalToUTC(tempOperand); clause = SubField.VALUE_DATETIME_END + " = '" + timeInstant + "'"; } else if (temporalFilter.contains("after:")) { String timeInstant = TimeConverter.convertLocalToUTC(tempOperand); clause = SubField.VALUE_DATETIME_END + " > '" + timeInstant + "'"; } else if (temporalFilter.contains("before:")) { String timeInstant = TimeConverter.convertLocalToUTC(tempOperand); clause = SubField.VALUE_DATETIME_END + " < '" + timeInstant + "'"; } else if (temporalFilter.contains("last:")) { long duration = Long.parseLong(tempOperand); // convert to UTC, since database is in UTC: Calendar utcTime = new GregorianCalendar(TimeZone.getTimeZone("UTC")); utcTime.setTimeInMillis(System.currentTimeMillis() - duration); int year = utcTime.get(Calendar.YEAR); int month = utcTime.get(Calendar.MONTH) + 1; int day = utcTime.get(Calendar.DAY_OF_MONTH); int hour = utcTime.get(Calendar.HOUR_OF_DAY); int minute = utcTime.get(Calendar.MINUTE); int second = utcTime.get(Calendar.SECOND); String timeInstant = TimeConverter.toISO8601(false, year, month, day, hour, minute, second); clause = SubField.VALUE_DATETIME_END + " > '" + timeInstant + "'"; } else { throw new IllegalArgumentException("Error while parsing the temporal filter."); } return clause; } /** * helper method to reduce code length. Appends "AND" to WHERE clause if 'isFirst == false'. */ private boolean ifIsFirstAppendAND (StringBuilder whereClauseParameterAppend, boolean isFirst) { if (isFirst == false) { whereClauseParameterAppend.append(" AND "); } else { isFirst = false; } return isFirst; } }