001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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 }