001: //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/ogcwebservices/wfs/GetFeatureHandler.java $
002: /*---------------- FILE HEADER ------------------------------------------
003:
004: This file is part of deegree.
005: Copyright (C) 2001-2008 by:
006: EXSE, Department of Geography, University of Bonn
007: http://www.giub.uni-bonn.de/deegree/
008: lat/lon GmbH
009: http://www.lat-lon.de
010:
011: This library is free software; you can redistribute it and/or
012: modify it under the terms of the GNU Lesser General Public
013: License as published by the Free Software Foundation; either
014: version 2.1 of the License, or (at your option) any later version.
015:
016: This library is distributed in the hope that it will be useful,
017: but WITHOUT ANY WARRANTY; without even the implied warranty of
018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019: Lesser General Public License for more details.
020:
021: You should have received a copy of the GNU Lesser General Public
022: License along with this library; if not, write to the Free Software
023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024:
025: Contact:
026:
027: Andreas Poth
028: lat/lon GmbH
029: Aennchenstr. 19
030: 53177 Bonn
031: Germany
032: E-Mail: poth@lat-lon.de
033:
034: Prof. Dr. Klaus Greve
035: Department of Geography
036: University of Bonn
037: Meckenheimer Allee 166
038: 53115 Bonn
039: Germany
040: E-Mail: greve@giub.uni-bonn.de
041:
042: ---------------------------------------------------------------------------*/
043: package org.deegree.ogcwebservices.wfs;
044:
045: import java.util.ArrayList;
046: import java.util.List;
047: import java.util.concurrent.Callable;
048: import java.util.concurrent.CancellationException;
049:
050: import org.deegree.datatypes.QualifiedName;
051: import org.deegree.framework.concurrent.ExecutionFinishedEvent;
052: import org.deegree.framework.concurrent.Executor;
053: import org.deegree.framework.log.ILogger;
054: import org.deegree.framework.log.LoggerFactory;
055: import org.deegree.i18n.Messages;
056: import org.deegree.io.datastore.Datastore;
057: import org.deegree.io.datastore.schema.MappedFeatureType;
058: import org.deegree.model.feature.FeatureCollection;
059: import org.deegree.model.feature.GMLFeatureAdapter;
060: import org.deegree.model.feature.GMLFeatureCollectionDocument;
061: import org.deegree.ogcwebservices.OGCWebServiceException;
062: import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities;
063: import org.deegree.ogcwebservices.wfs.capabilities.WFSFeatureType;
064: import org.deegree.ogcwebservices.wfs.capabilities.WFSOperationsMetadata;
065: import org.deegree.ogcwebservices.wfs.configuration.WFSConfiguration;
066: import org.deegree.ogcwebservices.wfs.operation.FeatureResult;
067: import org.deegree.ogcwebservices.wfs.operation.GetFeature;
068: import org.deegree.ogcwebservices.wfs.operation.Query;
069: import org.deegree.ogcwebservices.wfs.operation.GetFeature.RESULT_TYPE;
070: import org.deegree.owscommon.OWSDomainType;
071:
072: /**
073: * Handles {@link GetFeature} requests to the {@link WFService}. Since a {@link GetFeature} request
074: * may contain more than one {@link Query}, each {@link Query} is delegated to an own thread.
075: * <p>
076: * The results of all threads are collected and merged before they are returned to the calling
077: * {@link WFService} as a single {@link FeatureCollection}.
078: *
079: * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
080: * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
081: * @author last edited by: $Author: apoth $
082: *
083: * @version $Revision: 9345 $, $Date: 2007-12-27 08:22:25 -0800 (Thu, 27 Dec 2007) $
084: */
085: class GetFeatureHandler {
086:
087: private static final ILogger LOG = LoggerFactory
088: .getLogger(GetFeatureHandler.class);
089:
090: private static final String EPSG_URL = "http://www.opengis.net/gml/srs/epsg.xml#";
091:
092: // upper limit for timeout (overrides WFS configuration)
093: private static long MAX_TIMEOUT_MILLIS = 60 * 60 * 1000;
094:
095: private WFService wfs;
096:
097: private int maxFeatures = -1;
098:
099: /**
100: * Creates a new instance of <code>GetFeatureHandler</code>. Only called by the associated
101: * {@link WFService} (once).
102: *
103: * @param wfs
104: * associated WFService
105: */
106: GetFeatureHandler(WFService wfs) {
107: this .wfs = wfs;
108: WFSCapabilities capa = wfs.getCapabilities();
109: WFSOperationsMetadata md = (WFSOperationsMetadata) capa
110: .getOperationsMetadata();
111: OWSDomainType[] dt = md.getConstraints();
112: for (int i = 0; i < dt.length; i++) {
113: if (dt[i].getName().equals("DefaultMaxFeatures")) {
114: try {
115: String tmp = dt[i].getValues()[0];
116: this .maxFeatures = Integer.parseInt(tmp);
117: } catch (Exception e) {
118: // e.printStackTrace();
119: }
120: break;
121: }
122: }
123: LOG.logDebug("default maxFeatures " + this .maxFeatures);
124: }
125:
126: /**
127: * Handles a {@link GetFeature} request by delegating the contained {@link Query} objects to
128: * different threads.
129: * <p>
130: * If at least one query fails an exception will be thrown and all running threads will be
131: * stopped.
132: *
133: * @param getFeature
134: * @return result of the request
135: * @throws OGCWebServiceException
136: */
137: FeatureResult handleRequest(GetFeature getFeature)
138: throws OGCWebServiceException {
139:
140: if (getFeature.getMaxFeatures() > this .maxFeatures
141: || getFeature.getMaxFeatures() <= 0) {
142: getFeature.setMaxFeatures(this .maxFeatures);
143: }
144:
145: LOG.logDebug("maxFeatures " + getFeature.getMaxFeatures());
146:
147: Query[] queries = getFeature.getQuery();
148: List<Callable<FeatureCollection>> queryTasks = new ArrayList<Callable<FeatureCollection>>(
149: queries.length);
150:
151: for (Query query : queries) {
152:
153: QualifiedName[] ftNames = query.getTypeNames();
154: MappedFeatureType[] requestedFts = new MappedFeatureType[ftNames.length];
155: Datastore ds = null;
156:
157: for (int i = 0; i < ftNames.length; i++) {
158: QualifiedName ftName = ftNames[i];
159: MappedFeatureType ft = this .wfs
160: .getMappedFeatureType(ftName);
161:
162: if (ft == null) {
163: String msg = Messages.getMessage(
164: "WFS_FEATURE_TYPE_UNKNOWN", ftName);
165: throw new OGCWebServiceException(this .getClass()
166: .getName(), msg);
167: }
168: if (ft.isAbstract()) {
169: String msg = Messages.getMessage(
170: "WFS_FEATURE_TYPE_ABSTRACT", ftName);
171: throw new OGCWebServiceException(this .getClass()
172: .getName(), msg);
173: }
174: if (!ft.isVisible()) {
175: String msg = Messages.getMessage(
176: "WFS_FEATURE_TYPE_INVISIBLE", ftName);
177: throw new OGCWebServiceException(this .getClass()
178: .getName(), msg);
179: }
180: Datastore dsForFt = ft.getGMLSchema().getDatastore();
181: if (ds != null) {
182: if (ds != dsForFt) {
183: String msg = Messages
184: .getMessage("WFS_QUERY_JOIN_OVER_DIFFERENT_DS");
185: throw new OGCWebServiceException(this
186: .getClass().getName(), msg);
187: }
188: } else {
189: ds = dsForFt;
190: }
191: requestedFts[i] = ft;
192: }
193:
194: // check and normalize requested SRS
195: // TODO what about joins here?
196: String srsName = query.getSrsName();
197: if (srsName != null) {
198: WFSFeatureType wfsFT = this .wfs.getCapabilities()
199: .getFeatureTypeList()
200: .getFeatureType(ftNames[0]);
201: String normalizedSrsName = normalizeSrsName(srsName);
202: query.setSrsName(normalizedSrsName);
203:
204: if (!(wfsFT.supportsSrs(normalizedSrsName))) {
205: String msg = Messages.getMessage(
206: "WFS_FEATURE_TYPE_SRS_UNSUPPORTED",
207: ftNames[0], srsName);
208: throw new OGCWebServiceException(this .getClass()
209: .getName(), msg);
210: }
211: }
212:
213: QueryTask task = new QueryTask(ds, query, requestedFts);
214: queryTasks.add(task);
215: }
216:
217: WFSConfiguration conf = (WFSConfiguration) wfs
218: .getCapabilities();
219: long timeout = conf.getDeegreeParams().getRequestTimeLimit() * 1000;
220: if (timeout > MAX_TIMEOUT_MILLIS) {
221: // limit max timeout
222: timeout = MAX_TIMEOUT_MILLIS;
223: }
224:
225: List<ExecutionFinishedEvent<FeatureCollection>> finishedEvents = null;
226: try {
227: finishedEvents = Executor.getInstance()
228: .performSynchronously(queryTasks, timeout);
229: } catch (InterruptedException e) {
230: String msg = "Exception occured while waiting for the GetFeature results: "
231: + e.getMessage();
232: throw new OGCWebServiceException(this .getClass().getName(),
233: msg);
234: }
235:
236: // use id of the request as id of the result feature collection
237: // to allow identification of the original request that produced
238: // the feature collection
239: FeatureCollection fc = null;
240: if (getFeature.getResultType() == RESULT_TYPE.RESULTS) {
241: fc = mergeResults(getFeature.getId(), finishedEvents);
242: } else {
243: fc = mergeHits(getFeature.getId(), finishedEvents);
244: }
245:
246: if (LOG.getLevel() == ILogger.LOG_DEBUG) {
247: try {
248: GMLFeatureAdapter ada = new GMLFeatureAdapter(false);
249: GMLFeatureCollectionDocument doc = ada.export(fc);
250: LOG.logDebugXMLFile("GetFeatureHandler_result", doc);
251: } catch (Exception e) {
252: LOG.logError(e.getMessage(), e);
253: }
254: }
255:
256: FeatureResult fr = new FeatureResult(getFeature, fc);
257: return fr;
258: }
259:
260: /**
261: * Merges the results of the request subparts into one feature collection.
262: *
263: * @param fcid
264: * id of the new (result) feature collection
265: * @param finishedEvents
266: * @return feature collection containing all features from all responses
267: * @throws OGCWebServiceException
268: */
269: private FeatureCollection mergeResults(
270: String fcid,
271: List<ExecutionFinishedEvent<FeatureCollection>> finishedEvents)
272: throws OGCWebServiceException {
273:
274: FeatureCollection result = null;
275:
276: try {
277: for (ExecutionFinishedEvent<FeatureCollection> event : finishedEvents) {
278: if (result == null) {
279: result = event.getResult();
280: } else {
281: result.addAll(event.getResult());
282: }
283: }
284: } catch (CancellationException e) {
285: LOG.logError(e.getMessage(), e);
286: String msg = Messages.getMessage("WFS_GET_FEATURE_TIMEOUT",
287: e.getMessage());
288: throw new OGCWebServiceException(this .getClass().getName(),
289: msg);
290: } catch (Throwable t) {
291: LOG.logError(t.getMessage(), t);
292: String msg = Messages.getMessage("WFS_GET_FEATURE_BACKEND",
293: t.getMessage());
294: throw new OGCWebServiceException(this .getClass().getName(),
295: msg);
296: }
297:
298: result.setId(fcid);
299: result.setAttribute("numberOfFeatures", "" + result.size());
300: return result;
301: }
302:
303: /**
304: * Merges the results of the request subparts into one feature collection.
305: * <p>
306: * This method is used if only the HITS have been requested, i.e. the number of features.
307: *
308: * TODO: Do this a better way (maybe change feature model).
309: *
310: * @param fcid
311: * id of the new (result) feature collection
312: * @param finishedEvents
313: * @return empty feature collection with "numberOfFeatures" attribute
314: * @throws OGCWebServiceException
315: */
316: private FeatureCollection mergeHits(
317: String fcid,
318: List<ExecutionFinishedEvent<FeatureCollection>> finishedEvents)
319: throws OGCWebServiceException {
320:
321: FeatureCollection result = null;
322: int numberOfFeatures = 0;
323:
324: try {
325: for (ExecutionFinishedEvent<FeatureCollection> event : finishedEvents) {
326: FeatureCollection fc = event.getResult();
327: try {
328: numberOfFeatures += Integer.parseInt((fc
329: .getAttribute("numberOfFeatures")));
330: } catch (NumberFormatException e) {
331: String msg = "Internal error. Could not parse 'numberOfFeatures' attribute "
332: + "of sub-result as an integer value.";
333: throw new OGCWebServiceException(this .getClass()
334: .getName(), msg);
335: }
336: if (result == null) {
337: result = fc;
338: } else {
339: result.addAll(fc);
340: }
341: }
342: } catch (CancellationException e) {
343: String msg = Messages.getMessage("WFS_GET_FEATURE_TIMEOUT");
344: LOG.logError(msg, e);
345: throw new OGCWebServiceException(this .getClass().getName(),
346: msg);
347: } catch (Throwable t) {
348: String msg = Messages.getMessage("WFS_GET_FEATURE_BACKEND",
349: t.getMessage());
350: LOG.logError(msg, t);
351: throw new OGCWebServiceException(this .getClass().getName(),
352: msg);
353: }
354:
355: result.setId(fcid);
356: result.setAttribute("numberOfFeatures", "" + numberOfFeatures);
357: return result;
358: }
359:
360: /**
361: * Returns a normalized version of the given srs identifier.
362: * <p>
363: * Names in the format: <code>http://www.opengis.net/gml/srs/epsg.xml#XYZ</code> are returned
364: * as <code>EPSG:XYZ</code>.
365: *
366: * @param srsName
367: * name of the srs, <code>EPSG:xyz</code>
368: * @return a normalized version of <code>srsName</code>
369: */
370: private String normalizeSrsName(String srsName) {
371: String normalizedName = srsName;
372: if (srsName.startsWith(EPSG_URL)) {
373: String epsgCode = srsName.substring(EPSG_URL.length());
374: normalizedName = "EPSG:" + epsgCode;
375: }
376: return normalizedName;
377: }
378:
379: // ///////////////////////////////////////////////////////////////////////////
380: // inner classes
381: // ///////////////////////////////////////////////////////////////////////////
382:
383: /**
384: * Inner class for performing queries on a datastore.
385: */
386: private class QueryTask implements Callable<FeatureCollection> {
387:
388: private Datastore ds;
389:
390: private Query query;
391:
392: private MappedFeatureType[] fts;
393:
394: QueryTask(Datastore ds, Query query, MappedFeatureType[] fts) {
395: this .ds = ds;
396: this .query = query;
397: this .fts = fts;
398: }
399:
400: /**
401: * Performs the associated {@link Query} and returns the result.
402: *
403: * @return resulting feature collection
404: * @throws Exception
405: */
406: public FeatureCollection call() throws Exception {
407: FeatureCollection result = this.ds.performQuery(query, fts);
408: return result;
409: }
410: }
411: }
|