001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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 }