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 }