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>&lt;member&gt;&lt;name&gt;boundingBox&lt;/name&gt;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    }