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    }