    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    }