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