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