001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/portal/standard/context/control/DownloadListener.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.portal.standard.context.control; 038 039 import java.io.File; 040 import java.io.FileWriter; 041 import java.io.IOException; 042 import java.io.InputStreamReader; 043 import java.net.MalformedURLException; 044 import java.net.URI; 045 import java.net.URL; 046 import java.util.ArrayList; 047 import java.util.Iterator; 048 import java.util.List; 049 import java.util.UUID; 050 051 import javax.servlet.ServletContext; 052 import javax.servlet.http.HttpServletRequest; 053 import javax.servlet.http.HttpSession; 054 055 import org.deegree.datatypes.QualifiedName; 056 import org.deegree.enterprise.control.AbstractListener; 057 import org.deegree.enterprise.control.FormEvent; 058 import org.deegree.enterprise.control.RPCMember; 059 import org.deegree.enterprise.control.RPCMethodCall; 060 import org.deegree.enterprise.control.RPCParameter; 061 import org.deegree.enterprise.control.RPCStruct; 062 import org.deegree.enterprise.control.RPCWebEvent; 063 import org.deegree.framework.log.ILogger; 064 import org.deegree.framework.log.LoggerFactory; 065 import org.deegree.framework.mail.EMailMessage; 066 import org.deegree.framework.mail.MailHelper; 067 import org.deegree.framework.mail.MailMessage; 068 import org.deegree.framework.mail.SendMailException; 069 import org.deegree.framework.util.FeatureUtils; 070 import org.deegree.framework.util.IDGenerator; 071 import org.deegree.framework.util.NetWorker; 072 import org.deegree.framework.util.StringTools; 073 import org.deegree.framework.util.ZipUtils; 074 import org.deegree.framework.xml.Marshallable; 075 import org.deegree.framework.xml.NamespaceContext; 076 import org.deegree.framework.xml.XMLFragment; 077 import org.deegree.framework.xml.XMLParsingException; 078 import org.deegree.framework.xml.XMLTools; 079 import org.deegree.i18n.Messages; 080 import org.deegree.io.shpapi.ShapeFile; 081 import org.deegree.model.feature.Feature; 082 import org.deegree.model.feature.FeatureCollection; 083 import org.deegree.model.feature.FeatureProperty; 084 import org.deegree.model.feature.GMLFeatureCollectionDocument; 085 import org.deegree.model.filterencoding.ComplexFilter; 086 import org.deegree.model.filterencoding.Filter; 087 import org.deegree.model.filterencoding.Operation; 088 import org.deegree.model.filterencoding.OperationDefines; 089 import org.deegree.model.filterencoding.PropertyName; 090 import org.deegree.model.filterencoding.SpatialOperation; 091 import org.deegree.model.spatialschema.Envelope; 092 import org.deegree.model.spatialschema.Geometry; 093 import org.deegree.model.spatialschema.GeometryFactory; 094 import org.deegree.ogcbase.BaseURL; 095 import org.deegree.ogcbase.CommonNamespaces; 096 import org.deegree.ogcwebservices.OWSUtils; 097 import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities; 098 import org.deegree.ogcwebservices.wfs.capabilities.WFSOperationsMetadata; 099 import org.deegree.ogcwebservices.wfs.operation.GetFeature; 100 import org.deegree.ogcwebservices.wfs.operation.GetFeatureDocument; 101 import org.deegree.ogcwebservices.wfs.operation.Query; 102 import org.deegree.ogcwebservices.wfs.operation.GetFeature.RESULT_TYPE; 103 import org.deegree.owscommon.OWSDomainType; 104 import org.deegree.portal.Constants; 105 import org.deegree.portal.PortalException; 106 import org.deegree.portal.context.GeneralExtension; 107 import org.deegree.portal.context.Layer; 108 import org.deegree.portal.context.LayerExtension; 109 import org.deegree.portal.context.ViewContext; 110 import org.xml.sax.SAXException; 111 112 /** 113 * This Listener is used when a user likes to download the WFS data behind a WMS layer. 114 * 115 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 116 * @author last edited by: $Author: jmays $ 117 * 118 * @version $Revision: 22739 $ $Date: 2010-02-24 16:17:39 +0100 (Mi, 24 Feb 2010) $ 119 */ 120 public class DownloadListener extends AbstractListener { 121 122 private static final ILogger LOG = LoggerFactory.getLogger( DownloadListener.class ); 123 124 private static final NamespaceContext nsContext = CommonNamespaces.getNamespaceContext(); 125 126 private String mailAddr = ""; 127 128 private String downloadDir = ""; 129 130 private URL downloadURL = null; 131 132 /** 133 * default is false. A value may be provided as TEST_MAX_HITS in initParameters of controller.xml 134 */ 135 private boolean isMaxFeaturesTestEnabled = false; 136 137 /** 138 * default value for maximum number of features that shall be retreived from a WFS, as provided in initParameters of 139 * controller.xml 140 */ 141 private int defaultMaxFeatures = 0; 142 143 /* 144 * (non-Javadoc) 145 * 146 * @see org.deegree.enterprise.control.AbstractListener#actionPerformed(org.deegree.enterprise.control.FormEvent) 147 */ 148 @Override 149 public void actionPerformed( FormEvent event ) { 150 151 // set init parameter as provided in controller.xml 152 if ( Integer.getInteger( getInitParameter( "DEFAULT_MAX_FEATURES" ) ) != null ) { 153 defaultMaxFeatures = Integer.getInteger( getInitParameter( "DEFAULT_MAX_FEATURES" ) ).intValue(); 154 } 155 isMaxFeaturesTestEnabled = "true".equalsIgnoreCase( getInitParameter( "TEST_MAX_HITS" ) ); 156 157 RPCWebEvent rpc = (RPCWebEvent) event; 158 try { 159 validate( rpc ); 160 } catch ( PortalException e ) { 161 gotoErrorPage( Messages.get( "IGEO_STD_CNTXT_INVALID_RPC", "download", e.getMessage() ) ); 162 return; 163 } 164 165 try { 166 // get users email address 167 mailAddr = getUserEmail( rpc ); 168 } catch ( Exception e ) { 169 LOG.logError( e.getMessage(), e ); 170 gotoErrorPage( e.getMessage() ); 171 return; 172 } 173 if ( mailAddr == null ) { 174 gotoErrorPage( "Unknown user and/or email address. Download not possible." ); 175 return; 176 } 177 178 // read base context 179 HttpSession session = ( (HttpServletRequest) this.getRequest() ).getSession( true ); 180 ViewContext vc = (ViewContext) session.getAttribute( Constants.CURRENTMAPCONTEXT ); 181 182 downloadDir = ( (GeneralExtension) vc.getGeneral().getExtension() ).getIOSettings().getDownloadDirectory().getDirectoryName(); 183 downloadURL = ( (GeneralExtension) vc.getGeneral().getExtension() ).getIOSettings().getDownloadDirectory().getOnlineResource(); 184 185 ServletContext sc = session.getServletContext(); 186 sc.setAttribute( Constants.DOWNLOADDIR, downloadDir ); 187 188 String msg = ""; 189 if ( !msg.equals( "" ) ) { 190 // user doesn't have the authorization to download the ordered datasets 191 this.setNextPage( this.getAlternativeNextPage() ); 192 this.getRequest().setAttribute( Constants.MESSAGE, msg ); 193 } else { 194 try { 195 ArrayList<RequestBean> gfrl = new ArrayList<RequestBean>(); 196 ArrayList<FeatureTemplate> fids = createFeatureTemplates( rpc ); 197 Iterator<FeatureTemplate> iterator = fids.iterator(); 198 199 // create a GetFeature request for each ordered dataset 200 while ( iterator.hasNext() ) { 201 FeatureTemplate ft = iterator.next(); 202 RequestBean gfRequest = getWFSGetFeatureCalls( ft, vc ); 203 204 if ( gfRequest == null ) { 205 StringBuffer s = new StringBuffer( 500 ); 206 try { 207 s.append( Messages.get( "IGEO_STD_CNTXT_MISSING_DATA" ) ); 208 s.append( "<BR><BR>" ); 209 } catch ( Exception e ) { 210 LOG.logError( e.getMessage(), e ); 211 } 212 s.append( "<b>" ).append( ft.getTitle() ).append( "</b>" ); 213 throw new Exception( s.toString() ); 214 } else { 215 gfrl.add( gfRequest ); 216 } 217 } 218 219 // perform dataloading in another thread 220 RPCStruct struct = (RPCStruct) rpc.getRPCMethodCall().getParameters()[0].getValue(); 221 String format = (String) struct.getMember( "format" ).getValue(); 222 LoadController cntr = new LoadController( gfrl, format ); 223 cntr.start(); 224 String s = Messages.get( "IGEO_STD_CNTXT_INFO_EMAIL_CONFIRM", mailAddr ); 225 this.setNextPage( "message.jsp" ); 226 getRequest().setAttribute( Constants.MESSAGE, s ); 227 } catch ( Exception e ) { 228 LOG.logError( e.getMessage(), e ); 229 this.getRequest().setAttribute( Constants.MESSAGE, e.toString() ); 230 gotoErrorPage( e.toString() ); 231 } 232 } 233 } 234 235 private String getUserEmail( RPCWebEvent rpc ) 236 throws Exception { 237 String email = null; 238 239 RPCStruct struct = (RPCStruct) rpc.getRPCMethodCall().getParameters()[0].getValue(); 240 String sessionId = (String) struct.getMember( "sessionID" ).getValue(); // this is the userName 241 String rpcMail = (String) struct.getMember( "email" ).getValue(); 242 243 if ( ( sessionId == null || "null".equals( sessionId ) ) && ( rpcMail == null || "".equals( rpcMail ) ) ) { 244 // unknown session AND unknown email address. 245 // -> no way to find out where to send the download-info. 246 LOG.logDebug( "Neither sessionID nor email was set in RPC params" ); 247 return null; 248 } 249 250 HttpSession session = ( (HttpServletRequest) getRequest() ).getSession( true ); 251 ViewContext vc = (ViewContext) session.getAttribute( Constants.CURRENTMAPCONTEXT ); 252 if ( vc == null ) { 253 return null; 254 } 255 GeneralExtension ge = vc.getGeneral().getExtension(); 256 257 if ( sessionId != null && !"null".equals( sessionId ) && ge.getAuthentificationSettings() != null ) { 258 LOG.logDebug( "try getting user information (email address) from WAS/sessionID" ); 259 BaseURL baseUrl = ge.getAuthentificationSettings().getAuthentificationURL(); 260 String url = OWSUtils.validateHTTPGetBaseURL( baseUrl.getOnlineResource().toExternalForm() ); 261 StringBuffer sb = new StringBuffer( url ); 262 sb.append( "request=DescribeUser&SESSIONID=" ).append( sessionId ); 263 264 XMLFragment xml = new XMLFragment(); 265 xml.load( new URL( sb.toString() ) ); 266 267 email = XMLTools.getRequiredNodeAsString( xml.getRootElement(), "/User/EMailAddress", nsContext ); 268 } 269 if ( email == null || ( "" ).equals( email ) ) { 270 LOG.logDebug( "Taking email address from rpc request!" ); 271 email = rpcMail; 272 } 273 274 LOG.logDebug( "Email: " + email ); 275 return email; 276 } 277 278 /** 279 * gets the user name assigned to the passed session ID from a authentification service. If no user is assigned to 280 * the session ID <tt>null</tt> will be returned. If the session is closed or expired an exception will be thrown 281 * 282 * @param sessionId 283 * @return name of the user assigned to the passed session ID 284 */ 285 protected String getUserName( String sessionId ) 286 throws XMLParsingException, IOException, SAXException { 287 288 HttpSession session = ( (HttpServletRequest) getRequest() ).getSession( true ); 289 ViewContext vc = (ViewContext) session.getAttribute( Constants.CURRENTMAPCONTEXT ); 290 if ( vc == null ) { 291 return null; 292 } 293 GeneralExtension ge = vc.getGeneral().getExtension(); 294 String userName = null; 295 if ( sessionId != null && ge.getAuthentificationSettings() != null ) { 296 LOG.logDebug( "try getting user from WAS/sessionID" ); 297 BaseURL baseUrl = ge.getAuthentificationSettings().getAuthentificationURL(); 298 String url = OWSUtils.validateHTTPGetBaseURL( baseUrl.getOnlineResource().toExternalForm() ); 299 StringBuffer sb = new StringBuffer( url ); 300 sb.append( "request=DescribeUser&SESSIONID=" ).append( sessionId ); 301 302 XMLFragment xml = new XMLFragment(); 303 xml.load( new URL( sb.toString() ) ); 304 305 userName = XMLTools.getRequiredNodeAsString( xml.getRootElement(), "/User/UserName", nsContext ); 306 } else { 307 LOG.logDebug( "try getting user from getUserPrincipal()" ); 308 if ( ( (HttpServletRequest) getRequest() ).getUserPrincipal() != null ) { 309 userName = ( (HttpServletRequest) getRequest() ).getUserPrincipal().getName(); 310 if ( userName.indexOf( "\\" ) > 1 ) { 311 String[] us = StringTools.toArray( userName, "\\", false ); 312 userName = us[us.length - 1]; 313 } 314 } 315 } 316 LOG.logDebug( "userName: " + userName ); 317 return userName; 318 } 319 320 /** 321 * Convenience method to extract the boundig box from an rpc fragment. 322 * 323 * @param bboxStruct 324 * the <code>RPCStruct</code> containing the bounding box. For example, 325 * <code><member><name>boundingBox</name>etc...</code>. 326 * 327 * @return an envelope with the boundaries defined in the rpc structure 328 */ 329 protected Envelope extractBBox( RPCStruct bboxStruct ) { 330 331 // read base context 332 HttpSession session = ( (HttpServletRequest) this.getRequest() ).getSession( true ); 333 ViewContext vc = (ViewContext) session.getAttribute( Constants.CURRENTMAPCONTEXT ); 334 Double minx = (Double) bboxStruct.getMember( Constants.RPC_BBOXMINX ).getValue(); 335 Double miny = (Double) bboxStruct.getMember( Constants.RPC_BBOXMINY ).getValue(); 336 Double maxx = (Double) bboxStruct.getMember( Constants.RPC_BBOXMAXX ).getValue(); 337 Double maxy = (Double) bboxStruct.getMember( Constants.RPC_BBOXMAXY ).getValue(); 338 339 Envelope bbox = GeometryFactory.createEnvelope( minx.doubleValue(), miny.doubleValue(), maxx.doubleValue(), 340 maxy.doubleValue(), 341 vc.getGeneral().getBoundingBox()[0].getCoordinateSystem() ); 342 return bbox; 343 } 344 345 /** 346 * validates the request to be performed 347 */ 348 private void validate( RPCWebEvent rpc ) 349 throws PortalException { 350 351 RPCMethodCall mc = rpc.getRPCMethodCall(); 352 RPCParameter param = mc.getParameters()[0]; 353 RPCStruct struct = (RPCStruct) param.getValue(); 354 355 RPCMember layerList = struct.getMember( "layerList" ); 356 RPCMember sessionID = struct.getMember( "sessionID" ); 357 RPCMember email = struct.getMember( "email" ); 358 RPCMember format = struct.getMember( "format" ); 359 RPCMember boundingBox = struct.getMember( "boundingBox" ); 360 361 if ( layerList == null ) { 362 throw new PortalException( Messages.get( "IGEO_STD_CNTXT_MISSING_PARAM", "'layerList'", "Download" ) ); 363 } 364 if ( sessionID == null ) { 365 throw new PortalException( Messages.get( "IGEO_STD_CNTXT_MISSING_PARAM", "'sessionID'", "Download" ) ); 366 } 367 if ( email == null ) { 368 // has been added to RPC in deegree 2.4 369 throw new PortalException( Messages.get( "IGEO_STD_CNTXT_MISSING_PARAM", "'email'", "Download" ) ); 370 } 371 if ( format == null ) { 372 // has been added to RPC in deegree 2.4 373 throw new PortalException( Messages.get( "IGEO_STD_CNTXT_MISSING_PARAM", "'format'", "Download" ) ); 374 } 375 if ( boundingBox == null ) { 376 throw new PortalException( Messages.get( "IGEO_STD_CNTXT_MISSING_PARAM", "'boundingBox'", "Download" ) ); 377 } 378 379 // DEBUG STATEMENTS 380 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 381 RPCStruct layerStruct = (RPCStruct) layerList.getValue(); 382 RPCMember[] layers = layerStruct.getMembers(); 383 for ( int i = 0; i < layers.length; i++ ) { 384 // key = "layername,layertitle" , value = "WMS-Service-Url" 385 String tmp = layers[i].getName(); 386 String layerName = tmp.substring( 0, tmp.indexOf( "," ) ); 387 String layerTitle = tmp.substring( tmp.indexOf( "," ) + 1 ); 388 String layerServiceURL = (String) layers[i].getValue(); 389 LOG.logDebug( "layername: ", layerName, " layertitle: ", layerTitle, " url: ", layerServiceURL ); 390 } 391 LOG.logDebug( "sessionID=" + (String) sessionID.getValue() ); 392 LOG.logDebug( "email=" + (String) email.getValue() ); 393 LOG.logDebug( "format=" + (String) format.getValue() ); 394 RPCStruct bboxStruct = (RPCStruct) boundingBox.getValue(); 395 LOG.logDebug( "bbox minx=" + (Double) bboxStruct.getMember( Constants.RPC_BBOXMINX ).getValue() ); 396 LOG.logDebug( "bbox miny=" + (Double) bboxStruct.getMember( Constants.RPC_BBOXMINY ).getValue() ); 397 LOG.logDebug( "bbox maxx=" + (Double) bboxStruct.getMember( Constants.RPC_BBOXMAXX ).getValue() ); 398 LOG.logDebug( "bbox maxy=" + (Double) bboxStruct.getMember( Constants.RPC_BBOXMAXY ).getValue() ); 399 } 400 } 401 402 /** 403 * performs the access to the data marked at the shopping card 404 */ 405 protected ArrayList<FeatureTemplate> createFeatureTemplates( RPCWebEvent event ) 406 throws PortalException, Exception { 407 408 RPCParameter[] params = event.getRPCMethodCall().getParameters(); 409 RPCStruct struct = (RPCStruct) params[0].getValue(); 410 RPCStruct layerStruct = (RPCStruct) struct.getMember( "layerList" ).getValue(); 411 412 RPCStruct bboxStruct = (RPCStruct) struct.getMember( Constants.RPC_BBOX ).getValue(); 413 Envelope bbox = extractBBox( bboxStruct ); 414 415 RPCMember[] layers = layerStruct.getMembers(); 416 417 ArrayList<FeatureTemplate> fids = new ArrayList<FeatureTemplate>( layers.length ); 418 for ( int i = 0; i < layers.length; i++ ) { 419 // key = "layername,layertitle" , value = "WMS-Service-Url" 420 String tmp = layers[i].getName(); 421 String layerName = tmp.substring( 0, tmp.indexOf( "," ) ); // assuming there is no "," within the layername 422 String layerTitle = tmp.substring( tmp.indexOf( "," ) + 1 ); 423 String layerServiceURL = (String) layers[i].getValue(); 424 FeatureTemplate ft = new FeatureTemplate( layerName, layerTitle, bbox, layerServiceURL ); 425 fids.add( i, ft ); 426 } 427 LOG.logDebug( "created FeatureTemplate.length=", fids.size() ); 428 return fids; 429 } 430 431 /** 432 * returns the call to be used to perform a GetFeature request for the passed feature template. 433 * 434 * @param layer 435 * used 436 * @param vc 437 * @return the call to be used to perform a GetFeature request for the passed feature template 438 * @throws PortalException 439 */ 440 protected RequestBean getWFSGetFeatureCalls( FeatureTemplate layer, ViewContext vc ) 441 throws PortalException { 442 443 String href = null; 444 String version = null; 445 RequestBean result = null; 446 447 try { 448 Layer contxtLayer = vc.getLayerList().getLayer( layer.getId(), layer.getServerURL() ); 449 LayerExtension lExt = null; 450 try { 451 lExt = (LayerExtension) contxtLayer.getExtension(); 452 } catch ( ClassCastException e ) { 453 throw new PortalException( e.getMessage() ); 454 } 455 456 href = lExt.getDataService().getServer().getOnlineResource().toString(); 457 version = lExt.getDataService().getServer().getVersion(); 458 459 // feature from layer extension 460 String featureType = lExt.getDataService().getFeatureType(); 461 String nsFeature = featureType.substring( featureType.indexOf( '{' ) + 1, featureType.indexOf( '}' ) ); 462 String featureLocalName = featureType.substring( featureType.lastIndexOf( ':' ) + 1 ); 463 464 // geometry from layer extension 465 String geometryType = lExt.getDataService().getGeometryType(); 466 String nsGeometry = geometryType.substring( geometryType.indexOf( '{' ) + 1, geometryType.indexOf( '}' ) ); 467 String geometryLocalName = geometryType.substring( geometryType.lastIndexOf( ':' ) + 1 ); 468 469 if ( href != null ) { 470 // init RequestBean values 471 GetFeature gfrHits = null; 472 GetFeature gfr = null; 473 int maxRequest = defaultMaxFeatures; 474 475 if ( isMaxFeaturesTestEnabled ) { 476 // get the DefaultMaxFeatures from the capabilities of the wfs. 477 if ( !( lExt.getDataService().getServer().getCapabilities() instanceof WFSCapabilities ) ) { 478 LOG.logDebug( "very strange, should be a wfs capa." ); 479 // FIXME do something intelligent 480 } else { 481 WFSCapabilities cap = ( (WFSCapabilities) lExt.getDataService().getServer().getCapabilities() ); 482 WFSOperationsMetadata opm = ( (WFSOperationsMetadata) cap.getOperationsMetadata() ); 483 OWSDomainType[] constraints = opm.getConstraints(); 484 for ( OWSDomainType cons : constraints ) { 485 if ( "DefaultMaxFeatures".equals( cons.getName() ) ) { 486 String[] values = cons.getValues(); 487 if ( values != null ) { 488 // take the freaking first. 489 try { 490 maxRequest = Integer.parseInt( values[0] ); 491 } catch ( NumberFormatException e ) { 492 // do nothing 493 } 494 } 495 } 496 } 497 } 498 // RESULT_TYPE = HITS 499 gfrHits = createGetFeatureRequest( layer, version, new QualifiedName( "app", featureLocalName, 500 new URI( nsFeature ) ), 501 new QualifiedName( "app", geometryLocalName, 502 new URI( nsGeometry ) ), RESULT_TYPE.HITS ); 503 } 504 // RESULT_TYPE = RESULTS 505 gfr = createGetFeatureRequest( layer, version, new QualifiedName( "app", featureLocalName, 506 new URI( nsFeature ) ), 507 new QualifiedName( "app", geometryLocalName, new URI( nsGeometry ) ), 508 RESULT_TYPE.RESULTS ); 509 510 result = new RequestBean( href, gfrHits, gfr, maxRequest, layer.getId(), layer.getTitle() ); 511 } 512 } catch ( Exception e ) { 513 LOG.logError( e.getMessage(), e ); 514 throw new PortalException( e.getMessage(), e ); 515 } 516 return result; 517 } 518 519 /** 520 * creates a GetFeature request considering the feature type (ID) and the bounding box encapsulated in the passed 521 * <tt>FeatureTemplate</tt> 522 * 523 * @param ft 524 * FeatureTemplate 525 * @param ftName 526 * a Qualified Name representing the feature type name of the requested feature, ex: app:ZipCodes 527 * @param gtName 528 * a Qualified Name representing the geometry type name of the requested feature, ex: app:geometry 529 * @param resultType 530 * the type of result (GetFeature.RESULT_TYPE.HITS or GetFeature.RESULT_TYPE.RESULTS) 531 * @return {@link GetFeature} request to access data for download 532 * @throws PortalException 533 */ 534 protected GetFeature createGetFeatureRequest( FeatureTemplate ft, String version, QualifiedName ftName, 535 QualifiedName gtName, GetFeature.RESULT_TYPE resultType ) 536 throws PortalException { 537 538 // read base context 539 HttpSession session = ( (HttpServletRequest) this.getRequest() ).getSession( true ); 540 ViewContext vc = (ViewContext) session.getAttribute( Constants.CURRENTMAPCONTEXT ); 541 String srcName = vc.getGeneral().getBoundingBox()[0].getCoordinateSystem().getIdentifier(); 542 543 GetFeature gfr = null; 544 545 try { 546 Geometry geom = GeometryFactory.createSurface( ft.getEnvelope(), ft.getEnvelope().getCoordinateSystem() ); 547 Operation op = new SpatialOperation( OperationDefines.BBOX, new PropertyName( gtName ), geom ); 548 Filter filter = new ComplexFilter( op ); 549 IDGenerator idg = IDGenerator.getInstance(); 550 551 // create WFS GetFeature request for either HITS or RESULTS 552 Query query = Query.create( null, null, null, null, version, new QualifiedName[] { ftName }, null, srcName, 553 filter, -1, 0, resultType ); 554 gfr = GetFeature.create( "1.1.0", "" + idg.generateUniqueID(), resultType, "text/xml; subtype=gml/3.1.1", 555 null, -1, 0, -1, 0, new Query[] { query } ); 556 } catch ( Exception e ) { 557 LOG.logError( e.getMessage(), e ); 558 throw new PortalException( Messages.get( "IGEO_STD_CNTXT_ERROR_CREATE_GETFEATURE", ft.getTitle(), 559 ft.getEnvelope() ) ); 560 } 561 return gfr; 562 } 563 564 // ///////////////////////////////////////////////////////////////////////// 565 // inner class // 566 // ///////////////////////////////////////////////////////////////////////// 567 568 /** 569 * class that handles the loading of the requested data from the responsible WFS. The class also informs the user 570 * who had requested the data via email that the data are ready for download if everything had worked fine. 571 * otherwise the the user and the administrator will be informed about the problem that had occured. 572 */ 573 private class LoadController extends Thread { 574 575 private ArrayList<RequestBean> gfrbl = null; 576 577 private String errorFeatures = null; 578 579 private String errorHitsException = null; 580 581 /** 582 * default format for data download is SHP 583 */ 584 private String format = "SHP"; 585 586 /** 587 * initialize the LoadController with an array list of GetFeature requests beans and the download format 588 */ 589 LoadController( ArrayList<RequestBean> gfrl, String format ) { 590 this.gfrbl = gfrl; 591 this.format = format; 592 } 593 594 /* 595 * (non-Javadoc) 596 * 597 * @see java.lang.Thread#run() 598 */ 599 @Override 600 public void run() { 601 ArrayList<String> zipFiles = new ArrayList<String>(); 602 Iterator<RequestBean> iterator = gfrbl.iterator(); 603 604 while ( iterator.hasNext() ) { 605 RequestBean request = iterator.next(); 606 String wfsAddr = request.serverLocation; 607 GetFeature gfHits = request.hitsRequest; // is NULL if !isMaxFeaturesTestEnabled 608 GetFeature gfResults = request.request; 609 String layerName = request.layerName; 610 String layerTitle = request.layerTitle; 611 612 FeatureCollection fcHits = null; 613 FeatureCollection fcResults = null; 614 GMLFeatureCollectionDocument gmlDoc = null; 615 boolean isHitsException = false; 616 617 try { 618 URL url = new URL( wfsAddr ); 619 620 if ( isMaxFeaturesTestEnabled ) { 621 int numberOfHits = request.maxFeatures; 622 // get number of hits of features in a given BBOX from WFS 623 GetFeatureDocument doc = org.deegree.ogcwebservices.wfs.XMLFactory.export( gfHits ); 624 LOG.logDebug( "GetFeature request:\n", doc.getAsPrettyString() ); 625 NetWorker nw = new NetWorker( url, doc.getAsString() ); 626 // extract numberOfFeatures 627 gmlDoc = new GMLFeatureCollectionDocument(); 628 gmlDoc.load( new InputStreamReader( nw.getInputStream() ), XMLFragment.DEFAULT_URL ); 629 630 if ( "ServiceExceptionReport".equals( gmlDoc.getRootElement().getLocalName() ) 631 || "ExceptionReport".equals( gmlDoc.getRootElement().getLocalName() ) ) { 632 // if number of hits cannot be retreived from the WFS, then it is unsure whether the 633 // reseult set will contain all feature objects within the given BBOX, or merely a subset 634 // defined by the WFS settings. 635 LOG.logDebug( "Unknown number of Features in the selected area." ); 636 isHitsException = true; // -> inform the user to contact the admin 637 } else { 638 fcHits = gmlDoc.parse(); 639 numberOfHits = Integer.parseInt( fcHits.getAttribute( "numberOfFeatures" ) ); 640 } 641 if ( isHitsException ) { 642 // inform the user to contact the admin 643 LOG.logDebug( "ServiceException for layer " + layerName ); 644 if ( errorHitsException != null && errorHitsException.length() > 0 ) { 645 errorHitsException += "\n" + layerName; 646 } else { 647 errorHitsException = layerName; 648 } 649 } else if ( numberOfHits > request.maxFeatures ) { 650 // store info on featureTypes with to many features requested 651 LOG.logDebug( Messages.get( "IGEO_STD_CNTXT_TO_MANY_OBJECTS" ), " - ", layerTitle ); 652 653 if ( errorFeatures != null && errorFeatures.length() > 0 ) { 654 errorFeatures += "\n" + layerTitle; 655 } else { 656 errorFeatures = layerTitle; 657 } 658 } else { 659 // get feature data from WFS 660 doc = org.deegree.ogcwebservices.wfs.XMLFactory.export( gfResults ); 661 LOG.logDebug( "GetFeature request:\n", doc.getAsPrettyString() ); 662 nw = new NetWorker( url, doc.getAsString() ); 663 // transform data and store as shape file 664 gmlDoc = new GMLFeatureCollectionDocument(); 665 gmlDoc.load( new InputStreamReader( nw.getInputStream() ), XMLFragment.DEFAULT_URL ); 666 fcResults = gmlDoc.parse(); 667 } 668 669 } else { 670 // gfHits = NULL && !isMaxFeaturesTestEnabled 671 // get feature data from WFS 672 GetFeatureDocument doc = org.deegree.ogcwebservices.wfs.XMLFactory.export( gfResults ); 673 LOG.logDebug( "GetFeature request:\n", doc.getAsPrettyString() ); 674 NetWorker nw = new NetWorker( url, doc.getAsString() ); 675 // transform data and store as shape file 676 gmlDoc = new GMLFeatureCollectionDocument(); 677 gmlDoc.load( new InputStreamReader( nw.getInputStream() ), XMLFragment.DEFAULT_URL ); 678 fcResults = gmlDoc.parse(); 679 } 680 } catch ( Exception e ) { 681 LOG.logError( e.getMessage(), e ); 682 // remove all created zip files 683 // rollback( zipFiles, downloadDir ); 684 // LOG.logError( "create shape file: " + gfResults.getQuery()[0].getTypeNames(), e ); 685 try { 686 sendErrorMail( e, wfsAddr, ( (Marshallable) gfResults ).exportAsXML() ); 687 } catch ( Exception e2 ) { 688 LOG.logError( e2.getMessage(), e2 ); 689 } 690 return; 691 } 692 693 File fileDir = null; 694 try { 695 fileDir = new File( new URL( downloadDir ).getFile() ); 696 } catch ( MalformedURLException e ) { 697 LOG.logDebug( "Download dir did not start with 'file', trying just as file now.", e ); 698 fileDir = new File( downloadDir ); 699 } 700 LOG.logDebug( "download directory (fileDir): ", fileDir.toString() ); 701 702 try { 703 if ( fcResults != null && !isHitsException && !( fcResults.size() == 0 ) ) { 704 // add name of the created zipfile to an ArrayList to inform the user where to download the data 705 if ( "GML".equals( format ) ) { 706 String filename = storeGML( gmlDoc, layerName, fileDir ); 707 zipFiles.add( filename ); 708 } else if ( "SHP".equals( format ) ) { 709 List<String> filenames = storeFC( fcResults, layerName, fileDir ); 710 zipFiles.addAll( filenames ); 711 } else { 712 // fall back: default behaviour = SHAPE-Download 713 List<String> filenames = storeFC( fcResults, layerName, fileDir ); 714 zipFiles.addAll( filenames ); 715 } 716 } 717 } catch ( Exception e ) { 718 // remove all created zip files 719 rollback( zipFiles, downloadDir ); 720 LOG.logError( "create zip file", e ); 721 try { 722 String typeNames = arrayToString( gfResults.getQuery()[0].getTypeNames(), "," ); 723 sendErrorMail( e, null, typeNames ); 724 } catch ( Exception e2 ) { 725 LOG.logError( e2.getMessage(), e ); 726 } 727 return; 728 } 729 } // END WHILE 730 731 try { 732 sendSuccessMail( zipFiles ); 733 } catch ( Exception e ) { 734 LOG.logError( "sendSuccessMail", e ); 735 } 736 } 737 738 private String arrayToString( QualifiedName[] strings, String delimiter ) { 739 StringBuffer sb = new StringBuffer( 200 ); 740 for ( int i = 0; i < strings.length; i++ ) { 741 sb.append( strings[i] ); 742 if ( i < strings.length ) { 743 sb.append( delimiter ); 744 } 745 } 746 return sb.toString(); 747 } 748 749 /** 750 * stores the passed feature collection as zipped shape and returns the file name(s) 751 * 752 * @param fc 753 * feature collection to be stored 754 * @param layerName 755 * name of the WMS layer corresponding to the feature type requested from the WFS. It is used for 756 * file name. 757 * @param fileDir 758 * the name of the download directory, as set in the WMC 759 * @return a list of strings containing all the file names for the input feature collection. These may be more 760 * than on, as a feature collection might contain several geometry types which will be turned into 761 * different shape files: one for point, one for curves, ... 762 * @throws PortalException 763 */ 764 private List<String> storeFC( FeatureCollection fc, String layerName, File fileDir ) 765 throws PortalException { 766 767 String zipName = null; 768 String[] shapeFileNames = null; 769 770 truncatePropertyValues( fc, 127, "..." ); // these values could be externalised to init params, if need be. 771 // prevent creating shapes with multiple geometry types 772 FeatureCollection[] fcs = FeatureUtils.separateFeaturesForShapes( fc ); 773 774 List<String> files = new ArrayList<String>(); 775 for ( int i = 0; i < fcs.length - 1; i++ ) { 776 // only using the first four geometry types: point, multipoint, (multi-)curve, (multi-)surface. 777 if ( fcs[i].size() > 0 ) { 778 try { 779 // create and write shape 780 String shapeBaseName = layerName + System.currentTimeMillis() + fcs[i].getId(); 781 File fileBaseName = new File( fileDir, shapeBaseName ); 782 ShapeFile shp = new ShapeFile( fileBaseName.toString(), "rw" ); 783 shp.writeShape( fcs[i] ); 784 shp.close(); 785 786 // create zip file from .shp, .shx and .dbf 787 zipName = shapeBaseName + ".zip"; 788 shapeFileNames = new String[] { shapeBaseName + ".shp", shapeBaseName + ".shx", 789 shapeBaseName + ".dbf" }; 790 791 // create zip archive 792 ZipUtils zu = new ZipUtils(); 793 zu.doZip( fileDir.toString(), zipName, shapeFileNames, true, false ); 794 LOG.logDebug( "zip was created: " + zipName ); 795 files.add( zipName ); 796 } catch ( Exception e ) { 797 LOG.logError( e.getMessage(), e ); 798 // remove the created shape files 799 for ( int j = 0; j < 3; i++ ) { 800 File file = new File( fileDir, shapeFileNames[i] ); 801 file.delete(); 802 } 803 // remove the zip file 804 File file = new File( fileDir, zipName ); 805 file.delete(); 806 throw new PortalException( Messages.get( "IGEO_STD_CNTXT_ERROR_SAVE_SHAPEFILE", file.getName() ), 807 e ); 808 } 809 } 810 } 811 return files; 812 } 813 814 /** 815 * @param fc 816 * @param maxLength 817 * (e.g. "127") 818 * @param postfix 819 * (e.g. "...") 820 */ 821 private void truncatePropertyValues( FeatureCollection fc, int maxLength, String postfix ) { 822 postfix = postfix == null ? "" : postfix; 823 StringBuffer truncValue = new StringBuffer(); 824 825 for ( int i = 0; i < fc.size(); i++ ) { 826 Feature feat = fc.getFeature( i ); 827 FeatureProperty[] fps = feat.getProperties(); 828 for ( int j = 0; j < fps.length; j++ ) { 829 if ( fps[j].getValue() instanceof String ) { 830 truncValue.setLength( 0 ); 831 truncValue.append( (String) fps[j].getValue() ); 832 if ( truncValue.length() > maxLength ) { 833 while ( ( truncValue + postfix ).getBytes().length > maxLength ) { 834 truncValue.setLength( truncValue.length() - 1 ); 835 } 836 fps[j].setValue( truncValue + postfix ); 837 } 838 } 839 } 840 } 841 } 842 843 /** 844 * stores the passed feature collection as zipped GML and returns the file name 845 * 846 * @param gmlDoc 847 * the gml feature collection document to be stored 848 * @param layerName 849 * name of the WMS layer corresponding to the feature type requested from the WFS. It is used for 850 * file names. 851 * @param fileDir 852 * the name of the download directory, as set in the WMC 853 * @return 854 * @throws PortalException 855 */ 856 private String storeGML( GMLFeatureCollectionDocument gmlDoc, String layerName, File fileDir ) 857 throws PortalException { 858 859 String zipName = null; 860 String[] gmlFileNames = null; 861 862 try { 863 String gmlBaseName = layerName + System.currentTimeMillis() + UUID.randomUUID().toString(); 864 865 // create and write gml file 866 FileWriter fw = new FileWriter( new File( fileDir, gmlBaseName + ".xml" ) ); 867 fw.write( gmlDoc.getAsPrettyString() ); 868 fw.close(); 869 870 // create zip file 871 zipName = gmlBaseName + ".zip"; 872 gmlFileNames = new String[] { gmlBaseName + ".xml" }; 873 874 // create zip archive 875 ZipUtils zu = new ZipUtils(); 876 zu.doZip( fileDir.toString(), zipName, gmlFileNames, true, false ); 877 LOG.logDebug( "zip was created: " + zipName ); 878 } catch ( Exception e ) { 879 LOG.logError( e.getMessage(), e ); 880 // remove the created shape files 881 for ( int i = 0; i < 3; i++ ) { 882 File file = new File( fileDir, gmlFileNames[i] ); 883 file.delete(); 884 } 885 // remove the zip file 886 File file = new File( fileDir, zipName ); 887 file.delete(); 888 throw new PortalException( Messages.get( "IGEO_STD_CNTXT_ERROR_SAVE_SHAPEFILE", file.getName() ), e ); 889 } 890 return zipName; 891 } 892 893 /** 894 * deletes all files associated to the download order 895 * 896 * @param zipFiles 897 * list of zip files associated to the download order 898 */ 899 private void rollback( ArrayList<String> zipFiles, String dir ) { 900 901 try { 902 for ( int i = 0; i < zipFiles.size(); i++ ) { 903 String fn = (String) zipFiles.get( i ); 904 File file = new File( dir + "/" + fn ); 905 file.delete(); 906 } 907 } catch ( Exception e ) { 908 LOG.logError( e.getMessage(), e ); 909 } 910 } 911 912 /** 913 * sends a mail to inform the administrator and the user about an error that raised performing the data access 914 */ 915 private void sendErrorMail( Exception e, String addr, String target ) 916 throws SendMailException { 917 918 String mailAddress = mailAddr; 919 String mailHost = ContextMessages.getString( "Email.MailHost" ); 920 String mailHostUser = ContextMessages.getString( "Email.MailHostUser" ); 921 String mailHostPassword = ContextMessages.getString( "Email.MailHostPassword" ); 922 String sender = ContextMessages.getString( "Email.Sender" ); 923 String subject = ContextMessages.getString( "Email.Header" ); 924 925 // create mesage for informing the user 926 String st = StringTools.stackTraceToString( e.getStackTrace() ); 927 String message = null; 928 if ( addr == null ) { 929 message = Messages.get( "IGEO_STD_CNTXT_ERROR_CREATE_SHAPEFILE", target, st ); 930 } else { 931 message = Messages.get( "IGEO_STD_CNTXT_ERROR_PERFORM_GETFEATURE", addr, target, st ); 932 } 933 934 // send message to the user 935 MailMessage mm = new EMailMessage( sender, mailAddress, subject, message ); 936 if ( "!Email.MailHostUser!".equals( mailHostUser ) || "!Email.MailHostPassword!".equals( mailHostPassword ) ) { 937 MailHelper.createAndSendMail( mm, mailHost ); 938 } else { 939 MailHelper.createAndSendMail( mm, mailHost, mailHostUser, mailHostPassword ); 940 } 941 } 942 943 /** 944 * sends a mail to the user that the data download succeded and informs him where to download the created files 945 * 946 * @param files 947 * list of created files (names) 948 * 949 * @throws SendMailException 950 */ 951 private void sendSuccessMail( ArrayList<String> files ) 952 throws SendMailException { 953 954 String mailAddress = mailAddr; 955 String mailHost = ContextMessages.getString( "Email.MailHost" ); 956 String mailHostUser = ContextMessages.getString( "Email.MailHostUser" ); 957 String mailHostPassword = ContextMessages.getString( "Email.MailHostPassword" ); 958 String senderMail = ContextMessages.getString( "Email.Sender" ); 959 String emailSubject = ContextMessages.getString( "Email.Header" ); 960 961 // create mesage for informing the user 962 String address = NetWorker.url2String( downloadURL ); 963 if ( !address.endsWith( "/" ) ) { 964 address += "/"; 965 } 966 StringBuffer sb = new StringBuffer( Messages.get( "IGEO_STD_CNTXT_INFO_EMAIL_DATA_CREATED" ) ); 967 for ( int i = 0; i < files.size(); i++ ) { 968 String file = (String) files.get( i ); 969 sb.append( address + "download?file=" + file + "\n" ); 970 } 971 972 // add info on features with to many results 973 if ( errorFeatures != null ) { 974 sb.append( "\n" ).append( Messages.get( "IGEO_STD_CNTXT_TO_MANY_OBJECTS" ) ).append( "\n" ); 975 sb.append( errorFeatures ); 976 } 977 978 // inform the user to contact the admin for layers that caused a serviceException 979 if ( errorHitsException != null ) { 980 sb.append( "\n\n" ).append( Messages.get( "IGEO_STD_CNTXT_DOWNLOAD_SERVICE_EXCEPTION" ) ).append( "\n" ); 981 sb.append( errorHitsException ); 982 } 983 984 // send message to the user 985 MailMessage mm = new EMailMessage( senderMail, mailAddress, emailSubject, sb.toString() ); 986 if ( "!Email.MailHostUser!".equals( mailHostUser ) || "!Email.MailHostPassword!".equals( mailHostPassword ) ) { 987 // if these variables are not set in the properties file, they are set by the getString() method above. 988 MailHelper.createAndSendMail( mm, mailHost ); 989 } else { 990 MailHelper.createAndSendMail( mm, mailHost, mailHostUser, mailHostPassword ); 991 } 992 } 993 } 994 995 private class RequestBean { 996 997 String serverLocation; 998 999 /** 1000 * GetFeature request for result_type = hits (returning number of hits). may be null. 1001 */ 1002 GetFeature hitsRequest; 1003 1004 /** 1005 * GetFeature request for result_type = results 1006 */ 1007 GetFeature request; 1008 1009 String layerName; 1010 1011 String layerTitle; 1012 1013 /** 1014 * maximum number of results returned by this WFS server, as specified in the WFS capabilities (<ows:Constraint 1015 * name="DefaultMaxFeatures">). The default value is taken from init parameters, in case the WFS does not 1016 * provide the value in its capabilities 1017 */ 1018 int maxFeatures; 1019 1020 /** 1021 * @param server 1022 * this WFS server address 1023 * @param hitsRequest 1024 * GetFeature request for result_type = hits (returning number of hits). may be null. 1025 * @param request 1026 * GetFeature request for result_type = results 1027 * @param maxRequest 1028 * maximum number of results returned by this WFS server, as specified in the WFS capabilities 1029 * (<ows:Constraint name="DefaultMaxFeatures">). The default value is taken from init parameters, in 1030 * case the WFS does not provide the value in its capabilities. 1031 * @param layerName 1032 * the name (identifier) of the corresponding WMS layer 1033 * @param layerTitle 1034 * the title of the corresponding WMS layer 1035 */ 1036 RequestBean( String server, GetFeature hitsRequest, GetFeature request, int maxRequest, String layerName, 1037 String layerTitle ) { 1038 this.serverLocation = server; 1039 this.request = request; 1040 this.maxFeatures = maxRequest; 1041 this.hitsRequest = hitsRequest; 1042 this.layerName = layerName; 1043 this.layerTitle = layerTitle; 1044 } 1045 } 1046 1047 /** 1048 * little helper class to store association between IDs and bounding boxes 1049 */ 1050 protected class FeatureTemplate { 1051 1052 /** 1053 * WMS Layer name 1054 */ 1055 private String id = null; 1056 1057 /** 1058 * WMS Layer title 1059 */ 1060 private String title = null; 1061 1062 /** 1063 * WMS address for this layer 1064 */ 1065 private String serviceURL = null; 1066 1067 /** 1068 * current/requested bbox 1069 */ 1070 private Envelope bbox = null; 1071 1072 FeatureTemplate( String id, String title, Envelope bbox, String serviceURL ) { 1073 this.id = id; 1074 this.bbox = bbox; 1075 this.title = title; 1076 this.serviceURL = serviceURL; 1077 } 1078 1079 /** 1080 * @return id (the WMS layer name) 1081 */ 1082 public String getId() { 1083 return id; 1084 } 1085 1086 /** 1087 * @return Title (the WMS layer title) 1088 */ 1089 public String getTitle() { 1090 return title; 1091 } 1092 1093 /** 1094 * @return Envelope (current bbox) 1095 */ 1096 public Envelope getEnvelope() { 1097 return bbox; 1098 } 1099 1100 /** 1101 * @return ServiceURL (of WFS) 1102 */ 1103 public String getServerURL() { 1104 return serviceURL; 1105 } 1106 } 1107 }