001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/enterprise/servlet/GetMapFilter.java $
002 /*----------------------------------------------------------------------------
003 This file is part of deegree, http://deegree.org/
004 Copyright (C) 2001-2009 by:
005 Department of Geography, University of Bonn
006 and
007 lat/lon GmbH
008
009 This library is free software; you can redistribute it and/or modify it under
010 the terms of the GNU Lesser General Public License as published by the Free
011 Software Foundation; either version 2.1 of the License, or (at your option)
012 any later version.
013 This library is distributed in the hope that it will be useful, but WITHOUT
014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016 details.
017 You should have received a copy of the GNU Lesser General Public License
018 along with this library; if not, write to the Free Software Foundation, Inc.,
019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020
021 Contact information:
022
023 lat/lon GmbH
024 Aennchenstr. 19, 53177 Bonn
025 Germany
026 http://lat-lon.de/
027
028 Department of Geography, University of Bonn
029 Prof. Dr. Klaus Greve
030 Postfach 1147, 53001 Bonn
031 Germany
032 http://www.geographie.uni-bonn.de/deegree/
033
034 e-mail: info@deegree.org
035 ----------------------------------------------------------------------------*/
036
037 package org.deegree.enterprise.servlet;
038
039 import static java.awt.Color.decode;
040 import static java.awt.Color.white;
041 import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
042 import static java.util.Arrays.asList;
043 import static java.util.Collections.disjoint;
044 import static java.util.Collections.sort;
045 import static org.deegree.framework.log.LoggerFactory.getLogger;
046 import static org.deegree.framework.util.CollectionUtils.collectionToString;
047 import static org.deegree.framework.util.CollectionUtils.map;
048 import static org.deegree.framework.util.StringTools.arrayToString;
049 import static org.deegree.framework.util.WebappResourceResolver.resolveFileLocation;
050 import static org.deegree.framework.xml.XMLTools.appendElement;
051 import static org.deegree.framework.xml.XMLTools.importStringFragment;
052 import static org.deegree.model.crs.CRSFactory.create;
053 import static org.deegree.model.spatialschema.GMLGeometryAdapter.exportAsBox;
054 import static org.deegree.model.spatialschema.GeometryFactory.createEnvelope;
055 import static org.deegree.ogcbase.CommonNamespaces.OGCNS;
056 import static org.deegree.ogcbase.CommonNamespaces.WFSNS;
057 import static org.deegree.ogcbase.CommonNamespaces.getNamespaceContext;
058 import static org.deegree.ogcwebservices.OGCRequestFactory.createFromKVP;
059 import static org.deegree.ogcwebservices.wfs.WFServiceFactory.createInstance;
060 import static org.deegree.ogcwebservices.wfs.WFServiceFactory.setConfiguration;
061 import static org.deegree.ogcwebservices.wfs.operation.GetFeature.create;
062 import static org.deegree.ogcwebservices.wms.WMServiceFactory.getService;
063
064 import java.awt.Color;
065 import java.awt.Graphics2D;
066 import java.awt.Image;
067 import java.awt.image.BufferedImage;
068 import java.io.IOException;
069 import java.net.MalformedURLException;
070 import java.net.URI;
071 import java.net.URISyntaxException;
072 import java.util.Enumeration;
073 import java.util.LinkedList;
074 import java.util.List;
075 import java.util.Map;
076 import java.util.TreeMap;
077 import java.util.TreeSet;
078
079 import javax.servlet.Filter;
080 import javax.servlet.FilterChain;
081 import javax.servlet.FilterConfig;
082 import javax.servlet.ServletException;
083 import javax.servlet.ServletRequest;
084 import javax.servlet.ServletResponse;
085 import javax.servlet.http.HttpServletResponse;
086
087 import org.deegree.datatypes.QualifiedName;
088 import org.deegree.enterprise.ServiceException;
089 import org.deegree.framework.log.ILogger;
090 import org.deegree.framework.util.CollectionUtils.Mapper;
091 import org.deegree.framework.xml.InvalidConfigurationException;
092 import org.deegree.framework.xml.NamespaceContext;
093 import org.deegree.framework.xml.XMLFragment;
094 import org.deegree.graphics.sld.AbstractLayer;
095 import org.deegree.model.crs.UnknownCRSException;
096 import org.deegree.model.feature.Feature;
097 import org.deegree.model.feature.FeatureCollection;
098 import org.deegree.model.spatialschema.Envelope;
099 import org.deegree.ogcwebservices.OGCWebServiceException;
100 import org.deegree.ogcwebservices.OGCWebServiceRequest;
101 import org.deegree.ogcwebservices.wfs.WFService;
102 import org.deegree.ogcwebservices.wfs.operation.FeatureResult;
103 import org.deegree.ogcwebservices.wms.operation.GetMap;
104 import org.deegree.ogcwebservices.wms.operation.GetMapResult;
105 import org.deegree.ogcwebservices.wms.operation.GetMap.Layer;
106 import org.w3c.dom.Element;
107
108 /**
109 * <code>GetMapFilter</code>
110 *
111 * Init parameters:
112 *
113 * <ul>
114 * <li>prefix - default is app</li>
115 * <li>namespace - default is http://www.deegree.org/app</li>
116 * <li>typeName - no default</li>
117 * <li>geometryProperty - default is app:geometry</li>
118 * <li>propertyName - no default</li>
119 * <li>excludedLayers - default is empty list (no excluded layers)</li>
120 * <li>coverStyle - default is not to request cover layers</li>
121 * <li>coverColor - default is #ffffff</li>
122 * <li>onlyForSLDRequests - the cover-layer mechanism is only used for SLD requests</li>
123 * </ul>
124 *
125 * It is assumed that the WMS and its local WFS are configured in the same context as the filter. Take note that if you
126 * choose a property which occurs with null values, these features will be SKIPPED and NOT PAINTED, you will MISS THEM.
127 *
128 * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
129 * @author last edited by: $Author: mschneider $
130 *
131 * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
132 */
133 public class GetMapFilter implements Filter {
134
135 private static final ILogger LOG = getLogger( GetMapFilter.class );
136
137 private static final NamespaceContext nsContext = getNamespaceContext();
138
139 private String prefix = "app", namespace = "http://www.deegree.org/app", typeName, geomProperty = "app:geometry",
140 filterProperty, wmsFilterProperty, sortProperty, coverStyle;
141
142 private Color coverColor = white;
143
144 private boolean onlyForSLDRequests;
145
146 private QualifiedName filterPropertyQ, sortPropertyQ;
147
148 private TreeSet<String> excludedLayers;
149
150 private WFService wfs;
151
152 public void destroy() {
153 // nothing to do
154 }
155
156 private static Map<String, String> normalizeMap( ServletRequest request, ServletResponse response, FilterChain chain )
157 throws IOException, ServletException {
158 Map<?, ?> params = request.getParameterMap();
159
160 Map<String, String> map = new TreeMap<String, String>();
161
162 for ( Object key : params.keySet() ) {
163 map.put( ( (String) key ).toUpperCase(), arrayToString( (String[]) params.get( key ), ',' ) );
164 }
165
166 if ( map.size() == 0 ) {
167 chain.doFilter( request, response );
168 return null;
169 }
170
171 if ( map.get( "SERVICE" ) == null || !map.get( "SERVICE" ).equalsIgnoreCase( "wms" ) ) {
172 chain.doFilter( request, response );
173 return null;
174 }
175
176 if ( map.get( "REQUEST" ) == null || !map.get( "REQUEST" ).equalsIgnoreCase( "getmap" ) ) {
177 chain.doFilter( request, response );
178 return null;
179 }
180
181 if ( map.get( "FILTERPROPERTY" ) != null ) {
182 chain.doFilter( request, response );
183 return null;
184 }
185
186 if ( LOG.isDebug() ) {
187 LOG.logDebug( "Incoming request values", map );
188 }
189
190 return map;
191 }
192
193 public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain )
194 throws IOException, ServletException {
195
196 Map<String, String> map = normalizeMap( request, response, chain );
197
198 if ( map == null ) {
199 return;
200 }
201
202 WMSHandler handler = new WMSHandler();
203 // in case the request parsing goes wrong...
204 handler.determineExceptionFormat( null, null, null, (HttpServletResponse) response );
205
206 GetMap getMap;
207 try {
208 getMap = (GetMap) createFromKVP( new TreeMap<String, String>( map ) );
209 } catch ( OGCWebServiceException e ) {
210 handler.writeServiceExceptionReport( e );
211 LOG.logError( "Unknown error", e );
212 return;
213 }
214
215 Envelope env;
216 try {
217 env = createEnvelope( map.get( "BBOX" ), create( map.get( "SRS" ) ) );
218 } catch ( UnknownCRSException e ) {
219 // just in case...
220 LOG.logWarning( "deegree could not find the coordinate system " + map.get( "SRS" ) + "." );
221 LOG.logWarning( "Continuing without SRS. Probably the failure will come later..." );
222 env = createEnvelope( map.get( "BBOX" ), null );
223 }
224
225 List<String> values = doGetFeature( env );
226
227 TreeSet<String> lays = new TreeSet<String>();
228 lays.addAll( map( getMap.getLayers(), new Mapper<String, Layer>() {
229 public String apply( Layer u ) {
230 return u.getName();
231 }
232 } ) );
233 if ( getMap.getStyledLayerDescriptor() != null ) {
234 lays.addAll( map( getMap.getStyledLayerDescriptor().getNamedLayers(), new Mapper<String, AbstractLayer>() {
235 public String apply( AbstractLayer u ) {
236 return u.getName();
237 }
238 } ) );
239 }
240
241 if ( !disjoint( lays, excludedLayers ) ) {
242 chain.doFilter( request, response );
243 return;
244 }
245
246 if ( values == null ) {
247 // output error message?
248 chain.doFilter( request, response );
249 return;
250 }
251
252 sort( values );
253
254 // remove duplicates
255 LinkedList<String> uniq = new LinkedList<String>();
256 for ( String v : values ) {
257 if ( !uniq.contains( v ) ) {
258 uniq.add( v );
259 }
260 }
261
262 // request and combine the maps
263 try {
264 DummyRequest req = new DummyRequest( getMap );
265 handler.perform( req, (HttpServletResponse) response );
266 boolean requestCovers = coverStyle != null
267 && ( ( map.get( "SLD" ) != null || map.get( "SLD_BODY" ) != null ) || !onlyForSLDRequests );
268 GetMapResult result = renderMaps( requestCovers, getMap, doGetMaps( uniq, map, lays ) );
269
270 if ( result == null ) {
271 // probably an empty map, but before we do it by hand...
272 chain.doFilter( request, response );
273 return;
274 }
275
276 handler.setRequest( getMap );
277 handler.handleGetMapResponse( result );
278 } catch ( OGCWebServiceException e ) {
279 handler.writeServiceExceptionReport( e );
280 LOG.logError( "Unknown error", e );
281 } catch ( ServiceException e ) {
282 handler.writeServiceExceptionReport( new OGCWebServiceException( e.getLocalizedMessage() ) );
283 LOG.logError( "Unknown error", e );
284 }
285
286 }
287
288 private GetMapResult renderMaps( boolean requestCovers, GetMap req, LinkedList<Object> imgs ) {
289 if ( imgs.size() == 0 ) {
290 return null;
291 }
292
293 if ( imgs.size() == 1 ) {
294 return (GetMapResult) imgs.poll();
295 }
296
297 BufferedImage first = (BufferedImage) ( (GetMapResult) imgs.peek() ).getMap();
298
299 BufferedImage img = new BufferedImage( first.getWidth(), first.getHeight(), TYPE_INT_ARGB );
300
301 GetMapResult result = new GetMapResult( req, img );
302
303 Graphics2D g = img.createGraphics();
304
305 for ( Object i : imgs ) {
306 GetMapResult res = (GetMapResult) i;
307 if ( res.getException() != null ) {
308 result = res;
309 break;
310 }
311
312 g.drawImage( (Image) res.getMap(), 0, 0, null );
313 }
314
315 g.dispose();
316
317 LOG.logDebug( "Finished painting maps in GetMapFilter." );
318
319 if ( requestCovers ) {
320 LOG.logDebug( "Replacing colored pixels by transparent pixels." );
321 for ( int x = 0; x < img.getWidth(); ++x ) {
322 for ( int y = 0; y < img.getHeight(); ++y ) {
323 if ( coverColor.equals( new Color( img.getRGB( x, y ) ) ) ) {
324 img.setRGB( x, y, 0 );
325 }
326 }
327 }
328 }
329
330 return result;
331 }
332
333 private LinkedList<Object> doGetMaps( List<String> values, Map<String, String> map, TreeSet<String> layers )
334 throws OGCWebServiceException {
335
336 boolean requestCovers = coverStyle != null
337 && ( ( map.get( "SLD" ) != null || map.get( "SLD_BODY" ) != null ) || !onlyForSLDRequests );
338
339 if ( LOG.isDebug() ) {
340 LOG.logDebug( "The filter will request " + values.size() + " maps." );
341 LOG.logDebug( "Found values", values );
342 }
343
344 int ls = layers.size();
345 String styles = "";
346 if ( requestCovers ) {
347 for ( int i = 0; i < ls; ++i ) {
348 styles += coverStyle;
349 if ( i != ls - 1 ) {
350 styles += ",";
351 }
352 }
353 }
354
355 LinkedList<Object> imgs = new LinkedList<Object>();
356
357 TreeMap<String, String> newMap = new TreeMap<String, String>();
358
359 map.put( "FILTERPROPERTY", wmsFilterProperty );
360 for ( String val : values ) {
361 if ( requestCovers && !val.equals( values.get( 0 ) ) ) {
362 newMap.clear();
363 newMap.putAll( map );
364 newMap.remove( "FILTERVALUE" );
365 newMap.remove( "TRANSPARENT" );
366 if ( newMap.get( "LAYERS" ) == null ) {
367 if ( !layers.isEmpty() ) {
368 newMap.put( "LAYERS", collectionToString( layers, "," ) );
369 newMap.put( "STYLES", styles );
370 newMap.remove( "SLD" );
371 }
372 } else {
373 newMap.put( "STYLES", styles );
374 }
375 newMap.put( "FILTERVALUE", val );
376 newMap.put( "TRANSPARENT", "true" );
377 if ( LOG.isDebug() ) {
378 LOG.logDebug( "Requested a cover layer", newMap );
379 }
380 imgs.add( getService().doService( createFromKVP( newMap ) ) );
381 }
382 newMap.clear();
383 newMap.putAll( map );
384 newMap.remove( "FILTERVALUE" );
385 newMap.remove( "TRANSPARENT" );
386 if ( val.equals( values.get( 0 ) ) ) {
387 newMap.put( "TRANSPARENT", map.get( "TRANSPARENT" ) );
388 } else {
389 newMap.put( "TRANSPARENT", "true" );
390 }
391 newMap.put( "FILTERVALUE", val );
392 if ( LOG.isDebug() ) {
393 LOG.logDebug( "Requested a filtered layer", newMap );
394 }
395 imgs.add( getService().doService( createFromKVP( newMap ) ) );
396 }
397
398 LOG.logDebug( "Finished requesting maps in GetMapFilter." );
399
400 return imgs;
401 }
402
403 private List<String> doGetFeature( Envelope bbox ) {
404 XMLFragment doc = new XMLFragment( new QualifiedName( "wfs:GetFeature", WFSNS ) );
405
406 Element root = doc.getRootElement();
407 root.setAttribute( "version", "1.1.0" );
408 root.setAttribute( "xmlns:" + prefix, namespace );
409 Element elem = appendElement( root, WFSNS, "wfs:Query" );
410 elem.setAttribute( "typeName", typeName );
411
412 appendElement( elem, WFSNS, "wfs:PropertyName", filterProperty );
413 appendElement( elem, WFSNS, "wfs:PropertyName", sortProperty );
414 elem = appendElement( elem, OGCNS, "ogc:Filter" );
415 elem = appendElement( elem, OGCNS, "ogc:BBOX" );
416 appendElement( elem, OGCNS, "ogc:PropertyName", geomProperty );
417
418 StringBuffer str = exportAsBox( bbox );
419 Element el = importStringFragment( str.toString(), root.getOwnerDocument() );
420 el.setAttribute( "srsName", bbox.getCoordinateSystem().getIdentifier() );
421 elem.appendChild( el );
422
423 if ( LOG.isDebug() ) {
424 LOG.logDebug( "GetFeature request", doc.getAsPrettyString() );
425 }
426
427 try {
428 FeatureResult res = (FeatureResult) wfs.doService( create( "bogus", doc.getRootElement() ) );
429 FeatureCollection col = (FeatureCollection) res.getResponse();
430
431 TreeMap<String, String> values = new TreeMap<String, String>();
432
433 for ( int i = 0; i < col.size(); ++i ) {
434 Feature f = col.getFeature( i );
435 Object v1 = f.getProperties( filterPropertyQ )[0].getValue();
436 Object v2 = f.getProperties( sortPropertyQ )[0].getValue();
437 if ( v1 != null && v2 != null ) {
438 values.put( v2.toString(), v1.toString() );
439 }
440 }
441
442 LinkedList<String> r = new LinkedList<String>();
443 for ( String key : values.keySet() ) {
444 r.add( values.get( key ) );
445 }
446
447 return r;
448 } catch ( OGCWebServiceException e ) {
449 LOG.logError( "An unknown error occurred", e );
450 }
451
452 return null;
453 }
454
455 public void init( FilterConfig config )
456 throws ServletException {
457 excludedLayers = new TreeSet<String>();
458
459 try {
460 Enumeration<?> e = config.getInitParameterNames();
461 while ( e.hasMoreElements() ) {
462 String name = (String) e.nextElement();
463 String iname = name.toLowerCase();
464 if ( iname.equals( "namespace" ) ) {
465 namespace = config.getInitParameter( name );
466 }
467 if ( iname.equals( "prefix" ) ) {
468 prefix = config.getInitParameter( name );
469 }
470 if ( iname.equals( "typename" ) ) {
471 typeName = config.getInitParameter( name );
472 }
473 if ( iname.equals( "sortproperty" ) ) {
474 sortProperty = config.getInitParameter( name );
475 }
476 if ( iname.equals( "filterproperty" ) ) {
477 filterProperty = config.getInitParameter( name );
478 }
479 if ( iname.equals( "wmsfilterproperty" ) ) {
480 wmsFilterProperty = config.getInitParameter( name );
481 }
482 if ( iname.equals( "geometryproperty" ) ) {
483 geomProperty = config.getInitParameter( name );
484 }
485 if ( iname.equals( "excludelayers" ) ) {
486 excludedLayers.addAll( asList( config.getInitParameter( name ).split( "," ) ) );
487 }
488 if ( iname.equals( "coverstyle" ) ) {
489 coverStyle = config.getInitParameter( name );
490 }
491 if ( iname.equals( "covercolor" ) ) {
492 coverColor = decode( config.getInitParameter( name ) );
493 }
494 if ( iname.equals( "onlyforsldrequests" ) ) {
495 onlyForSLDRequests = config.getInitParameter( name ).equalsIgnoreCase( "true" );
496 }
497 if ( iname.equals( "wfsconfiguration" ) ) {
498 String cfg = config.getInitParameter( name );
499 setConfiguration( resolveFileLocation( cfg, config.getServletContext(), LOG ) );
500 wfs = createInstance();
501 }
502 }
503
504 URI uri = new URI( namespace );
505 nsContext.addNamespace( prefix, uri );
506 filterPropertyQ = new QualifiedName( filterProperty, uri );
507 sortPropertyQ = new QualifiedName( sortProperty, uri );
508
509 LOG.logInfo( "GetMap filter initialized." );
510 } catch ( URISyntaxException e ) {
511 LOG.logError( "Your configuration is not correct. The namespace is not an URI", e );
512 } catch ( MalformedURLException e ) {
513 LOG.logError( "Unknown error", e );
514 } catch ( InvalidConfigurationException e ) {
515 LOG.logError( "The WFS configuration was not correct", e );
516 } catch ( IOException e ) {
517 LOG.logError( "The WFS configuration could not be read", e );
518 } catch ( OGCWebServiceException e ) {
519 LOG.logError( "The WFS could not be initialized", e );
520 }
521 }
522
523 /**
524 * <code>DummyRequest</code>
525 *
526 * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
527 * @author last edited by: $Author: mschneider $
528 *
529 * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
530 */
531 public static class DummyRequest implements OGCWebServiceRequest {
532
533 private OGCWebServiceRequest req;
534
535 /**
536 * @param req
537 */
538 public DummyRequest( OGCWebServiceRequest req ) {
539 this.req = req;
540 }
541
542 public String getId() {
543 return req.getId();
544 }
545
546 public String getRequestParameter()
547 throws OGCWebServiceException {
548 return null;
549 }
550
551 public String getServiceName() {
552 return req.getServiceName();
553 }
554
555 public String getVendorSpecificParameter( String name ) {
556 return null;
557 }
558
559 public Map<String, String> getVendorSpecificParameters() {
560 return null;
561 }
562
563 public String getVersion() {
564 return req.getVersion();
565 }
566 }
567
568 }