001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/io/datastore/wfs/CascadingWFSDatastore.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2008 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/deegree/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Aennchenstr. 19
030     53177 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041    
042     ---------------------------------------------------------------------------*/
043    package org.deegree.io.datastore.wfs;
044    
045    import java.io.ByteArrayOutputStream;
046    import java.io.IOException;
047    import java.io.InputStream;
048    import java.net.MalformedURLException;
049    import java.net.URL;
050    import java.util.ArrayList;
051    import java.util.HashMap;
052    import java.util.List;
053    import java.util.Map;
054    import java.util.concurrent.Callable;
055    import java.util.concurrent.CancellationException;
056    
057    import javax.xml.transform.TransformerException;
058    
059    import org.apache.commons.httpclient.HttpClient;
060    import org.apache.commons.httpclient.methods.PostMethod;
061    import org.apache.commons.httpclient.methods.StringRequestEntity;
062    import org.deegree.datatypes.QualifiedName;
063    import org.deegree.enterprise.WebUtils;
064    import org.deegree.framework.concurrent.ExecutionFinishedEvent;
065    import org.deegree.framework.concurrent.Executor;
066    import org.deegree.framework.log.ILogger;
067    import org.deegree.framework.log.LoggerFactory;
068    import org.deegree.framework.util.CharsetUtils;
069    import org.deegree.framework.xml.XMLFragment;
070    import org.deegree.framework.xml.XMLParsingException;
071    import org.deegree.framework.xml.XSLTDocument;
072    import org.deegree.i18n.Messages;
073    import org.deegree.io.datastore.Datastore;
074    import org.deegree.io.datastore.DatastoreException;
075    import org.deegree.io.datastore.DatastoreTransaction;
076    import org.deegree.io.datastore.schema.MappedFeatureType;
077    import org.deegree.model.crs.UnknownCRSException;
078    import org.deegree.model.feature.FeatureCollection;
079    import org.deegree.model.feature.FeatureFactory;
080    import org.deegree.model.feature.GMLFeatureCollectionDocument;
081    import org.deegree.ogcwebservices.OGCWebServiceException;
082    import org.deegree.ogcwebservices.OWSUtils;
083    import org.deegree.ogcwebservices.getcapabilities.InvalidCapabilitiesException;
084    import org.deegree.ogcwebservices.wfs.XMLFactory;
085    import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities;
086    import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilitiesDocument;
087    import org.deegree.ogcwebservices.wfs.operation.GetFeature;
088    import org.deegree.ogcwebservices.wfs.operation.Query;
089    import org.deegree.ogcwebservices.wfs.operation.GetFeature.RESULT_TYPE;
090    import org.xml.sax.SAXException;
091    
092    /**
093     * 
094     * 
095     * 
096     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
097     * @author last edited by: $Author: apoth $
098     * 
099     * @version $Revision: 9342 $, $Date: 2007-12-27 13:32:57 +0100 (Do, 27 Dez 2007) $
100     */
101    public class CascadingWFSDatastore extends Datastore {
102    
103        private ILogger LOG = LoggerFactory.getLogger( CascadingWFSDatastore.class );
104    
105        private static Map<URL, WFSCapabilities> wfsCapabilities;
106        static {
107            if ( wfsCapabilities == null ) {
108                wfsCapabilities = new HashMap<URL, WFSCapabilities>();
109            }
110        }
111    
112        @Override
113        public CascadingWFSAnnotationDocument getAnnotationParser() {
114            return new CascadingWFSAnnotationDocument();
115        }
116    
117        @Override
118        public void close()
119                                throws DatastoreException {
120        }
121    
122        @Override
123        public FeatureCollection performQuery( Query query, MappedFeatureType[] rootFts, DatastoreTransaction context )
124                                throws DatastoreException, UnknownCRSException {
125            return performQuery( query, rootFts );
126        }
127    
128        @Override
129        public FeatureCollection performQuery( Query query, MappedFeatureType[] rootFts )
130                                throws DatastoreException, UnknownCRSException {
131    
132            GetFeature getFeature = GetFeature.create( "1.1.0", "ID", RESULT_TYPE.RESULTS, "text/xml; subtype=gml/3.1.1",
133                                                       "", query.getMaxFeatures(), query.getStartPosition(), -1, -1,
134                                                       new Query[] { query } );
135            XMLFragment gfXML = null;
136            try {
137                gfXML = XMLFactory.export( getFeature );
138            } catch ( IOException e ) {
139                LOG.logError( e.getMessage(), e );
140                throw new DatastoreException( e.getMessage() );
141            } catch ( XMLParsingException e ) {
142                LOG.logError( e.getMessage(), e );
143                throw new DatastoreException( e.getMessage() );
144            }
145    
146            // get URL that is target of a GetFeature request
147            CascadingWFSDatastoreConfiguration config = (CascadingWFSDatastoreConfiguration) this.getConfiguration();
148            WFSDescription[] wfs = config.getWFSDescription();
149            List<Callable<FeatureCollection>> queryTasks = new ArrayList<Callable<FeatureCollection>>( wfs.length );
150            int timeout = 0;
151            for ( int i = 0; i < wfs.length; i++ ) {
152                QueryTask task = new QueryTask( gfXML, wfs[i] );
153                queryTasks.add( task );
154                timeout += wfs[i].getTimeout();
155            }
156    
157            List<ExecutionFinishedEvent<FeatureCollection>> finishedEvents = null;
158            try {
159                finishedEvents = Executor.getInstance().performSynchronously( queryTasks, timeout );
160            } catch ( InterruptedException e ) {
161                LOG.logError( e.getMessage(), e );
162                throw new DatastoreException( Messages.getMessage( "WFS_CASCDS_PERFORM_GF" ), e );
163            }
164    
165            return mergeResults( getFeature.getId(), finishedEvents );
166        }
167    
168        /**
169         * Merges the results of the request subparts into one feature collection.
170         * 
171         * @param fcid
172         *            id of the new (result) feature collection
173         * @param finishedEvents
174         * @return feature collection containing all features from all responses
175         * @throws OGCWebServiceException
176         */
177        private FeatureCollection mergeResults( String fcid, List<ExecutionFinishedEvent<FeatureCollection>> finishedEvents )
178                                throws DatastoreException {
179    
180            FeatureCollection result = null;
181    
182            try {
183                for ( ExecutionFinishedEvent<FeatureCollection> event : finishedEvents ) {
184                    if ( result == null ) {
185                        result = event.getResult();
186                    } else {
187                        result.addAll( event.getResult() );
188                    }
189                }
190            } catch ( CancellationException e ) {
191                LOG.logError( e.getMessage(), e );
192                String msg = Messages.getMessage( "WFS_GET_FEATURE_TIMEOUT", e.getMessage() );
193                throw new DatastoreException( msg, e );
194            } catch ( Throwable t ) {
195                LOG.logError( t.getMessage(), t );
196                String msg = Messages.getMessage( "WFS_GET_FEATURE_BACKEND", t.getMessage() );
197                throw new DatastoreException( msg, t );
198            }
199    
200            result.setId( fcid );
201            result.setAttribute( "numberOfFeatures", "" + result.size() );
202            return result;
203        }
204    
205        private WFSCapabilities getWFSCapabilities( URL url )
206                                throws DatastoreException {
207            String href = OWSUtils.validateHTTPGetBaseURL( url.toExternalForm() );
208            href = href + "request=GetCapabilities&version=1.1.0&service=WFS";
209    
210            LOG.logDebug( "requested capabilities: ", href );
211    
212            try {
213                url = new URL( href );
214            } catch ( MalformedURLException e1 ) {
215                e1.printStackTrace();
216            }
217    
218            WFSCapabilities caps = wfsCapabilities.get( url );
219            if ( caps == null ) {
220                // access capabilities if not already has been loaded
221                WFSCapabilitiesDocument cd = new WFSCapabilitiesDocument();
222                try {
223                    cd.load( url );
224                } catch ( IOException e ) {
225                    LOG.logError( e.getMessage(), e );
226                    throw new DatastoreException( e.getMessage() );
227                } catch ( SAXException e ) {
228                    LOG.logError( e.getMessage(), e );
229                    throw new DatastoreException( e.getMessage() );
230                }
231                try {
232                    caps = (WFSCapabilities) cd.parseCapabilities();
233                } catch ( InvalidCapabilitiesException e ) {
234                    LOG.logError( e.getMessage(), e );
235                    throw new DatastoreException( e.getMessage() );
236                }
237                wfsCapabilities.put( url, caps );
238            }
239            return caps;
240        }
241    
242        // ///////////////////////////////////////////////////////////////////////////
243        // inner classes
244        // ///////////////////////////////////////////////////////////////////////////
245    
246        /**
247         * Inner class for performing queries on a datastore.
248         */
249        private class QueryTask implements Callable<FeatureCollection> {
250    
251            private XMLFragment getFeature;
252    
253            private WFSDescription wfs;
254    
255            /**
256             * 
257             * @param getFeature
258             * @param wfs
259             */
260            QueryTask( XMLFragment getFeature, WFSDescription wfs ) {
261                this.getFeature = getFeature;
262                this.wfs = wfs;
263            }
264    
265            /**
266             * Performs the associated {@link Query} and returns the result.
267             * 
268             * @return resulting feature collection
269             * @throws Exception
270             */
271            public FeatureCollection call()
272                                    throws Exception {
273    
274                URL url = OWSUtils.getHTTPPostOperationURL( getWFSCapabilities( wfs.getUrl() ), GetFeature.class );
275    
276                // filter request if necessary
277                XSLTDocument inFilter = wfs.getInFilter();
278                if ( inFilter != null ) {
279                    try {
280                        getFeature = inFilter.transform( getFeature );
281                    } catch ( TransformerException e ) {
282                        LOG.logError( e.getMessage(), e );
283                        throw new DatastoreException( e.getMessage() );
284                    }
285                }
286    
287                if ( isFeatureTypeSupported( getFeature, wfs.getUrl() ) ) {
288    
289                    InputStream is = null;
290                    FeatureCollection fc = null;
291                    try {
292                        // perform GetFeature request against cascaded WFS
293                        HttpClient client = new HttpClient();
294                        client = WebUtils.enableProxyUsage( client, url );
295                        client.getHttpConnectionManager().getParams().setSoTimeout( wfs.getTimeout() );
296                        PostMethod post = new PostMethod( url.toExternalForm() );
297                        StringRequestEntity se = new StringRequestEntity( getFeature.getAsString(), "text/xml",
298                                                                          CharsetUtils.getSystemCharset() );
299                        post.setRequestEntity( se );
300                        client.executeMethod( post );
301                        is = post.getResponseBodyAsStream();
302                    } catch ( Exception e ) {
303                        throw new DatastoreException( Messages.getMessage( "DATASTORE_WFS_ACCESS", url ) );
304                    }
305    
306                    // read result as GMLFeatureColllection
307                    GMLFeatureCollectionDocument fcd = new GMLFeatureCollectionDocument( true );
308                    try {
309                        fcd.load( is, url.toExternalForm() );
310                    } catch ( Exception e ) {
311                        if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
312                            ByteArrayOutputStream bos = new ByteArrayOutputStream( 50000 );
313                            int c = 0;
314                            while ( c > -1 ) {
315                                c = is.read();
316                                bos.write( c );
317                            }
318                            byte[] b = bos.toByteArray();
319                            bos.close();
320                            System.out.println( new String( b ) );
321                        }
322                        LOG.logError( e.getMessage(), e );
323                        throw new DatastoreException( e.getMessage() );
324                    } finally {
325                        try {
326                            is.close();
327                        } catch ( IOException shouldNeverHappen ) {
328                        }
329                    }
330    
331                    // filter result if necessary
332                    XSLTDocument outFilter = wfs.getOutFilter();
333                    if ( outFilter != null ) {
334                        try {
335                            XMLFragment xml = outFilter.transform( fcd );
336                            fcd = new GMLFeatureCollectionDocument();
337                            fcd.setRootElement( xml.getRootElement() );
338                        } catch ( TransformerException e ) {
339                            LOG.logError( e.getMessage(), e );
340                            throw new DatastoreException( e.getMessage() );
341                        }
342                    }
343                    try {
344                        fc = fcd.parse();
345                    } catch ( XMLParsingException e ) {
346                        LOG.logError( e.getMessage(), e );
347                        throw new DatastoreException( e.getMessage() );
348                    }
349    
350                    return fc;
351                } else {
352                    return FeatureFactory.createFeatureCollection( "ID", 1 );
353                }
354            }
355    
356            /**
357             * returns true if the WFS reachable through the passed URL supports all feature types
358             * targeted by the passed GetFeature request.
359             * 
360             * @param getFeature
361             * @param url
362             * @return
363             * @throws OGCWebServiceException
364             * @throws DatastoreException
365             */
366            private boolean isFeatureTypeSupported( XMLFragment getFeature, URL url )
367                                    throws OGCWebServiceException, DatastoreException {
368    
369                WFSCapabilities caps = getWFSCapabilities( url );
370    
371                GetFeature gf = GetFeature.create( "ID" + System.currentTimeMillis(), getFeature.getRootElement() );
372                Query[] queries = gf.getQuery();
373                for ( int i = 0; i < queries.length; i++ ) {
374                    QualifiedName featureType = queries[i].getTypeNames()[0];
375                    if ( caps.getFeatureTypeList().getFeatureType( featureType ) == null ) {
376                        return false;
377                    }
378                }
379                return true;
380            }
381        }
382    }