View Javadoc

1   package nl.geozet.wfs;
2   
3   import static nl.geozet.common.NumberConstants.DEFAULT_MAX_FEATURES;
4   import static nl.geozet.common.NumberConstants.OPENLS_ZOOMSCALE_STANDAARD;
5   import static nl.geozet.common.StringConstants.AFSTAND_NAAM;
6   import static nl.geozet.common.StringConstants.CONFIG_PARAM_WFS_CAPABILITIES_URL;
7   import static nl.geozet.common.StringConstants.CONFIG_PARAM_WFS_TYPENAME;
8   import static nl.geozet.common.StringConstants.FEATURE_ATTR_NAAM_CATEGORIE;
9   import static nl.geozet.common.StringConstants.FEATURE_ATTR_NAAM_ONDERWERP;
10  import static nl.geozet.common.StringConstants.FEATURE_ATTR_NAAM_STRAAT;
11  import static nl.geozet.common.StringConstants.FEATURE_ATTR_NAAM_TITEL;
12  import static nl.geozet.common.StringConstants.FILTER_CATEGORIE_NAAM;
13  import static nl.geozet.common.StringConstants.REQ_PARAM_EXPLICITUSEFILTER;
14  import static nl.geozet.common.StringConstants.REQ_PARAM_FID;
15  import static nl.geozet.common.StringConstants.REQ_PARAM_FILTER;
16  import static nl.geozet.common.StringConstants.REQ_PARAM_GEVONDEN;
17  import static nl.geozet.common.StringConstants.REQ_PARAM_PAGEOFFSET;
18  import static nl.geozet.common.StringConstants.REQ_PARAM_STRAAL;
19  import static nl.geozet.common.StringConstants.REQ_PARAM_XCOORD;
20  import static nl.geozet.common.StringConstants.REQ_PARAM_YCOORD;
21  import static nl.geozet.common.StringConstants.SERVLETCONFIG_WFS_MAXFEATURES;
22  import static nl.geozet.common.StringConstants.SERVLETCONFIG_WFS_TIMEOUT;
23  
24  import java.io.IOException;
25  import java.io.PrintWriter;
26  import java.text.DecimalFormat;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.Map;
30  import java.util.Vector;
31  
32  import javax.servlet.RequestDispatcher;
33  import javax.servlet.ServletConfig;
34  import javax.servlet.ServletException;
35  import javax.servlet.http.HttpServletRequest;
36  import javax.servlet.http.HttpServletResponse;
37  
38  import nl.geozet.common.AfstandComparator;
39  import nl.geozet.common.ServletBase;
40  
41  import org.apache.log4j.Logger;
42  import org.geotools.data.DataStore;
43  import org.geotools.data.DataStoreFinder;
44  import org.geotools.data.Query;
45  import org.geotools.data.simple.SimpleFeatureCollection;
46  import org.geotools.data.simple.SimpleFeatureIterator;
47  import org.geotools.data.simple.SimpleFeatureSource;
48  import org.geotools.data.wfs.WFSDataStoreFactory;
49  import org.geotools.factory.Hints;
50  import org.geotools.filter.text.cql2.CQL;
51  import org.geotools.filter.text.cql2.CQLException;
52  import org.geotools.geometry.jts.JTSFactoryFinder;
53  import org.geotools.referencing.CRS;
54  import org.opengis.feature.simple.SimpleFeature;
55  import org.opengis.feature.simple.SimpleFeatureType;
56  import org.opengis.filter.Filter;
57  import org.opengis.referencing.FactoryException;
58  import org.opengis.referencing.NoSuchAuthorityCodeException;
59  
60  import com.vividsolutions.jts.geom.Coordinate;
61  import com.vividsolutions.jts.geom.Geometry;
62  import com.vividsolutions.jts.geom.GeometryFactory;
63  import com.vividsolutions.jts.geom.Point;
64  
65  /**
66   * WFSClientServlet. Een WFS client voor de GEOZET Core versie viewer.
67   * 
68   * @author prinsmc@minlnv.nl
69   * @since 1.6
70   * @since GeoTools 2.7
71   * @since Servlet API 2.5
72   * @note zoeken en tonen van punt bekendmakingen
73   */
74  public class WFSClientServlet extends ServletBase {
75  
76      /** generated serialVersionUID. */
77      private static final long serialVersionUID = -1293974305859874046L;
78  
79      /** log4j logger. */
80      private static final Logger LOGGER = Logger
81              .getLogger(WFSClientServlet.class);
82  
83      /**
84       * DataStore interface van de WFS.
85       */
86      private transient DataStore data = null;
87  
88      /**
89       * connection parameters voor de bekendmakingen WFS.
90       */
91      private final Map<String, Object> connectionParameters = new HashMap<String, Object>();
92      /** Geometry factory. */
93      private GeometryFactory geometryFactory;
94  
95      /**
96       * SimpleFeatureType schema wordt uit de WFS gehaald.
97       */
98      protected transient SimpleFeatureType schema;
99  
100     /**
101      * Simple featuresource.
102      */
103     protected transient SimpleFeatureSource source;
104 
105     /** type name. */
106     protected String typeName;
107 
108     /**
109      * (non-Javadoc).
110      * 
111      * @param request
112      *            servlet request
113      * @param response
114      *            servlet response
115      * @throws ServletException
116      *             the servlet exception
117      * @throws IOException
118      *             Signals that an I/O exception has occurred.
119      * @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
120      */
121     @Override
122     protected void service(HttpServletRequest request,
123             HttpServletResponse response) throws ServletException, IOException {
124 
125         double xcoord;
126         double ycoord;
127         double straal;
128         try {
129             // request params uitlezen voor het zoeken
130             xcoord = Double
131                     .valueOf(request.getParameter(REQ_PARAM_XCOORD.code));
132             ycoord = Double
133                     .valueOf(request.getParameter(REQ_PARAM_YCOORD.code));
134             straal = Double
135                     .valueOf((null == request
136                             .getParameter(REQ_PARAM_STRAAL.code) ? OPENLS_ZOOMSCALE_STANDAARD
137                             .toString() : request
138                             .getParameter(REQ_PARAM_STRAAL.code)));
139         } catch (final NullPointerException e) {
140             LOGGER.error(
141                     "Een van de vereiste parameters werd niet in het request gevonden.",
142                     e);
143             // eventueel :
144             // LOGGER.debug("Naar de dispatcher.");
145             // RequestDispatcher rd = getServletContext().getRequestDispatcher(
146             // "/" + this._GEOZET);
147             // evt. nog een foutmelding in de request stoppen
148             // rd.forward(request, response);
149             // return;
150             throw new ServletException(
151                     "Een van de vereiste parameters werd niet in het request gevonden.",
152                     e);
153         } catch (final NumberFormatException e) {
154             LOGGER.error(
155                     "Een van de vereiste parameters kon niet geparsed worden als Double.",
156                     e);
157             // eventueel :
158             // LOGGER.debug("Naar de dispatcher.");
159             // RequestDispatcher rd = getServletContext().getRequestDispatcher(
160             // "/" + this._GEOZET);
161             // evt. nog een foutmelding in de request stoppen
162             // rd.forward(request, response);
163             // return;
164             throw new ServletException(
165                     "Een van de vereiste parameters kon niet geparsed worden als Double.",
166                     e);
167         }
168         LOGGER.debug("request params:" + xcoord + ":" + ycoord + " straal:"
169                 + straal);
170         // uitlezen subset bekendmakingen/categorieen en bewust filtergebruik,
171         // beide mogen null zijn
172         final String[] fString = request
173                 .getParameterValues(REQ_PARAM_FILTER.code);
174         final String filterUsed = request
175                 .getParameter(REQ_PARAM_EXPLICITUSEFILTER.code);
176         if ((null == fString) && (null != filterUsed)
177                 && filterUsed.equalsIgnoreCase("true")) {
178             LOGGER.debug("Er is expres gezocht met een leeg filter");
179             // als filterUsed==true en fString==null dan is er bewust een leeg
180             // filter gekozen, per definitie zijn er dan geen resultaten
181             // output resultaat als html
182             this.renderHTMLResults(request, response,
183                     new Vector<SimpleFeature>());
184         } else {
185             LOGGER.debug("Er is wordt gezocht met een filter");
186             // filter maken
187             final Filter filter = this.maakFilter(xcoord, ycoord, straal,
188                     fString);
189             // ophalen van de bekendmakingen
190             final Vector<SimpleFeature> results = this.ophalenBekendmakingen(
191                     filter, xcoord, ycoord);
192             // output resultaat als html
193             this.renderHTMLResults(request, response, results);
194         }
195         response.flushBuffer();
196     }
197 
198     /**
199      * Initilaisatie op basis van de configuratie. init params inlezen en
200      * controleren en init geotools
201      * 
202      * @see "http://docs.codehaus.org/display/GEOTDOC/WFS+Plugin"
203      * @param config
204      *            the <code>ServletConfig</code> object that contains
205      *            configutation information for this servlet
206      * @throws ServletException
207      *             if an exception occurs that interrupts the servlet's normal
208      *             operation
209      * @see javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
210      */
211     @Override
212     public void init(ServletConfig config) throws ServletException {
213         LOGGER.debug("opstarten servlet");
214         super.init(config);
215         // wfs capablities
216         final String capsUrl = this.getServletContext().getInitParameter(
217                 CONFIG_PARAM_WFS_CAPABILITIES_URL.code);
218         if (capsUrl == null) {
219             LOGGER.fatal("config param " + CONFIG_PARAM_WFS_CAPABILITIES_URL
220                     + " is null.");
221             throw new ServletException("config param "
222                     + CONFIG_PARAM_WFS_CAPABILITIES_URL + " is null.");
223         }
224         this.connectionParameters.put(
225                 "WFSDataStoreFactory:GET_CAPABILITIES_URL", capsUrl);
226         this.typeName = config.getInitParameter(CONFIG_PARAM_WFS_TYPENAME.code);
227         if (this.typeName == null) {
228             LOGGER.fatal("Config param " + CONFIG_PARAM_WFS_TYPENAME
229                     + " is null.");
230             throw new ServletException("Config param "
231                     + CONFIG_PARAM_WFS_TYPENAME + " is null.");
232         }
233         LOGGER.debug("typeName is: " + this.typeName);
234         // wfs timeout
235         final String wfsTimeout = config
236                 .getInitParameter(SERVLETCONFIG_WFS_TIMEOUT.code);
237         int timeout;
238         try {
239             timeout = Integer.valueOf(wfsTimeout);
240         } catch (final Exception e) {
241             timeout = 5;
242         }
243         LOGGER.info("WFS timeout voor servlet: " + this.getServletName()
244                 + " ingesteld op: " + timeout + " sec.");
245         this.connectionParameters.put(WFSDataStoreFactory.TIMEOUT.key,
246                 (timeout * 1000));
247         // wfs max features
248         final String wfsMaxFeat = config
249                 .getInitParameter(SERVLETCONFIG_WFS_MAXFEATURES.code);
250         int maxFeat;
251         try {
252             maxFeat = Integer.valueOf(wfsMaxFeat);
253         } catch (final Exception e) {
254             maxFeat = DEFAULT_MAX_FEATURES.intValue();
255         }
256         LOGGER.info("WFS max. features voor servlet: " + this.getServletName()
257                 + " ingesteld op: " + maxFeat + "");
258         this.connectionParameters.put(WFSDataStoreFactory.MAXFEATURES.key,
259                 maxFeat);
260         // wfs buffer size
261         this.connectionParameters.put(WFSDataStoreFactory.BUFFER_SIZE.key, 20);
262         // prefer GET voor noodgevallen
263         // this.connectionParameters.put(WFSDataStoreFactory.PROTOCOL.key,
264         // Boolean.FALSE);
265 
266         // verbinding met de wfs server maken
267         try {
268             this.data = DataStoreFinder.getDataStore(this.connectionParameters);
269             this.schema = this.data.getSchema(this.typeName);
270             LOGGER.debug("Schema Attributen: "
271                     + this.schema.getAttributeCount());
272             this.source = this.data.getFeatureSource(this.typeName);
273             LOGGER.debug("Metadata bounds: " + this.source.getBounds());
274 
275             final Hints hints = new Hints(Hints.CRS, CRS.decode("EPSG:28992"));
276             this.geometryFactory = JTSFactoryFinder.getGeometryFactory(hints);
277         } catch (final IOException e) {
278             LOGGER.fatal(
279                     "Verbinding met de WFS is mislukt. Controleer de configuratie en herstart de applicatie.",
280                     e);
281             throw new ServletException(
282                     "Verbinding met de WFS server is mislukt.", e);
283         } catch (final NoSuchAuthorityCodeException e) {
284             LOGGER.fatal("De gevraagde CRS autoriteit is niet gevonden.", e);
285             throw new ServletException(
286                     "De gevraagde CRS autoriteit is niet gevonden.", e);
287         } catch (final FactoryException e) {
288             LOGGER.fatal(
289                     "Gevraagde GeoTools factory voor CRS is niet gevonden.", e);
290             throw new ServletException(
291                     "Gevraagde GeoTools factory voor CRS is niet gevonden.", e);
292         }
293         LOGGER.debug("schema info: " + this.schema);
294     }
295 
296     /**
297      * Opruimen en sluiten van verbindingen.
298      * 
299      * @see javax.servlet.GenericServlet#destroy()
300      */
301     @Override
302     public void destroy() {
303         this.data.dispose();
304         this.data = null;
305         this.schema = null;
306         this.source = null;
307         this.geometryFactory = null;
308     }
309 
310     /**
311      * Maak zoek filter.
312      * 
313      * @param xcoord
314      *            x coordinaat van het de zoeklocatie, niet null
315      * @param ycoord
316      *            y coordinaat van het de zoeklocatie, niet null
317      * @param straal
318      *            de straal, in meter, waarbinnen we bekendmakingen gaan
319      *            ophalen, niet null
320      * @param categorieen
321      *            lijst met categorieën van bekendmakingen, mag null zijn
322      * @return het aangemaakte WFS filter
323      * @throws ServletException
324      *             servlet exception wordt geworpen als er een fout optreed in
325      *             het maken van het {@code Filter} object
326      */
327     private Filter maakFilter(double xcoord, double ycoord, double straal,
328             String[] categorieen) throws ServletException {
329 
330         Filter filter;
331         final StringBuilder filterString = new StringBuilder();
332         // cirkel filter maken
333         filterString.append("DWITHIN("
334                 + this.schema.getGeometryDescriptor().getLocalName()
335                 + ", POINT(" + xcoord + " " + ycoord + "), " + straal
336                 /*
337                  * LET OP: door een bug in GeoTools/GeoServer is de afstand
338                  * uiteindelijk altijd in mapping units.
339                  */
340                 + ", meters)");
341         // uitbreiden van filter met categorieen bekendmakingen
342         if (categorieen != null) {
343             filterString.append(" AND (");
344             for (int i = 0; i < categorieen.length; i++) {
345                 filterString.append(FILTER_CATEGORIE_NAAM + "='");
346                 filterString.append(categorieen[i]);
347                 filterString.append("'");
348                 if (i < categorieen.length - 1) {
349                     filterString.append(" OR ");
350                 }
351             }
352             filterString.append(")");
353         }
354         LOGGER.debug("CQL voor filter is: " + filterString);
355 
356         try {
357             filter = CQL.toFilter(filterString.toString());
358         } catch (final CQLException e) {
359             LOGGER.error("CQL Fout in de query voor de WFS.", e);
360             throw new ServletException("CQL Fout in de query voor de WFS.", e);
361         }
362         return filter;
363     }
364 
365     /**
366      * Ophalen bekendmakingen bij de WFS.
367      * 
368      * @param filter
369      *            the filter
370      * @param xcoord
371      *            x coordinaat van het de zoeklocatie
372      * @param ycoord
373      *            y coordinaat van het de zoeklocatie
374      * @return Een vector met daarin de bekendmakingen gesorteerd op en
375      *         aangerijkt met de afstand naar het zoekpunt
376      * @throws ServletException
377      *             the servlet exception
378      * @throws IOException
379      *             Signals that an I/O exception has occurred.
380      */
381     protected Vector<SimpleFeature> ophalenBekendmakingen(Filter filter,
382             double xcoord, double ycoord) throws ServletException, IOException {
383         // query maken
384         final Query query = new Query();
385         try {
386             query.setCoordinateSystem(CRS.decode("EPSG:28992"));
387             query.setTypeName(this.typeName);
388             query.setFilter(filter);
389             query.setPropertyNames(Query.ALL_NAMES);
390             query.setHandle("GEOZET-handle");
391         } catch (final NoSuchAuthorityCodeException e) {
392             LOGGER.fatal("De gevraagde CRS autoriteit is niet gevonden.", e);
393             throw new ServletException(
394                     "De gevraagde CRS autoriteit is niet gevonden.", e);
395         } catch (final FactoryException e) {
396             LOGGER.fatal(
397                     "Gevraagde GeoTools factory voor CRS is niet gevonden.", e);
398             throw new ServletException(
399                     "Gevraagde GeoTools factory voor CRS is niet gevonden.", e);
400         }
401 
402         // data ophalen
403         final SimpleFeatureCollection features = this.source.getFeatures(query);
404         LOGGER.debug("Er zijn " + features.size() + " features opgehaald.");
405 
406         // zoekpunt maken voor afstandberekening
407         final Point p = this.geometryFactory.createPoint(new Coordinate(xcoord,
408                 ycoord));
409 
410         double afstand = -1d;
411         final Vector<SimpleFeature> bekendmakingen = new Vector<SimpleFeature>();
412 
413         final SimpleFeatureIterator iterator = features.features();
414         try {
415             while (iterator.hasNext()) {
416                 // voor iedere feature de afstand bepalen tussen p en geometrie
417                 // van de feature
418                 final SimpleFeature feature = iterator.next();
419                 LOGGER.debug("Opgehaalde feature: " + feature);
420                 afstand = p.distance((Geometry) feature
421                         .getDefaultGeometryProperty().getValue());
422                 feature.getUserData().put(AFSTAND_NAAM, afstand);
423                 bekendmakingen.add(feature);
424             }
425         } finally {
426             iterator.close();
427         }
428         // sorteren op afstand
429         Collections.sort(bekendmakingen, new AfstandComparator());
430         LOGGER.debug("Er zijn " + bekendmakingen.size()
431                 + " features gesorteerd.");
432         return bekendmakingen;
433     }
434 
435     /**
436      * Renderen van de features in html formaat.
437      * 
438      * @param request
439      *            servlet request
440      * @param response
441      *            servlet response
442      * @param results
443      *            vector met SimpleFeature objecten aangereijkt met afstand in
444      *            de userdata
445      * @throws IOException
446      *             als er een schrijffout optreedt
447      * @throws ServletException
448      *             the servlet exception
449      */
450     protected void renderHTMLResults(HttpServletRequest request,
451             HttpServletResponse response, Vector<SimpleFeature> results)
452             throws IOException, ServletException {
453         // response headers instellen
454         response.setContentType("text/html; charset=UTF-8");
455         response.setBufferSize(8192);
456 
457         // header inhaken
458         final RequestDispatcher header = this.getServletContext()
459                 .getRequestDispatcher("/WEB-INF/zoekresultaat_begin.jsp");
460         if (header != null) {
461             header.include(request, response);
462         }
463 
464         final StringBuilder sb = new StringBuilder();
465         sb.append(this._RESOURCES.getString("KEY_BEKENDMAKINGEN_TITEL"));
466         sb.append("<dl class=\"geozetResults\">");
467 
468         sb.append("<dt class=\"first\">")
469                 .append(this._RESOURCES
470                         .getString("KEY_BEKENDMAKINGEN_GEVONDEN"))
471                 .append("</dt>");
472 
473         sb.append("<dd>")
474                 .append(results.size())
475                 .append(" ")
476                 .append(this._RESOURCES
477                         .getString("KEY_BEKENDMAKINGEN_RESULTATEN"))
478                 .append("</dd>");
479 
480         sb.append("<dt>")
481                 .append(this._RESOURCES.getString("KEY_BEKENDMAKINGEN_GEZOCHT"))
482                 .append(" ").append("</dt>");
483         sb.append("<dd>").append(request.getParameter(REQ_PARAM_GEVONDEN.code))
484                 .append("</dd>");
485         sb.append("</dl>");
486 
487         if (results.size() < 1) {
488             sb.append("<p>")
489                     .append(this._RESOURCES
490                             .getString("KEY_BEKENDMAKINGEN_NIETSGEVONDEN"))
491                     .append("</p>\n");
492         }
493 
494         final String paginering = this.buildPageList(results.size(), request);
495         sb.append(paginering);
496         if (results.size() > 0) {
497             // geen lege OL
498             sb.append("<ol id=\"geozetResultsList\">");
499         }
500 
501         final DecimalFormat fmtKilometer = new DecimalFormat("#.#");
502         final DecimalFormat fmtMeter = new DecimalFormat("#");
503 
504         // String qString = request.getQueryString();
505         final String qString = this.buildQueryString(request, "");
506 
507         // uitlezen offset == begin van lijst, doorgaan tot offset +
508         // ctxPageItems of max results
509         int currentOffset;
510         try {
511             currentOffset = Integer.parseInt(request
512                     .getParameter(REQ_PARAM_PAGEOFFSET.code));
513         } catch (final NumberFormatException e) {
514             currentOffset = 0;
515         }
516         final int renderItems = (currentOffset + this.itemsPerPage > results
517                 .size() ? results.size() : currentOffset + this.itemsPerPage);
518 
519         for (int index = currentOffset; index < renderItems; index++) {
520             final SimpleFeature f = results.get(index);
521             sb.append("<li>");
522             sb.append("<a href=\"").append(this._BEKENDMAKINGDETAIL)
523                     .append("?").append(REQ_PARAM_FID).append("=")
524                     .append(f.getID()).append(qString).append("\">");
525             sb.append("<strong>")
526                     .append(this.featureAttribuutCheck(f
527                             .getAttribute(FEATURE_ATTR_NAAM_TITEL.code)))
528                     .append("</strong>");
529             sb.append("</a>");
530             sb.append(this.featureAttribuutCheck(f
531                     .getAttribute(FEATURE_ATTR_NAAM_ONDERWERP.code)));
532             sb.append(" (")
533                     .append(this.featureAttribuutCheck(f
534                             .getAttribute(FEATURE_ATTR_NAAM_CATEGORIE.code)))
535                     .append(")");
536             // adres
537             sb.append("<span>locatie: ").append(
538                     this.featureAttribuutCheck(f
539                             .getAttribute(FEATURE_ATTR_NAAM_STRAAT.code)));
540             // plaatsnaam??
541             // sb.append(", ").append(
542             // this.featureAttribuutCheck(this.featureAttribuutCheck(f
543             // .getAttribute(FEATURE_ATTR_NAAM_PLAATS.code))));
544 
545             // afstand tussen haakjes
546             // LET OP: dit gaat ervan uit dat de eenheid voor de dataset meter
547             // is
548             final double afstand = Double.valueOf(f.getUserData()
549                     .get(AFSTAND_NAAM).toString());
550             if (afstand >= 1000) {
551                 /* afstand is een kilometer of meer */
552                 sb.append(" (").append(fmtKilometer.format(afstand / 1000))
553                         .append(" km)");
554             } else {
555                 sb.append(" (").append(fmtMeter.format(afstand)).append(" m)");
556             }
557             sb.append("</span>");
558             sb.append("</li>");
559 
560         }
561         if (results.size() > 0) {
562             // geen lege OL
563             sb.append("</ol>");
564         }
565         sb.append(paginering);
566 
567         // vlakgericht link
568         sb.append(this._RESOURCES.getString("KEY_BEKENDMAKINGEN_OVERIG_TITEL"));
569         sb.append("<p><a href=\"");
570         sb.append(this._BEKENDMAKINGENVLAK)
571                 .append("?")
572                 .append(this.buildQueryString(request,
573                         REQ_PARAM_PAGEOFFSET.code));
574         sb.append("\">");
575         sb.append(this._RESOURCES.getString("KEY_BEKENDMAKINGEN_OVERIG"));
576         sb.append("</a></p>");
577 
578         final PrintWriter out = response.getWriter();
579         out.print(sb);
580         out.flush();
581 
582         // footer aanhaken
583         final RequestDispatcher footer = this.getServletContext()
584                 .getRequestDispatcher("/WEB-INF/zoekresultaat_einde.jsp");
585         if (footer != null) {
586             footer.include(request, response);
587         }
588     }
589 }