/** * 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.Collection; import java.util.Iterator; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.n52.oxf.valueDomains.time.ITimePosition; import org.n52.oxf.valueDomains.time.TimePeriod; import org.n52.sos.cache.OnOfferingRetrieved; import org.n52.sos.cache.OnOfferingRetrieved.RetrievingCancelledException; import org.n52.sos.dataTypes.AGSEnvelope; import org.n52.sos.dataTypes.ObservationOffering; import org.n52.sos.db.AccessGdbForOfferings; import org.n52.util.logging.Logger; import com.esri.arcgis.geodatabase.ICursor; import com.esri.arcgis.geodatabase.IRow; import com.esri.arcgis.geometry.Envelope; import com.esri.arcgis.geometry.Point; /** * @author <a href="mailto:broering@52north.org">Arne Broering</a> */ public class AccessGdbForOfferingsImpl implements AccessGdbForOfferings { static Logger LOGGER = Logger.getLogger(AccessGdbForOfferingsImpl.class.getName()); private ExecutorService futureExecutor = Executors.newSingleThreadExecutor(); private AccessGDBImpl gdb; private Collection<ObservationOffering> observationOfferingsCache; public AccessGdbForOfferingsImpl(AccessGDBImpl accessGDB) { this.gdb = accessGDB; } /** * This method can be used to retrieve all {@link ObservationOffering}s * associated with the SOS. * * @return all offerings from the Geodatabase * @throws IOException */ @Override public void getNetworksAsObservationOfferingsAsync( OnOfferingRetrieved retriever) throws IOException { LOGGER.info("getNetworksAsObservationOfferings() is called. "+System.identityHashCode(this)); List<ObservationOffering> offerings = new ArrayList<ObservationOffering>(); // ~~~~~~~~~~~~~~~~~~~~ // request all networks: // set tables List<String> tables = new ArrayList<String>(); tables.add(Table.NETWORK); // set sub fields List<String> subFields = new ArrayList<String>(); subFields.add(AccessGDBImpl.concatTableAndField(Table.NETWORK, SubField.NETWORK_ID)); // evaluate the database query ICursor cursor; try { cursor = retrieveCursor(tables, new StringBuilder(), subFields); } catch (ExecutionException e1) { throw new IOException(e1); } // convert cursor entries to abstract observations IRow row; try { while ((row = retrieveNextRow(cursor)) != null) { // We will use the 'network identifier' as the 'offering id' and 'offering name' String networkIdentifier = (String) row.getValue(subFields.indexOf(AccessGDBImpl.concatTableAndField(Table.NETWORK, SubField.NETWORK_ID))); // offering name String name = networkIdentifier; // offering id String id = networkIdentifier; ObservationOffering offering = new ObservationOffering(id, name, null, networkIdentifier, null, null); offerings.add(offering); } } catch (ExecutionException e1) { throw new IOException(e1); } /* * tell the retriever how many offerings might come in */ retriever.retrieveExpectedOfferingsCount(offerings.size()); int currentOffering = 0; for (ObservationOffering offering : offerings) { currentOffering++; try { LOGGER.info("Working on offering (id: '" + offering.getId() + "') at index " + offerings.indexOf(offering) + " out of " + offerings.size()); safetySleep(200); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // request the timeperiod // set tables final List<String> tablesTime = new ArrayList<String>(); tablesTime.add(Table.VALUE); tablesTime.add(Table.OBSERVATION); tablesTime.add(Table.SAMPLINGPOINT); tablesTime.add(Table.STATION); tablesTime.add(Table.NETWORK); // LOGGER.info("Tables clause := " + queryDefTime.getTables()); // set sub fields final List<String> subFieldsTime = new ArrayList<String>(); subFieldsTime.add("MIN(" + AccessGDBImpl.concatTableAndField(Table.VALUE, SubField.VALUE_DATETIME_END)+") AS MINTIME"); subFieldsTime.add("MAX(" + AccessGDBImpl.concatTableAndField(Table.VALUE, SubField.VALUE_DATETIME_END)+") AS MAXTIME"); // create where clause with joins and constraints final StringBuilder whereClauseTime = new StringBuilder(); whereClauseTime.append(AccessGDBImpl.concatTableAndField(Table.VALUE, SubField.VALUE_FK_OBSERVATION) + " = " + AccessGDBImpl.concatTableAndField(Table.OBSERVATION, SubField.OBSERVATION_PK_OBSERVATION)); whereClauseTime.append(" AND "); whereClauseTime.append(AccessGDBImpl.concatTableAndField(Table.OBSERVATION, SubField.OBSERVATION_FK_SAMPLINGPOINT) + " = " + AccessGDBImpl.concatTableAndField(Table.SAMPLINGPOINT, SubField.SAMPLINGPOINT_PK_SAMPLINGPOINT)); whereClauseTime.append(" AND "); whereClauseTime.append(AccessGDBImpl.concatTableAndField(Table.SAMPLINGPOINT, SubField.SAMPLINGPOINT_FK_STATION) + " = " + AccessGDBImpl.concatTableAndField(Table.STATION, SubField.STATION_PK_STATION)); whereClauseTime.append(" AND "); whereClauseTime.append(AccessGDBImpl.concatTableAndField(Table.STATION, SubField.STATION_FK_NETWORK_GID) + " = " + AccessGDBImpl.concatTableAndField(Table.NETWORK, SubField.NETWORK_PK_NETWOK)); whereClauseTime.append(" AND "); whereClauseTime.append(AccessGDBImpl.concatTableAndField(Table.NETWORK, SubField.NETWORK_ID) + " = '" + offering.getId() + "'"); LOGGER.debug(String.format("Evaluating time query for network: '%s'", offering.getId())); final ICursor cursorOffering = retrieveCursor(tablesTime, whereClauseTime, subFieldsTime); row = retrieveNextRow(cursorOffering); Object startValue = row.getValue(0); Object endValue = row.getValue(1); if (startValue == null || endValue == null) { LOGGER.debug("skipping network"); continue; } else { // LOGGER.info("start time: " + startValue); // LOGGER.info("end time: " + endValue); // start time stamp ITimePosition startTime = AccessGDBImpl.createTimePosition(startValue); // end time stamp ITimePosition endTime = AccessGDBImpl.createTimePosition(endValue); // add time extent to offering if (startTime != null && endTime != null) { offering.setTimeExtent(new TimePeriod(startTime, endTime)); } } safetySleep(200); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // set observed property // set tables List<String> tablesProp = new ArrayList<String>(); tablesProp.add(Table.PROPERTY); tablesProp.add(Table.OBSERVATION); tablesProp.add(Table.SAMPLINGPOINT); tablesProp.add(Table.STATION); tablesProp.add(Table.NETWORK); // set sub fields List<String> subFieldsProp = new ArrayList<String>(); subFieldsProp.add(AccessGDBImpl.concatTableAndField(Table.PROPERTY, SubField.PROPERTY_ID)); // create where clause with joins and constraints StringBuilder whereClauseProp = new StringBuilder(); whereClauseProp.append(AccessGDBImpl.concatTableAndField(Table.OBSERVATION, SubField.OBSERVATION_FK_PROPERTY) + " = " + AccessGDBImpl.concatTableAndField(Table.PROPERTY, SubField.PROPERTY_PK_PROPERTY)); whereClauseProp.append(" AND "); whereClauseProp.append(AccessGDBImpl.concatTableAndField(Table.OBSERVATION, SubField.OBSERVATION_FK_SAMPLINGPOINT) + " = " + AccessGDBImpl.concatTableAndField(Table.SAMPLINGPOINT, SubField.SAMPLINGPOINT_PK_SAMPLINGPOINT)); whereClauseProp.append(" AND "); whereClauseProp.append(AccessGDBImpl.concatTableAndField(Table.SAMPLINGPOINT, SubField.SAMPLINGPOINT_FK_STATION) + " = " + AccessGDBImpl.concatTableAndField(Table.STATION, SubField.STATION_PK_STATION)); whereClauseProp.append(" AND "); whereClauseProp.append(AccessGDBImpl.concatTableAndField(Table.STATION, SubField.STATION_FK_NETWORK_GID) + " = " + AccessGDBImpl.concatTableAndField(Table.NETWORK, SubField.NETWORK_PK_NETWOK)); whereClauseProp.append(" AND "); whereClauseProp.append(AccessGDBImpl.concatTableAndField(Table.NETWORK, SubField.NETWORK_ID) + " = '" + offering.getId() + "'"); LOGGER.debug(String.format("Evaluating property query for network: '%s'", offering.getId())); // evaluate the database query ICursor cursorProp = retrieveCursor(tablesProp, whereClauseProp, subFieldsProp); List<String> obsProps = new ArrayList<String>(); while ((row = retrieveNextRow(cursorProp)) != null) { String obsPropID = (String) row.getValue(subFieldsProp.indexOf(AccessGDBImpl.concatTableAndField(Table.PROPERTY, SubField.PROPERTY_ID))); if (! obsProps.contains(obsPropID)) { obsProps.add(obsPropID); } } // copy obsProps list to String Array: String[] obsPropsArray = new String[obsProps.size()]; int i=0; for (Iterator<String> iterator = obsProps.iterator(); iterator.hasNext();) { obsPropsArray[i++] = (String) iterator.next(); } offering.setObservedProperties(obsPropsArray); safetySleep(200); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // set envelope through feature positions // set tables List<String> tablesFoi = new ArrayList<String>(); tablesFoi.add(Table.FEATUREOFINTEREST); tablesFoi.add(Table.OBSERVATION); tablesFoi.add(Table.SAMPLINGPOINT); tablesFoi.add(Table.STATION); tablesFoi.add(Table.NETWORK); // set sub fields List<String> subFieldsFoi = new ArrayList<String>(); subFieldsFoi.add(AccessGDBImpl.concatTableAndField(Table.FEATUREOFINTEREST, SubField.FEATUREOFINTEREST_SHAPE)); // create the where clause with joins and constraints StringBuilder whereClauseFoi = new StringBuilder(); whereClauseFoi.append(AccessGDBImpl.concatTableAndField(Table.OBSERVATION, SubField.OBSERVATION_FK_FEATUREOFINTEREST) + " = " + AccessGDBImpl.concatTableAndField(Table.FEATUREOFINTEREST, SubField.FEATUREOFINTEREST_PK_FEATUREOFINTEREST)); whereClauseFoi.append(" AND "); whereClauseFoi.append(AccessGDBImpl.concatTableAndField(Table.OBSERVATION, SubField.OBSERVATION_FK_SAMPLINGPOINT) + " = " + AccessGDBImpl.concatTableAndField(Table.SAMPLINGPOINT, SubField.SAMPLINGPOINT_PK_SAMPLINGPOINT)); whereClauseFoi.append(" AND "); whereClauseFoi.append(AccessGDBImpl.concatTableAndField(Table.SAMPLINGPOINT, SubField.SAMPLINGPOINT_FK_STATION) + " = " + AccessGDBImpl.concatTableAndField(Table.STATION, SubField.STATION_PK_STATION)); whereClauseFoi.append(" AND "); whereClauseFoi.append(AccessGDBImpl.concatTableAndField(Table.STATION, SubField.STATION_FK_NETWORK_GID) + " = " + AccessGDBImpl.concatTableAndField(Table.NETWORK, SubField.NETWORK_PK_NETWOK)); whereClauseFoi.append(" AND "); whereClauseFoi.append(AccessGDBImpl.concatTableAndField(Table.NETWORK, SubField.NETWORK_ID) + " = '" + offering.getId() + "'"); // LOGGER.info("Where clause := " + queryDefFoi.getWhereClause()); LOGGER.debug(String.format("Evaluating FOI query for network: '%s'", offering.getId())); // evaluate the database query ICursor cursorFoi = retrieveCursor(tablesFoi, whereClauseFoi, subFieldsFoi); List<Point> points = new ArrayList<Point>(); while ((row = retrieveNextRow(cursorFoi)) != null) { Object shape = row.getValue(subFieldsFoi.indexOf(AccessGDBImpl.concatTableAndField(Table.FEATUREOFINTEREST, SubField.FEATUREOFINTEREST_SHAPE))); if (shape != null && shape instanceof Point) { points.add((Point) shape); } else { LOGGER.debug("Could not cast a shape in offering " + offering.getId() + " to a Point. Shape object class: "+(shape==null?"null":shape.getClass())); continue; } } if (points.size() == 0) { LOGGER.debug("No points in offering " + offering.getId()); continue; } Point[] pointArray = new Point[points.size()]; for (int j = 0; j < pointArray.length; j++) { pointArray[j] = points.get(j); } Envelope envelope = new Envelope(); envelope.defineFromPoints(pointArray); offering.setObservedArea(new AGSEnvelope(envelope)); try { retriever.retrieveOffering(offering, currentOffering); } catch (RetrievingCancelledException e) { LOGGER.warn("retrieval mechanism cancelled. stopping cache update", e); break; } } catch (ExecutionException e) { futureExecutor.shutdownNow(); futureExecutor = Executors.newSingleThreadExecutor(); LOGGER.warn("Exception caught, cancelling cache update", e); throw new IOException(e); } } } private void safetySleep(int i) { try { Thread.sleep(i); } catch (InterruptedException e) { LOGGER.warn(e.getMessage(), e); } } /** * META: Currently this method is not used. Instead {@link getNetworksAsObservationOfferings()} is used. * * This method can be used to retrieve all {@link ObservationOffering}s associated with the SOS. * Thereby, there is 1 ObservationOffering per 1 Procedure. * * @return all offerings from the Geodatabase * @throws IOException */ public synchronized Collection<ObservationOffering> getProceduresAsObservationOfferings() throws IOException { LOGGER.debug("getObservationOfferings() is called."); if (observationOfferingsCache != null) { //TODO Do we need to update this cache at some point? LOGGER.debug("Using Offerings cache."); } else { List<ObservationOffering> offerings = new ArrayList<ObservationOffering>(); // create request to get all offerings an the procedures // set tables List<String> tables = new ArrayList<String>(); tables.add(Table.PROCEDURE); // set sub fields List<String> subFields = new ArrayList<String>(); subFields.add(AccessGDBImpl.concatTableAndField(Table.PROCEDURE, SubField.PROCEDURE_ID)); // evaluate the database query ICursor cursor = DatabaseUtils.evaluateQuery(AccessGDBImpl.createCommaSeparatedList(tables), "", AccessGDBImpl.createCommaSeparatedList(subFields), gdb); // convert cursor entries to abstract observations IRow row; while ((row = cursor.nextRow()) != null) { // We will use the 'procedure identifier' also as the 'offering id' and 'offering name', since there is only one procedure per offering. // // procedure identifier String procedureIdentifier = (String) row.getValue(subFields.indexOf(AccessGDBImpl.concatTableAndField(Table.PROCEDURE, SubField.PROCEDURE_ID))); // offering name String name = procedureIdentifier; // offering id String id = procedureIdentifier; ObservationOffering offering = new ObservationOffering(id, name, null, procedureIdentifier, null, null); offerings.add(offering); } for (ObservationOffering offering : offerings) { LOGGER.debug("Working on offering (id: '" + offering.getId() + "') at index " + offerings.indexOf(offering) + " out of " + offerings.size()); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // request the timeperiod // set tables List<String> tablesTime = new ArrayList<String>(); tablesTime.add(Table.OBSERVATION); tablesTime.add(Table.VALUE); tablesTime.add(Table.PROCEDURE); // set sub fields List<String> subFieldsOff = new ArrayList<String>(); subFieldsOff.add("MIN(" + AccessGDBImpl.concatTableAndField(Table.VALUE, SubField.VALUE_DATETIME_END)+") AS MINTIME"); subFieldsOff.add("MAX(" + AccessGDBImpl.concatTableAndField(Table.VALUE, SubField.VALUE_DATETIME_END)+") AS MAXTIME"); // create where clause with joins and constraints StringBuilder whereClauseTime = new StringBuilder(); whereClauseTime.append(AccessGDBImpl.concatTableAndField(Table.OBSERVATION, SubField.OBSERVATION_FK_PROCEDURE) + " = " + AccessGDBImpl.concatTableAndField(Table.PROCEDURE, SubField.PROCEDURE_PK_PROCEDURE)); whereClauseTime.append(" AND "); whereClauseTime.append(AccessGDBImpl.concatTableAndField(Table.PROCEDURE, SubField.PROCEDURE_ID) + " = '" + offering.getId() + "'"); // LOGGER.info("Where clause := " + queryDefTime.getWhereClause()); ICursor cursorOffering = DatabaseUtils.evaluateQuery(AccessGDBImpl.createCommaSeparatedList(tablesTime), whereClauseTime.toString(), AccessGDBImpl.createCommaSeparatedList(subFieldsOff), gdb); IRow nextRow = cursorOffering.nextRow(); Object startValue = nextRow.getValue(0); Object endValue = nextRow.getValue(1); boolean noObservationsForOffering = false; if (startValue == null || endValue == null) { noObservationsForOffering = true; } else { // LOGGER.info("start time: " + startValue); // LOGGER.info("end time: " + endValue); // start time stamp ITimePosition startTime = AccessGDBImpl.createTimePosition(startValue); // end time stamp ITimePosition endTime = AccessGDBImpl.createTimePosition(endValue); // add time extent to offering if (startTime != null && endTime != null) { offering.setTimeExtent(new TimePeriod(startTime, endTime)); } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // set observed property if (noObservationsForOffering == false) { // set tables List<String> tablesProp = new ArrayList<String>(); tablesProp.add(Table.OBSERVATION); tablesProp.add(Table.PROPERTY); tablesProp.add(Table.PROCEDURE); // LOGGER.info("Tables clause := " + queryDefProp.getTables()); // set sub fields List<String> subFieldsProp = new ArrayList<String>(); subFieldsProp.add(AccessGDBImpl.concatTableAndField(Table.PROPERTY, SubField.PROPERTY_ID)); // LOGGER.info("Subfields clause := " + queryDefProp.getSubFields()); // create where clause with joins and constraints StringBuilder whereClauseProp = new StringBuilder(); whereClauseProp.append(AccessGDBImpl.concatTableAndField(Table.OBSERVATION, SubField.OBSERVATION_FK_PROPERTY) + " = " + AccessGDBImpl.concatTableAndField(Table.PROPERTY, SubField.PROPERTY_PK_PROPERTY)); whereClauseProp.append(" AND "); whereClauseProp.append(AccessGDBImpl.concatTableAndField(Table.OBSERVATION, SubField.OBSERVATION_FK_PROCEDURE) + " = " + AccessGDBImpl.concatTableAndField(Table.PROCEDURE, SubField.PROCEDURE_PK_PROCEDURE)); whereClauseProp.append(" AND "); whereClauseProp.append(AccessGDBImpl.concatTableAndField(Table.PROCEDURE, SubField.PROCEDURE_ID) + " = '" + offering.getId() + "'"); // LOGGER.info("Where clause := " + queryDefProp.getWhereClause()); // evaluate the database query ICursor cursorProp = DatabaseUtils.evaluateQuery(AccessGDBImpl.createCommaSeparatedList(tablesProp), whereClauseProp.toString(), AccessGDBImpl.createCommaSeparatedList(subFieldsProp), gdb); List<String> obsProps = new ArrayList<String>(); while ((row = cursorProp.nextRow()) != null) { String obsPropID = (String) row.getValue(subFieldsProp.indexOf(AccessGDBImpl.concatTableAndField(Table.PROPERTY, SubField.PROPERTY_ID))); obsProps.add(obsPropID); } // copy obsProps list to String Array: String[] obsPropsArray = new String[obsProps.size()]; int i=0; for (Iterator<String> iterator = obsProps.iterator(); iterator.hasNext();) { obsPropsArray[i] = (String) iterator.next(); i++; } offering.setObservedProperties(obsPropsArray); } // no observations associated with this offering/procedure yet, so an empty String array is attached: else { offering.setObservedProperties(new String[0]); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // set envelope through feature positions if (noObservationsForOffering == false) { // set tables List<String> tablesFoi = new ArrayList<String>(); tablesFoi.add(Table.OBSERVATION); tablesFoi.add(Table.FEATUREOFINTEREST); tablesFoi.add(Table.PROCEDURE); // set sub fields List<String> subFieldsFoi = new ArrayList<String>(); subFieldsFoi.add(AccessGDBImpl.concatTableAndField(Table.FEATUREOFINTEREST, SubField.FEATUREOFINTEREST_SHAPE)); // create the where clause with joins and constraints StringBuilder whereClauseFoi = new StringBuilder(); whereClauseFoi.append(AccessGDBImpl.concatTableAndField(Table.OBSERVATION, SubField.OBSERVATION_FK_FEATUREOFINTEREST) + " = " + AccessGDBImpl.concatTableAndField(Table.FEATUREOFINTEREST, SubField.FEATUREOFINTEREST_PK_FEATUREOFINTEREST)); whereClauseFoi.append(" AND "); whereClauseFoi.append(AccessGDBImpl.concatTableAndField(Table.OBSERVATION, SubField.OBSERVATION_FK_PROCEDURE) + " = " + AccessGDBImpl.concatTableAndField(Table.PROCEDURE, SubField.PROCEDURE_PK_PROCEDURE)); whereClauseFoi.append(" AND "); whereClauseFoi.append(AccessGDBImpl.concatTableAndField(Table.PROCEDURE, SubField.PROCEDURE_ID) + " = '" + offering.getId() + "'"); // LOGGER.info("Where clause := " + queryDefFoi.getWhereClause()); // evaluate the database query ICursor cursorFoi = DatabaseUtils.evaluateQuery(AccessGDBImpl.createCommaSeparatedList(tablesFoi), whereClauseFoi.toString(), AccessGDBImpl.createCommaSeparatedList(subFieldsFoi), gdb); List<Point> points = new ArrayList<Point>(); while ((row = cursorFoi.nextRow()) != null) { Object shape = row.getValue(subFieldsFoi.indexOf(AccessGDBImpl.concatTableAndField(Table.FEATUREOFINTEREST, SubField.FEATUREOFINTEREST_SHAPE))); if (shape instanceof Point) { points.add((Point) shape); } else { throw new IllegalArgumentException("Could not cast a shape in offering " + offering.getId() + " to a Point"); } } Point[] pointArray = new Point[points.size()]; for (int i = 0; i < pointArray.length; i++) { pointArray[i] = points.get(i); } Envelope envelope = new Envelope(); envelope.defineFromPoints(pointArray); offering.setObservedArea(new AGSEnvelope(envelope)); } // no observations associated with this offering/procedure yet, so an empty envelope is attached: else { Envelope envelope = new Envelope(); offering.setObservedArea(new AGSEnvelope(envelope)); } } observationOfferingsCache = offerings; } return observationOfferingsCache; } @Override public Collection<ObservationOffering> getNetworksAsObservationOfferings() throws IOException { final List<ObservationOffering> result = new ArrayList<>(); OnOfferingRetrieved retriever = new OnOfferingRetrieved() { @Override public void retrieveOffering(ObservationOffering oo, int currentOffering) { result.add(oo); } @Override public void retrieveExpectedOfferingsCount(int count) { } }; getNetworksAsObservationOfferingsAsync(retriever); LOGGER.info("Networks with observations: "+result.size()); return result; } private ICursor retrieveCursor(final List<String> tablesTime, final StringBuilder whereClauseTime, final List<String> subFieldsTime) throws ExecutionException { FutureTask<ICursor> curFuture = new FutureTask<>(new Callable<ICursor>() { @Override public ICursor call() throws Exception { ICursor cursorOffering = DatabaseUtils.evaluateQuery(AccessGDBImpl.createCommaSeparatedList(tablesTime), whereClauseTime.toString(), AccessGDBImpl.createCommaSeparatedList(subFieldsTime), gdb); return cursorOffering; } }); futureExecutor.execute(curFuture); final ICursor cur; try { cur = curFuture.get(2, TimeUnit.MINUTES); } catch (InterruptedException | ExecutionException | TimeoutException e) { LOGGER.severe("Timeout at cursor retrieval", e); throw new ExecutionException(e); } if (cur == null) { throw new ExecutionException("Future Result was null", new NullPointerException()); } return cur; } private IRow retrieveNextRow(final ICursor cur) throws ExecutionException { FutureTask<IRow> nextRowFuture = new FutureTask<>(new Callable<IRow>() { @Override public IRow call() throws Exception { return cur.nextRow(); } }); futureExecutor.execute(nextRowFuture); IRow nextRow; try { nextRow = nextRowFuture.get(2, TimeUnit.MINUTES); } catch (InterruptedException | ExecutionException | TimeoutException e) { LOGGER.severe("Timeout at cursor next row retrieval", e); throw new ExecutionException(e); } return nextRow; } }