001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/portal/portlet/modules/map/actions/portlets/FeatureInfoPortletPerform.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    package org.deegree.portal.portlet.modules.map.actions.portlets;
037    
038    import java.awt.Color;
039    import java.io.File;
040    import java.io.StringWriter;
041    import java.net.URL;
042    import java.util.ArrayList;
043    import java.util.Arrays;
044    import java.util.HashMap;
045    import java.util.List;
046    import java.util.Map;
047    
048    import javax.servlet.ServletContext;
049    import javax.servlet.http.HttpServletRequest;
050    import javax.xml.transform.Source;
051    import javax.xml.transform.dom.DOMSource;
052    import javax.xml.transform.stream.StreamResult;
053    import javax.xml.transform.stream.StreamSource;
054    
055    import org.apache.jetspeed.portal.Portlet;
056    import org.deegree.framework.log.ILogger;
057    import org.deegree.framework.log.LoggerFactory;
058    import org.deegree.framework.util.StringTools;
059    import org.deegree.framework.xml.XMLFragment;
060    import org.deegree.framework.xml.XSLTDocument;
061    import org.deegree.model.spatialschema.Envelope;
062    import org.deegree.model.spatialschema.GeometryFactory;
063    import org.deegree.model.spatialschema.Point;
064    import org.deegree.ogcwebservices.OGCWebServiceException;
065    import org.deegree.ogcwebservices.OWSUtils;
066    import org.deegree.ogcwebservices.wms.operation.GetFeatureInfo;
067    import org.deegree.ogcwebservices.wms.operation.GetMap;
068    import org.deegree.portal.PortalException;
069    import org.deegree.portal.context.Layer;
070    import org.deegree.portal.context.LayerList;
071    import org.deegree.portal.context.ViewContext;
072    import org.deegree.portal.portlet.modules.actions.IGeoPortalPortletPerform;
073    
074    /**
075     *
076     *
077     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
078     * @author last edited by: $Author: mschneider $
079     *
080     * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
081     */
082    public class FeatureInfoPortletPerform extends IGeoPortalPortletPerform {
083    
084        private static final ILogger LOG = LoggerFactory.getLogger( FeatureInfoPortletPerform.class );
085    
086        protected static String PARAM_FILAYERS = "FILAYERS";
087    
088        protected static String PARAM_LAYERS = "LAYERS";
089    
090        protected static String PARAM_X = "X";
091    
092        protected static String PARAM_Y = "Y";
093    
094        protected static String PARAM_FEATURECOUNT = "FEATURECOUNT";
095    
096        private static String SESSION_INITPARAM = "FIINITPARAM";
097    
098        protected static String INIT_PATHTOXSLT = "pathToXSLT";
099    
100        private Map<?, ?> initParams = null;
101    
102        /**
103         * @param request
104         * @param portlet
105         * @param sc
106         *            will be needed to evaluate the absolut path of the transform script(s)
107         */
108        public FeatureInfoPortletPerform( HttpServletRequest request, Portlet portlet, ServletContext sc ) {
109            super( request, portlet, sc );
110            initParams = (Map<?,?>) request.getSession().getAttribute( SESSION_INITPARAM );
111        }
112    
113        /**
114         * initializes the portlet by putting the init parameters to the users session. Even if this is
115         * not absolutly neccessary it simplifies a few things ...
116         *
117         */
118        public void init() {
119            if ( request.getSession().getAttribute( SESSION_INITPARAM ) == null ) {
120                Map<?, ?> map = portlet.getPortletConfig().getInitParameters();
121                request.getSession().setAttribute( SESSION_INITPARAM, map );
122            }
123        }
124    
125        /**
126         * performs a GetFeatureInfo/GetFeature/DescribeCoverage request depending on the layer typs the
127         * request targets. If defiend the result will be transformed by a XSLT script to get a human
128         * readable out put.
129         *
130         * @throws PortalException
131         * @throws OGCWebServiceException
132         */
133        protected void doGetFeatureInfo()
134                                throws PortalException, OGCWebServiceException {
135    
136            StringBuffer sb = new StringBuffer( 20000 );
137    
138            // a get feature info request only can be performed if a viewcontext
139            // has been initialized before by the MapWindowPortletAction
140            ViewContext vc = getCurrentViewContext( (String) initParams.get( INIT_MAPPORTLETID ) );
141            if ( vc == null ) {
142                throw new PortalException( "no valid view context available through users session" );
143            }
144    
145            if ( parameter.get( "FILAYERS_ALL" ) == null ) {
146    
147                String tmp = parameter.get( PARAM_FILAYERS );
148                if ( tmp == null || tmp.length() == 0 ) {
149                    throw new PortalException( "at least one layer/featuretype/coverage must be set" );
150                }
151    
152                String[] layers = StringTools.toArray( tmp, ",", true );
153    
154                setCurrentFILayer( layers );
155    
156                // synchronize list of visible layer and BBOX with the users view context
157                // because maybe the user had changed the visible layers before performing
158                // a GetFeatureInfo request
159                updateContext();
160    
161                Layer layer = null;
162                Layer former = null;
163                List<Layer> layerList = new ArrayList<Layer>( 50 );
164    
165                // performe a feature info request every time the hosting
166                // server changes. This is required because maybe a user has
167                // selected two or more layers hosted by different OWS
168                for ( int i = 0; i < layers.length; i++ ) {
169                    former = layer;
170                    layer = vc.getLayerList().getLayer( layers[i], null );
171                    if ( i > 0 ) {
172                        if ( layer.getServer().equals( former.getServer() ) ) {
173                            layerList.add( layer );
174                        } else {
175                            sb.append( perform( layerList, vc ) );
176                            layerList.clear();
177                            layerList.add( layer );
178                        }
179                    } else {
180                        layerList.add( layer );
181                    }
182                }
183                sb.append( perform( layerList, vc ) );
184            } else {
185                handleFeatureInfoForAllLayers( vc, sb );
186            }
187    
188            vc.getGeneral().getExtension().setMode( "FEATUREINFO" );
189    
190            setCurrentMapContext( vc, (String) initParams.get( INIT_MAPPORTLETID ) );
191    
192            // the result will be available through the forwarded request as well as
193            // through the users session (the portal frontend deciceds what behavior
194            // it should have)
195            request.setAttribute( "HTML", sb.toString() );
196            request.getSession().setAttribute( "HTML", sb.toString() );
197    
198        }
199    
200        /**
201         *
202         * @param vc
203         * @param sb
204         * @throws OGCWebServiceException
205         * @throws PortalException
206         */
207        private void handleFeatureInfoForAllLayers( ViewContext vc, StringBuffer sb )
208                                throws OGCWebServiceException, PortalException {
209            String[] layers = findCurrentFILayers();
210            // synchronize list of visible layer and BBOX with the users view context
211            // because maybe the user had changed the visible layers before performing
212            // a GetFeatureInfo request
213            updateContext();
214    
215            Layer layer = null;
216            Layer former = null;
217            List<Layer> layerList = new ArrayList<Layer>( 50 );
218    
219            // performe a feature info request every time the hosting
220            // server changes. This is required because maybe a user has
221            // selected two or more layers hosted by different OWS
222            for ( int i = 0; i < layers.length; i++ ) {
223                former = layer;
224                layer = vc.getLayerList().getLayer( layers[i], null );
225                if ( i > 0 ) {
226                    if ( layer.getServer().equals( former.getServer() ) ) {
227                        layerList.add( layer );
228                    } else {
229                        String result = perform( layerList, vc );
230                        if ( result != null ) {
231                            sb.append( perform( layerList, vc ) );
232                        }
233                        layerList.clear();
234                        layerList.add( layer );
235                    }
236                } else {
237                    layerList.add( layer );
238                }
239            }
240            String result = perform( layerList, vc );
241            if ( result != null ) {
242                sb.append( result );
243            }
244        }
245    
246        /**
247         * sets the name of the the layers that are activated for feature info requests in the uses WMC
248         *
249         * @param fiLayer
250         */
251        void setCurrentFILayer( String[] fiLayer ) {
252    
253            List<String> list = Arrays.asList( fiLayer );
254            list = new ArrayList<String>( list );
255    
256            ViewContext vc = getCurrentViewContext( (String) initParams.get( INIT_MAPPORTLETID ) );
257            LayerList layerList = vc.getLayerList();
258            Layer[] layers = layerList.getLayers();
259            for ( int i = 0; i < layers.length; i++ ) {
260                if ( list.contains( layers[i].getName() ) ) {
261                    layers[i].getExtension().setSelectedForQuery( true );
262                } else {
263                    layers[i].getExtension().setSelectedForQuery( false );
264                }
265            }
266    
267        }
268    
269        /**
270         * distributes the performance of the feature info requests depending on the requested service
271         * to a specialized method
272         *
273         * @param layerList
274         *            list of context layers provided by the same OWS
275         * @param vc
276         * @return formated feature info result
277         * @throws OGCWebServiceException
278         * @throws PortalException
279         */
280        protected String perform( List<Layer> layerList, ViewContext vc )
281                                throws OGCWebServiceException, PortalException {
282    
283            if ( layerList.size() > 0 ) {
284                Layer layer = layerList.get( 0 );
285                if ( layer.getServer().getService().indexOf( "WMS" ) > -1 ) {
286                    return performWMS( layerList, vc );
287                } else if ( layer.getServer().getService().indexOf( "WFS" ) > -1 ) {
288                    throw new PortalException( "WFS is not supported as feature info target yet!" );
289                } else if ( layer.getServer().getService().indexOf( "WCS" ) > -1 ) {
290                    throw new PortalException( "WCS is not supported as feature info target yet!" );
291                } else {
292                    throw new PortalException( "not supported service: " + layer.getServer().getService()
293                                               + " as feature info target!" );
294                }
295            }
296            return "";
297    
298        }
299    
300        /**
301         * performes a GetFeatureInfo request on a WMS and transforms the result using a XSLT script
302         * defined in the portlets init-parameters
303         *
304         * @param layerList
305         *            list of context layers provided by the same WMS
306         * @param vc
307         * @return formated get feature info result
308         */
309        private String performWMS( List<Layer> layerList, ViewContext vc )
310                                throws OGCWebServiceException, PortalException {
311    
312            GetFeatureInfo gfi = createGetFeatureInfoRequest( layerList, vc );
313            Layer layer = layerList.get( 0 );
314    
315            URL url = OWSUtils.getHTTPGetOperationURL( layer.getServer().getCapabilities(), GetFeatureInfo.class );
316            String href = OWSUtils.validateHTTPGetBaseURL( url.toExternalForm() );
317            StringBuffer sb = new StringBuffer( 1000 );
318            sb.append( href ).append( gfi.getRequestParameter() );
319            // If a user is registered to the portal use his name and password to
320            // perform GetFeatureInfo request because maybe the connected server
321            // is hidden behind a owsProxy
322            // TODO
323            // read informations too which server user name and password shall be send
324            // TODO
325            // replace sending user name and password by a sessionID
326            if ( !"anon".equals( request.getAttribute( "$U$" ) ) ) {
327                sb.append( "&user=" ).append( request.getAttribute( "$U$" ) );
328                request.removeAttribute( "$U$" );
329            }
330            if ( request.getAttribute( "$P$" ) != null ) {
331                sb.append( "&password=" ).append( request.getAttribute( "$P$" ) );
332                request.removeAttribute( "$P$" );
333            }
334            LOG.logDebug( "info request: ", sb );
335    
336            XMLFragment frag = null;
337            try {
338                frag = new XMLFragment( new URL( sb.toString() ) );
339                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
340                    LOG.logDebug( "GetFeatureInfo result: \n", frag.getAsPrettyString() );
341                }
342            } catch ( Exception e ) {
343                e.printStackTrace();
344                throw new OGCWebServiceException( "could not perform GetFeatureInfo request " + e.getMessage() );
345            }
346    
347            LOG.logDebug( "get xslt for:", href );
348            String path = getPathToXSLTScript( layer, href );
349            LOG.logDebug( "selected path to xslt: ", path );
350    
351            path = sc.getRealPath( "/WEB-INF/" + path );
352    
353            Source xmlSource = new DOMSource( frag.getRootElement() );
354    
355            Source xslSource = new StreamSource( new File( path ) );
356            StringWriter sw = new StringWriter( 20000 );
357    
358            Map<String, String> param = new HashMap<String, String>();
359            param.put( "SERVER", layer.getServer().getTitle() );
360            String s = null;
361            try {
362                XSLTDocument.transform( xmlSource, xslSource, new StreamResult( sw ), null, param );
363                s = sw.getBuffer().toString();
364                sw.close();
365            } catch ( Exception e ) {
366                throw new PortalException( "could not transform GetFeatureInfo result of request: " + sb + " - "
367                                           + StringTools.stackTraceToString( e ) );
368            }
369    
370            return s;
371    
372        }
373    
374        /**
375         * returns the path to the xslt script to be used for transforming info data
376         *
377         * @param layer
378         * @param server
379         * @return the path to the xslt script to be used for transforming info data
380         * @throws PortalException
381         */
382        private String getPathToXSLTScript( Layer layer, String server )
383                                throws PortalException {
384            StringBuffer sbb = new StringBuffer( 1000 );
385            sbb.append( INIT_PATHTOXSLT ).append( ';' ).append( server ).append( ';' );
386            sbb.append( layer.getServer().getService() );
387            String path = (String) initParams.get( sbb.toString() );
388            if ( path == null ) {
389                sbb.delete( 0, sbb.length() );
390                sbb.append( INIT_PATHTOXSLT ).append( ';' );
391                sbb.append( layer.getServer().getService() );
392                path = (String) initParams.get( sbb.toString() );
393            }
394            if ( path == null ) {
395                path = (String) initParams.get( INIT_PATHTOXSLT );
396            }
397            if ( path == null ) {
398                // for being compliant with an error in init-param that
399                // has been used in several instances
400                sbb = new StringBuffer( 1000 );
401                sbb.append( "pathToXLST" ).append( ';' ).append( server ).append( ';' );
402                sbb.append( layer.getServer().getService() );
403                path = (String) initParams.get( sbb.toString() );
404                if ( path == null ) {
405                    sbb.delete( 0, sbb.length() );
406                    sbb.append( "pathToXLST" ).append( ';' );
407                    sbb.append( layer.getServer().getService() );
408                    path = (String) initParams.get( sbb.toString() );
409                }
410                if ( path == null ) {
411                    path = (String) initParams.get( "pathToXLST" );
412                }
413            }
414    
415            if ( path == null ) {
416                LOG.logDebug( "initParams: ", initParams );
417                throw new PortalException( "no XSLT script defined for processing GetFeatureInfo " + "response of:"
418                                           + INIT_PATHTOXSLT + ';' + server + ';' + layer.getServer().getService() );
419            }
420    
421            return path;
422        }
423    
424        /**
425         * creates a GetFeatureInfo request from the requested target layers depending on the current
426         * view context
427         *
428         * @param layerList
429         * @param vc
430         * @return GetFeatureInfo request
431         */
432        private GetFeatureInfo createGetFeatureInfoRequest( List<Layer> layerList, ViewContext vc ) {
433    
434            String[] fiLayers = new String[layerList.size()];
435            GetMap.Layer[] gmLayers = new GetMap.Layer[layerList.size()];
436            for ( int i = 0; i < gmLayers.length; i++ ) {
437                Layer layer = layerList.get( i );
438                fiLayers[i] = layer.getName();
439                gmLayers[i] = new GetMap.Layer( layer.getName(), layer.getStyleList().getCurrentStyle().getName() );
440            }
441    
442            Layer layer = layerList.get( 0 );
443            Point[] pt = vc.getGeneral().getBoundingBox();
444            String srs = pt[0].getCoordinateSystem().getIdentifier();
445            int width = vc.getGeneral().getWindow().width;
446            int height = vc.getGeneral().getWindow().height;
447            Envelope bbox = GeometryFactory.createEnvelope( pt[0].getX(), pt[0].getY(), pt[1].getX(), pt[1].getY(),
448                                                            pt[0].getCoordinateSystem() );
449    
450            // create GetMap request being mandatory part of the GetFeatureInfo request
451            GetMap gm = GetMap.create( layer.getServer().getVersion(), "id", gmLayers, null, null,
452                                       layer.getFormatList().getCurrentFormat().getName(), width, height, srs, bbox, false,
453                                       Color.WHITE, null, null, null, null, null );
454    
455            int x = Integer.parseInt( parameter.get( PARAM_X ) );
456            int y = Integer.parseInt( parameter.get( PARAM_Y ) );
457            java.awt.Point point = new java.awt.Point( x, y );
458            int featureCount = Integer.parseInt( parameter.get( PARAM_FEATURECOUNT ) );
459            GetFeatureInfo gfi = GetFeatureInfo.create( layer.getServer().getVersion(), "id", fiLayers, gm,
460                                                        "application/vnd.ogc.gml", featureCount, point, null, null, null );
461    
462            return gfi;
463        }
464    
465        /**
466         * sets the name of the the layers that are activated for feature info requests in the uses WMC
467         *
468         * @return layers involved in feature info
469         */
470        String[] findCurrentFILayers() {
471    
472            List<String> list = new ArrayList<String>();
473    
474            ViewContext vc = getCurrentViewContext( (String) initParams.get( INIT_MAPPORTLETID ) );
475    
476            LayerList layerList = vc.getLayerList();
477            Layer[] layers = layerList.getLayers();
478            for ( int i = 0; i < layers.length; i++ ) {
479    
480                if ( !layers[i].isHidden() && layers[i].isQueryable() ) {
481                    layers[i].getExtension().setSelectedForQuery( true );
482                    list.add( layers[i].getName() );
483                }
484            }
485            return list.toArray( new String[list.size()] );
486        }
487    
488    }