001    // $HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/portal/standard/csw/control/SimpleSearchListener.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.csw.control;
038    
039    import java.io.BufferedReader;
040    import java.io.InputStream;
041    import java.io.InputStreamReader;
042    import java.io.Reader;
043    import java.io.StringReader;
044    import java.net.URL;
045    import java.util.ArrayList;
046    import java.util.HashMap;
047    import java.util.Iterator;
048    import java.util.List;
049    import java.util.Map;
050    
051    import javax.servlet.http.HttpServletRequest;
052    import javax.servlet.http.HttpSession;
053    
054    import org.apache.commons.httpclient.HttpClient;
055    import org.apache.commons.httpclient.methods.PostMethod;
056    import org.apache.commons.httpclient.methods.StringRequestEntity;
057    import org.deegree.enterprise.WebUtils;
058    import org.deegree.enterprise.control.AbstractListener;
059    import org.deegree.enterprise.control.FormEvent;
060    import org.deegree.enterprise.control.RPCException;
061    import org.deegree.enterprise.control.RPCFactory;
062    import org.deegree.enterprise.control.RPCMember;
063    import org.deegree.enterprise.control.RPCMethodCall;
064    import org.deegree.enterprise.control.RPCParameter;
065    import org.deegree.enterprise.control.RPCStruct;
066    import org.deegree.enterprise.control.RPCWebEvent;
067    import org.deegree.framework.log.ILogger;
068    import org.deegree.framework.log.LoggerFactory;
069    import org.deegree.framework.util.CharsetUtils;
070    import org.deegree.framework.util.StringTools;
071    import org.deegree.framework.xml.DOMPrinter;
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.ogcbase.CommonNamespaces;
078    import org.deegree.portal.standard.csw.CatalogClientException;
079    import org.deegree.portal.standard.csw.MetadataTransformer;
080    import org.deegree.portal.standard.csw.configuration.CSWClientConfiguration;
081    import org.deegree.portal.standard.csw.model.DataSessionRecord;
082    import org.w3c.dom.Document;
083    import org.w3c.dom.Element;
084    import org.w3c.dom.Node;
085    
086    /**
087     * A <code>${type_name}</code> class.<br/>
088     * This listener does more than just search for data. It searches for data *and* then searches if there are services
089     * (WMS, WFS) available, which provide this data.
090     * 
091     * @author <a href="mailto:mays@lat-lon.de">Judit Mays</a>
092     * @author last edited by: $Author: apoth $
093     * 
094     * @version $Revision: 27569 $, $Date: 2010-10-28 08:57:50 +0200 (Do, 28 Okt 2010) $
095     */
096    public class SimpleSearchListener extends AbstractListener {
097    
098        private static final ILogger LOG = LoggerFactory.getLogger( SimpleSearchListener.class );
099    
100        /**
101         * used in jsp pages
102         */
103        public static final String HTML_FRAGMENT = "HTML_FRAGMENT"; // needs to be public for jsp pages
104    
105        static final String RESULT_SEARCH = "RESULT_SEARCH";
106    
107        static final String RPC_CATALOG = "catalog";
108    
109        static final String RPC_FORMAT = "RPC_FORMAT"; // ISO19115, ISO19119, ...
110    
111        static final String SESSION_AVAILABLESERVICECATALOGS = "AVAILABLESERVICECATALOGS";
112    
113        static final String SESSION_DATARECORDS = "DATARECORDS";
114    
115        static final String SESSION_REQUESTFORRESULTS = "SESSION_REQUESTFORRESULTS";
116    
117        static final String SESSION_RESULTFORHITS = "SESSION_RESULTFORHITS";
118    
119        protected CSWClientConfiguration config = null;
120    
121        // protected Node nsNode = null;
122        protected NamespaceContext nsContext = CommonNamespaces.getNamespaceContext();
123    
124        @Override
125        public void actionPerformed( FormEvent event ) {
126    
127            RPCWebEvent rpcEvent = (RPCWebEvent) event;
128            HttpSession session = ( (HttpServletRequest) this.getRequest() ).getSession( true );
129            config = (CSWClientConfiguration) session.getAttribute( Constants.CSW_CLIENT_CONFIGURATION );
130    
131            try {
132                validateRequest( rpcEvent );
133            } catch ( Exception e ) {
134                gotoErrorPage( Messages.getMessage( "IGEO_STD_CSW_INVALID_RPC_REQ", e.getMessage() ) );
135                LOG.logError( e.getMessage(), e );
136                return;
137            }
138    
139            List rpcCatalogs = null;
140            RPCStruct rpcStruct = null;
141            String rpcFormat = null;
142            String rpcProtocol = null;
143    
144            try {
145                rpcCatalogs = extractRPCCatalogs( rpcEvent );
146                rpcStruct = extractRPCStruct( rpcEvent, 1 );
147                rpcFormat = (String) extractRPCMember( rpcStruct, RPC_FORMAT );
148                LOG.logDebug( "rpcFormat: " + rpcFormat );
149                rpcProtocol = (String) extractRPCMember( rpcStruct, Constants.RPC_PROTOCOL );
150                LOG.logDebug( "rpcProtocol: " + rpcProtocol );
151            } catch ( Exception e ) {
152                gotoErrorPage( Messages.getMessage( "IGEO_STD_CSW_INVALID_RPC_EVENT", e.getMessage() ) );
153                LOG.logError( e.getMessage(), e );
154                return;
155            }
156    
157            // for further use in TurnPageListener
158            session.setAttribute( Constants.RPC_PROTOCOL, rpcProtocol );
159    
160            // first "GetRecords"-request (resultType="HITS", typeNames="dataset")
161            String req = null;
162            HashMap resultHits = null;
163            try {
164                req = createRequest( rpcStruct, rpcFormat, "HITS" );
165                LOG.logDebug( "First GetRecords Request:\n", new XMLFragment( (Reader) new StringReader( req ),
166                                                                              XMLFragment.DEFAULT_URL ).getAsPrettyString() );
167            } catch ( Exception e ) {
168                gotoErrorPage( Messages.getMessage( "IGEO_STD_CSW_INVALID_HITS_REQ", e.getMessage() ) );
169                LOG.logError( e.getMessage(), e );
170                return;
171            }
172            try {
173                resultHits = performRequest( rpcProtocol, req, rpcCatalogs );
174                LOG.logDebug( "Result length for HITS is: ", resultHits.keySet().size() );
175                // save to session for further use in TurnPageListener
176                session.setAttribute( SESSION_RESULTFORHITS, resultHits );
177            } catch ( Exception e ) {
178                gotoErrorPage( Messages.getMessage( "IGEO_STD_CSW_SERVER_ERROR", e.getMessage() ) );
179                LOG.logError( e.getMessage(), e );
180                return;
181            }
182    
183            int hits = 0;
184            try {
185                hits = numberOfMatchesInMap( resultHits );
186            } catch ( Exception e ) {
187                gotoErrorPage( Messages.getMessage( "IGEO_STD_CSW_INVALID_RESULT", e.getMessage() ) );
188                LOG.logError( e.getMessage(), e );
189                return;
190            }
191    
192            HashMap resultResults = null;
193            List dsrListSearch = null;
194            Map availableServiceCatalogsMap = null;
195            if ( hits > 0 ) {
196    
197                // second "GetRecords"-request (resultType="RESULTS", typeNames="dataset")
198                req = null;
199                try {
200                    req = createRequest( rpcStruct, rpcFormat, "RESULTS" );
201                    LOG.logDebug( "Second GetRecords Request :\n", req );
202                    // if resultType="RESULT", then save request to session to be able to perform
203                    // the request again and again for more of the google-like pages (TurnPageListener)
204                    session.setAttribute( SESSION_REQUESTFORRESULTS, req );
205                } catch ( Exception e ) {
206                    gotoErrorPage( Messages.getMessage( "IGEO_STD_CSW_INVALID_RESULTS_REQ", e.getMessage() ) );
207                    LOG.logError( e.getMessage(), e );
208                    return;
209                }
210                try {
211                    resultResults = performRequest( rpcProtocol, req, rpcCatalogs );
212                    LOG.logDebug( "Result length for RESULTS is: ", resultResults.keySet().size() );
213                } catch ( Exception e ) {
214                    gotoErrorPage( Messages.getMessage( "IGEO_STD_CSW_SERVER_ERROR", e.getMessage() ) );
215                    LOG.logError( e.getMessage(), e );
216                    return;
217                }
218    
219                // create data session records for results and store them in the session
220                try {
221                    dsrListSearch = createDataSessionRecords( resultResults );
222                    LOG.logDebug( "dsrListSearch length=", dsrListSearch.size() );
223                } catch ( CatalogClientException e ) {
224                    gotoErrorPage( Messages.getMessage( "IGEO_STD_CSW_ERROR_CREATE_DSRLIST", e.getMessage() ) );
225                    LOG.logError( e.getMessage(), e );
226                    return;
227                }
228    
229                try {
230                    // TODO make usable for other formats, not only "ISO19119"
231                    LOG.logDebug( "doService for ISO19119 HITS" );
232                    availableServiceCatalogsMap = doServiceSearch( resultResults, "ISO19119", "HITS" );
233                } catch ( CatalogClientException e ) {
234                    gotoErrorPage( Messages.getMessage( "IGEO_STD_CSW_ERROR_CREATE_SEARCH_RESULTS", e.getMessage() ) );
235                    LOG.logError( e.getMessage(), e );
236                    return;
237                }
238            }
239            // save List of data session records to session (may be null or empty)
240            session.setAttribute( SESSION_DATARECORDS, dsrListSearch );
241            // save Map of available service catalogs to session (may be null or empty)
242            session.setAttribute( SESSION_AVAILABLESERVICECATALOGS, availableServiceCatalogsMap );
243    
244            // handle result: take result and transform it to produce html output
245            String fileName = "csw/metaList2html.xsl"; // default value
246            // FIXME replace format with current value
247            HashMap xslMap = config.getProfileXSL( "Profiles." + rpcFormat );
248            if ( xslMap != null ) {
249                if ( xslMap.get( "brief" ) != null ) {
250                    fileName = (String) xslMap.get( "brief" );
251                }
252            }
253    
254            try {
255                String pathToFile = "file:" + getHomePath() + "WEB-INF/conf/igeoportal/" + fileName;
256                LOG.logDebug( "path to file metList2Html: ", pathToFile );
257                handleResult( resultHits, resultResults, pathToFile );
258            } catch ( Exception e ) {
259                LOG.logError( e.getMessage(), e );
260                gotoErrorPage( Messages.getMessage( "IGEO_STD_CSW_ERROR_HANDLE_RESULT", e.getMessage() ) );
261                return;
262            }
263    
264            return;
265        }
266    
267        // /**
268        // *
269        // * @param session
270        // * @param dsrListSearch
271        // */
272        // private void addDataSessionRecordsToSessionAttrib( HttpSession session, List dsrListSearch )
273        // {
274        //
275        //
276        // List dsrListSession;
277        // if ( session.getAttribute( Constants.SESSION_DATARECORDS ) != null ) {
278        // dsrListSession = (ArrayList)session.getAttribute( Constants.SESSION_DATARECORDS );
279        // for( int i = 0; i < dsrListSearch.size(); i++ ) {
280        // if ( ! dsrListSession.contains( dsrListSearch.get(i) ) ) {
281        // dsrListSession.add( dsrListSearch );
282        // }
283        // }
284        // } else {
285        // dsrListSession = new ArrayList( dsrListSearch.size() );
286        // dsrListSession.addAll( dsrListSearch );
287        // }
288        // session.setAttribute( Constants.SESSION_DATARECORDS, dsrListSession );
289        //
290        //
291        // }
292    
293        /**
294         * Validates the rpc request and makes sure that all the needed parameters are included
295         * 
296         * @param rpcEvent
297         * @throws CatalogClientException
298         */
299        protected void validateRequest( RPCWebEvent rpcEvent )
300                                throws CatalogClientException {
301    
302            RPCParameter[] params = extractRPCParameters( rpcEvent );
303    
304            // validity check for number of parameters in RPCMethodCall
305            if ( params.length != 2 ) {
306                throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_WRONG_PARAMS_NUMBER", "2",
307                                                                       params.length ) );
308            }
309    
310            RPCStruct rpcStruct = extractRPCStruct( rpcEvent, 1 );
311            String rpcFormat = (String) extractRPCMember( rpcStruct, RPC_FORMAT );
312            String rpcProtocol = (String) extractRPCMember( rpcStruct, Constants.RPC_PROTOCOL );
313            if ( rpcFormat == null || rpcProtocol == null ) {
314                throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_ERROR_PARAMS_NOT_SET" ) );
315            }
316    
317            // go through each catalog of the rpc and validate
318            List rpcCatalogs = extractRPCCatalogs( rpcEvent );
319            String rpc_catalog = null;
320            for ( int i = 0; i < rpcCatalogs.size(); i++ ) {
321                rpc_catalog = (String) rpcCatalogs.get( i );
322    
323                // validity check for catalog
324                String[] catalogs = config.getCatalogNames();
325                boolean containsCatalog = false;
326                for ( int j = 0; j < catalogs.length; j++ ) {
327                    if ( catalogs[j].equals( rpc_catalog ) ) {
328                        containsCatalog = true;
329                    }
330                }
331                if ( !containsCatalog ) {
332                    throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_WRONG_CAT", rpc_catalog ) );
333                }
334    
335                // validity check for format
336                // is requested catalog capable to serve requested metadata format?
337                List formats = config.getCatalogFormats( rpc_catalog );
338                if ( formats == null || !formats.contains( rpcFormat ) ) {
339                    throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_WRONG_FORMAT", rpc_catalog,
340                                                                           rpcFormat ) );
341                }
342    
343                // validity check for protocol
344                // is requested catalog reachable through requested protocol?
345                List protocols = config.getCatalogProtocols( rpc_catalog );
346                if ( !protocols.contains( rpcProtocol ) ) {
347                    throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_WRONG_PROTOCOL", rpc_catalog,
348                                                                           rpcProtocol ) );
349                }
350            }
351    
352            return;
353        }
354    
355        /**
356         * This method creates a csw request with the RequestFactory of the passed format, using the paramter values passed
357         * in the rpcStruct. (csw:GetRecords request, if the passed resultType is HITS or RESULTS, and csw:GetRecordsById
358         * request, if the passed resultType is null).
359         * 
360         * @param rpcStruct
361         *            The struct contains parameter values to be used in the RequestFactory.
362         * @param format
363         *            The format determines the RequestFactory to be used.
364         * @param resultType
365         *            The type of desired result. Possible values are HITS or RESULTS or null.
366         * @return Returns the xml encoded request as <code>String</code>.
367         * @throws CatalogClientException
368         */
369        protected String createRequest( RPCStruct rpcStruct, String format, String resultType )
370                                throws CatalogClientException {
371    
372            CSWRequestFactory fac = RequestFactoryFinder.findFactory( format );
373            fac.setConfiguration( config );
374            String request = fac.createRequest( rpcStruct, resultType );
375    
376            return request;
377        }
378    
379        /**
380         * Performs the CSW request and returns the result as a HashMap
381         * 
382         * @param protocol
383         * @param request
384         * @param catalogs
385         * @return Returns a <code>HashMap</code>, which contains one key-value-pair for each catalogue, that has been
386         *         searched. The key is the name of the catalogue. The value is the doc Document, that contains the number
387         *         of matches (resultType="HITS"), or 1 to n metadata entries (resultType="RESULTS")
388         * @throws CatalogClientException
389         */
390        protected HashMap performRequest( String protocol, String request, List catalogs )
391                                throws CatalogClientException {
392    
393            HashMap<String, Document> result = new HashMap<String, Document>();
394    
395            // loop for all catalogues contained in catalogs
396            for ( int i = 0; i < catalogs.size(); i++ ) {
397                boolean useSOAP = false;
398                List list = config.getCatalogProtocols( (String) catalogs.get( i ) );
399                LOG.logDebug( "Catalog List length=", list.size() );
400                if ( protocol != null ) {
401                    if ( !list.contains( protocol ) ) {
402                        throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_UNSUPPORTED_PROTOCOL" ) );
403                    }
404                    useSOAP = "SOAP".equals( protocol );
405                } else {
406                    for ( int j = 0; j < list.size(); j++ ) {
407                        if ( "SOAP".equals( list.get( j ) ) ) {
408                            useSOAP = true;
409                            break;
410                        }
411                    }
412                }
413    
414                String cswAddress = config.getCatalogServerAddress( (String) catalogs.get( i ) );
415                if ( cswAddress == null ) {
416                    throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_WRONG_SERVER_ADDR" ) );
417                }
418    
419                try {
420                    if ( useSOAP ) {
421                        // TODO test if this SOAP is working properly
422                        StringBuffer soapRequest = new StringBuffer( 5000 );
423                        soapRequest.append( "<soap:Envelope " );
424                        soapRequest.append( "xmlns:soap=\"http://www.w3.org/2001/12/soap-envelope\" " );
425                        soapRequest.append( "soap:encodingStyle=\"http://www.w3.org/2001/12/soap-encoding\">" );
426                        soapRequest.append( request ).append( "</soap:Envelope>" );
427    
428                        request = soapRequest.toString();
429                    }
430    
431                    // send post request
432                    HttpClient httpclient = new HttpClient();
433                    LOG.logDebug( "CSW Address: " + cswAddress );
434                    httpclient = WebUtils.enableProxyUsage( httpclient, new URL( cswAddress ) );
435                    httpclient.getHttpConnectionManager().getParams().setSoTimeout( 30000 );
436                    PostMethod postMethod = new PostMethod( cswAddress );
437                    postMethod.setRequestEntity( new StringRequestEntity( request, "text/xml",
438                                                                          CharsetUtils.getSystemCharset() ) );
439                    httpclient.executeMethod( postMethod );
440                    // String resp = postMethod.getResponseBodyAsString();
441                    // Document doc = XMLTools.parse( new StringReader( resp ) );
442    
443                    // Using XMLFragment instead of XMLTools to avoid encoding problems
444                    XMLFragment frag = new XMLFragment();
445                    frag.load( postMethod.getResponseBodyAsStream(), cswAddress );
446                    Document doc = frag.getRootElement().getOwnerDocument();
447    
448                    Element root = doc.getDocumentElement();
449                    if ( root.getLocalName().equals( "ExceptionReport" ) ) {
450                        LOG.logError( "CSW Error\n " + new XMLFragment( root ).getAsPrettyString() );
451                        throw new CatalogClientException( extractException( root ) );
452                    } else if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
453                        LOG.logDebug( "\nResponse: \n", new XMLFragment( root ).getAsPrettyString() );
454                    }
455    
456                    // write key-value-pair to HashMap
457                    result.put( (String) catalogs.get( i ), doc );
458    
459                } catch ( Exception e ) {
460                    LOG.logError( e.getMessage(), e );
461                    throw new CatalogClientException( e.getMessage() );
462                }
463            }
464            return result;
465        }
466    
467        /**
468         * Handles the result of the search requests and transforms the result to HTML using xslt, then saves the
469         * transformed result into the session
470         * 
471         * @param resultHits
472         * @param resultResults
473         * @param pathToXslFile
474         *            e.g. file://$iGeoPortal_home$/WEB-INF/conf/igeoportal/metaList2html.xsl
475         * @throws XMLParsingException
476         *             if the documents contained in resultHits don't have the expected structure.
477         * @throws CatalogClientException
478         */
479        protected void handleResult( Object resultHits, Object resultResults, String pathToXslFile )
480                                throws XMLParsingException, CatalogClientException {
481    
482            Map map = ( resultResults != null ) ? (HashMap) resultResults : (HashMap) resultHits;
483            if ( resultResults != null ) {
484                LOG.logDebug( "Resultset Result will be used" );
485            } else {
486                LOG.logDebug( "Resultset HITS will be used" );
487            }
488            Iterator it = map.keySet().iterator();
489    
490            URL u = null;
491            StringBuffer htmlFragment = new StringBuffer( 5000 );
492            MetadataTransformer mt = null;
493            try {
494                u = new URL( pathToXslFile );
495                mt = new MetadataTransformer( u.getFile() );
496            } catch ( Exception e ) {
497                throw new CatalogClientException( e.getMessage() );
498            }
499    
500            String catalog = null;
501            Document doc = null;
502            String docString = null;
503            // one loop for each catalog in result ( where resultType="RESULTS" )
504            while ( it.hasNext() ) {
505                catalog = (String) it.next();
506                doc = (Document) map.get( catalog );
507                LOG.logDebug( "Document to transform to html: \n",
508                              new XMLFragment( doc.getDocumentElement() ).getAsPrettyString() );
509                docString = DOMPrinter.nodeToString( doc, CharsetUtils.getSystemCharset() );
510                Reader reader = new StringReader( docString );
511    
512                // need to get numberOfRecordsMatched from the document where resultType is "HITS",
513                // because only there the number is correct.
514                // In document where resultType="RESULTS" the numberOfRecordsMatched is always equal to
515                // numberOfRecordReturned, which is LESS OR EQUAL to the correct numberOfRecordsMatched.
516                int matches = numberOfMatchesInDoc( (Document) ( (HashMap) resultHits ).get( catalog ) );
517    
518                // need to get startPosition from the request, because the value 'nextRec' in
519                // GetRecordsResponse is always '0', which is not always correct ;o)
520                int startPos = 0;
521                // load request from session
522                HttpSession session = ( (HttpServletRequest) this.getRequest() ).getSession( true );
523                String req = (String) session.getAttribute( SESSION_REQUESTFORRESULTS );
524                if ( req != null ) {
525                    try {
526                        StringReader sr = new StringReader( req );
527                        Document docReq = XMLTools.parse( sr );
528                        startPos = Integer.parseInt( docReq.getDocumentElement().getAttribute( "startPosition" ) );
529                    } catch ( Exception e ) {
530                        throw new CatalogClientException( e.getMessage() );
531                    }
532                }
533    
534                // transformation
535                try {
536                    String transformedMetaData = mt.transformMetadata( reader, catalog, null, matches, startPos, "list" );
537                    htmlFragment.append( transformedMetaData );
538                    LOG.logDebug( "transformed html fragment:\n", htmlFragment.toString() );
539                } catch ( Exception e ) {
540                    throw new CatalogClientException( e.getMessage() );
541                }
542            }
543    
544            this.getRequest().setAttribute( RESULT_SEARCH, resultResults );
545            this.getRequest().setAttribute( HTML_FRAGMENT, htmlFragment.toString() );
546    
547        }
548    
549        /**
550         * Created a list of DataSessionRecord from all the returned results
551         * 
552         * @param results
553         * @return Returns a List of distinct DataSessionRecords for all metadata elements within the passed results.
554         * @throws CatalogClientException
555         *             if the identifier or the title of a metadata element could not be extracted.
556         */
557        protected List<DataSessionRecord> createDataSessionRecords( HashMap results )
558                                throws CatalogClientException {
559    
560            List<DataSessionRecord> dsrList = new ArrayList<DataSessionRecord>();
561    
562            Iterator it = results.keySet().iterator();
563            while ( it.hasNext() ) {
564                String catalog = (String) it.next();
565                Document doc = (Document) results.get( catalog );
566    
567                List mdList; // list of unique metadata element nodes
568                try {
569                    mdList = extractMetadata( doc );
570                } catch ( Exception e ) {
571                    throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_ERROR_EXTRACT_MD_ELEMS",
572                                                                           e.getMessage() ) );
573                }
574    
575                for ( int j = 0; j < mdList.size(); j++ ) {
576                    Node mdNode = (Node) mdList.get( j );
577    
578                    String xPathToId = getXPathToId( (Element) mdNode );
579                    String xPathToTitle = getXPathToTitle( (Element) mdNode );
580                    LOG.logDebug( "xPathToId=", xPathToId );
581                    LOG.logDebug( "xPathToDataTitle=", xPathToTitle );
582                    String id = null;
583                    String title = null;
584                    try {
585                        id = extractValue( mdNode, xPathToId );
586                        LOG.logDebug( "extracted id=", id );
587                        title = extractValue( mdNode, xPathToTitle );
588                        LOG.logDebug( "extracted title=", title );
589                    } catch ( Exception e ) {
590                        throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_ERROR_EXTRACT_TITLE_IDENT",
591                                                                               e.getMessage() ) );
592                    }
593                    DataSessionRecord dsr = new DataSessionRecord( id, catalog, title );
594    
595                    if ( !dsrList.contains( dsr ) ) {
596                        // this should be a redundant check. testing just in case...
597                        dsrList.add( dsr );
598                    }
599                }
600            }
601    
602            return dsrList;
603        }
604    
605        /**
606         * Extracts all Metadata nodes from the passed csw:GetRecordsResponse Document.
607         * 
608         * @param doc
609         *            The csw:GetRecordsResponse Document from which to extract the Metadata nodes.
610         * @return Returns a NodeList of Metadata Elements for the passed Document.
611         * @throws CatalogClientException
612         *             if metadata nodes could not be extracted from the passed Document.
613         * @throws XMLParsingException
614         */
615        protected List extractMetadata( Document doc )
616                                throws CatalogClientException, XMLParsingException {
617    
618            List nl = null;
619    
620            String xPathToMetadata = "csw202:GetRecordsResponse/csw202:SearchResults/child::*";
621            // nl = XMLTools.getXPath( xPathToMetadata, doc, nsNode ); // old
622            nl = XMLTools.getNodes( doc, xPathToMetadata, nsContext ); // new
623            if ( nl == null || nl.size() < 1 ) {
624                throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_ERROR_EXTRACT_MD_NODES" ) );
625            }
626    
627            return nl;
628        }
629    
630        /**
631         * Extracts node value
632         * 
633         * @param node
634         * @param xPath
635         * @return Returns the value for the passed node and xPath.
636         * @throws CatalogClientException
637         * @throws XMLParsingException
638         */
639        protected String extractValue( Node node, String xPath )
640                                throws CatalogClientException, XMLParsingException {
641    
642            String s = XMLTools.getNodeAsString( node, xPath, nsContext, null );
643            if ( s == null ) {
644                throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_ERROR_NO_VALUE_FOUND", xPath ) );
645            }
646    
647            return s;
648        }
649    
650        /**
651         * The number of matches returned is the number of matches for all catalogs added together.
652         * 
653         * @param result
654         *            The HashMap containing the result document from the performed request.
655         * @return Returns the number of matches indicated in the result.
656         * @throws XMLParsingException
657         *             if the result document in the passed HashMap does not contain the expected nodes and attributes.
658         */
659        private int numberOfMatchesInMap( HashMap result )
660                                throws XMLParsingException {
661    
662            int hits = 0;
663    
664            Iterator iterator = result.keySet().iterator();
665            while ( iterator.hasNext() ) {
666                String catalog = (String) iterator.next();
667                Document doc = (Document) result.get( catalog ); // result(value)
668                Element root = doc.getDocumentElement();
669                if ( root.getLocalName().equals( "ExceptionReport" ) ) {
670                    LOG.logError( "csw Error\n " + new XMLFragment( root ).getAsPrettyString() );
671                    throw new XMLParsingException( extractException( root ) );
672                }
673                int matches = numberOfMatchesInDoc( doc );
674                hits += matches;
675            }
676    
677            return hits;
678        }
679    
680        /**
681         * Extracts the exception message from a given ows:Excpetion xml fragment
682         * 
683         * @param root
684         * 
685         * @return the error message
686         * 
687         * @throws XMLParsingException
688         */
689        private String extractException( Element root )
690                                throws XMLParsingException {
691            return XMLTools.getRequiredNodeAsString( root, "./ows:Exception", nsContext );
692        }
693    
694        /**
695         * The number of matches returned is the number for one single catalog.
696         * 
697         * @param doc
698         *            The Document containing the result for one catalog.
699         * @return Returns the number of matches for one catalog only.
700         * @throws XMLParsingException
701         */
702        private int numberOfMatchesInDoc( Document doc )
703                                throws XMLParsingException {
704    
705            Element docElement = doc.getDocumentElement(); // root element
706    
707            if ( docElement.getLocalName().equals( "ExceptionReport" ) ) {
708                LOG.logError( "csw Error\n " + new XMLFragment( docElement ).getAsPrettyString() );
709                throw new XMLParsingException( extractException( docElement ) );
710            }
711            Element searchResults = (Element) XMLTools.getRequiredNode( docElement,
712                                                                        StringTools.concat( 100, "./",
713                                                                                            CommonNamespaces.CSW202_PREFIX,
714                                                                                            ":SearchResults" ), nsContext );
715            String matches = XMLTools.getRequiredAttrValue( "numberOfRecordsMatched", null, searchResults );
716    
717            return Integer.parseInt( matches );
718        }
719    
720        /**
721         * Invokes a GetRecords request with type RESULT to get the actual records for all the metadata returned during the
722         * GetRecords request with type HIT
723         * 
724         * @param result
725         *            HashMap containing data catalog names (as keys) and GetRecordResponse Documents (as values).
726         * @param format
727         *            some service format like "ISO19119"
728         * @param resultType
729         *            either "HITS" or "RESULTS".
730         * @return Returns a Map that contains the title extracted from the passed document (as key) and a List of all
731         *         corresponding available service catalogs (as value).
732         * @throws CatalogClientException
733         */
734        protected Map doServiceSearch( HashMap result, String format, String resultType )
735                                throws CatalogClientException {
736    
737            List serviceCatalogs = config.getServiceMetadataCatalogs();
738            Map<String, List<String>> availableServiceCatalogsMap = new HashMap<String, List<String>>( 10 );
739    
740            // for each kvp (i.e. for each data catalog) in result do the following loop
741            Iterator iterator = result.keySet().iterator();
742            while ( iterator.hasNext() ) {
743                String catalog = (String) iterator.next(); // result(key)
744                Document doc = (Document) result.get( catalog ); // result(value)
745    
746                List nl; // list of unique metadata element nodes
747                try {
748                    nl = extractMetadata( doc );
749                } catch ( Exception e ) {
750                    throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_ERROR_EXTRACT_METADATA",
751                                                                           e.getMessage() ) );
752                }
753                List<String> idList = new ArrayList<String>( nl.size() );
754    
755                // csw: 2.0.0
756                /*
757                 * if ( "full".equals( elementSet ) ) { xPathToTitle = config.getXPathToDataTitleFull(); }
758                 */
759    
760                // get list of titles for current catalog
761                // REASON: the link between iso19115 and iso19119 is the "title".
762                // TODO make this work for other formats where the link is something else completely.
763                try {
764                    for ( int i = 0; i < nl.size(); i++ ) {
765                        idList.add( extractValue( (Node) nl.get( i ), getXPathToId( (Element) nl.get( i ) ) ) );
766                    }
767                } catch ( Exception e ) {
768                    throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_ERROR_EXTRACT_TITLE",
769                                                                           e.getMessage() ) );
770                }
771    
772                // for each title
773                for ( int i = 0; i < idList.size(); i++ ) {
774                    // DO SERVICE SEARCH
775                    // simple search had results (number of hits > 0), so a getRecords request with
776                    // resulttype=RESULTS was done and now a service search is needed for those results.
777    
778                    // get the service info: search a service for the current title
779                    RPCStruct serviceStruct = null;
780                    String template = "CSWServiceSearchRPCMethodCallTemplate.xml";
781                    try {
782                        serviceStruct = createRpcStructForServiceSearch( template, idList.get( i ) );
783                    } catch ( Exception e ) {
784                        throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_INVALID_STRUCT",
785                                                                               e.getMessage() ) );
786                    }
787                    String serviceReq = null;
788                    try {
789                        serviceReq = createRequest( serviceStruct, format, resultType );
790                        LOG.logDebug( "created service request=\n", serviceReq );
791                    } catch ( Exception e ) {
792                        throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_INVALID_REQ", e.getMessage() ) );
793                    }
794    
795                    HashMap serviceResult = null;
796                    try {
797                        serviceResult = performRequest( null, serviceReq, serviceCatalogs );
798                    } catch ( Exception e ) {
799                        throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_SERVER_ERROR", e.getMessage() ) );
800                    }
801                    List<String> availableServiceCatalogs = null;
802                    // get service catalogs that are available for the current dataTitle
803                    try {
804                        availableServiceCatalogs = extractAvailableServiceCatalogs( serviceResult );
805                    } catch ( XMLParsingException e ) {
806                        throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_INVALID_RESULT",
807                                                                               e.getMessage() ) );
808                    }
809                    availableServiceCatalogsMap.put( idList.get( i ), availableServiceCatalogs );
810                } // end for each dataTitle
811            } // end for each kvp in result
812    
813            return availableServiceCatalogsMap;
814        }
815    
816        /**
817         * Extract all catalogs from serviceResult that actually do have the service available (= where number of records
818         * matched is greater than 0) and add them to the returned List.
819         * 
820         * @param serviceResult
821         * @return Returns a <code>List</code> of all available service catalogs. May be null.
822         * @throws XMLParsingException
823         */
824        private List<String> extractAvailableServiceCatalogs( Map serviceResult )
825                                throws XMLParsingException {
826    
827            List<String> availableServiceCatalogs = new ArrayList<String>( serviceResult.size() );
828    
829            // one loop for each catalog in serviceResult
830            Iterator it = serviceResult.keySet().iterator();
831            while ( it.hasNext() ) {
832                String servCatalog = (String) it.next();
833                Document servDoc = (Document) serviceResult.get( servCatalog );
834    
835                int matches = numberOfMatchesInDoc( servDoc );
836                if ( matches > 0 ) {
837                    availableServiceCatalogs.add( servCatalog );
838                }
839            }
840    
841            return ( availableServiceCatalogs.size() == 0 ) ? null : availableServiceCatalogs;
842        }
843    
844        /**
845         * Creates an RPC request from the given template
846         * 
847         * @param template
848         * @param identifier
849         * @return Returns the new rpcStruct.
850         * @throws CatalogClientException
851         * @throws RPCException
852         */
853        protected RPCStruct createRpcStructForServiceSearch( String template, String identifier )
854                                throws CatalogClientException, RPCException {
855    
856            RPCStruct rpcStruct = null;
857            InputStream is = SimpleSearchListener.class.getResourceAsStream( template );
858    
859            String rpc = null;
860            try {
861                InputStreamReader ireader = new InputStreamReader( is );
862                BufferedReader br = new BufferedReader( ireader );
863                StringBuffer sb = new StringBuffer( 50000 );
864                while ( ( rpc = br.readLine() ) != null ) {
865                    sb.append( rpc );
866                }
867                rpc = sb.toString();
868                br.close();
869            } catch ( Exception e ) {
870                throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_TEMPLATE_ERROR", e.getMessage() ) );
871            }
872    
873            // replace templates in struct with passed values
874            rpc = rpc.replaceAll( "\\$SEARCH", identifier );
875    
876            StringReader reader = new StringReader( rpc );
877            RPCMethodCall mc = RPCFactory.createRPCMethodCall( reader );
878            try {
879                RPCParameter[] params = mc.getParameters();
880                rpcStruct = (RPCStruct) params[0].getValue();
881            } catch ( Exception e ) {
882                throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_ERROR_EXTRACT_STRUCT_FROM_RPC",
883                                                                       e.getMessage() ) );
884            }
885    
886            return rpcStruct;
887        }
888    
889        /**
890         * Extracts the parameters from the method call element within the passed rpcEvent.
891         * 
892         * @param rpcEvent
893         * @return Returns the parameters as array of <code>RPCParameter</code>.
894         * @throws CatalogClientException
895         */
896        protected RPCParameter[] extractRPCParameters( RPCWebEvent rpcEvent )
897                                throws CatalogClientException {
898    
899            RPCParameter[] params;
900            try {
901                RPCMethodCall mc = rpcEvent.getRPCMethodCall();
902                params = mc.getParameters();
903            } catch ( Exception e ) {
904                throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_ERROR_EXTRACT_PARAMS_FROM_RPC",
905                                                                       e.getMessage() ) );
906            }
907            return params;
908        }
909    
910        /**
911         * Extracts the catalog names from the first parameter of the params element within the passed rpcEvent.
912         * 
913         * @param rpcEvent
914         * @return Returns the catalogue names as array of <code>String</code>.
915         * @throws CatalogClientException
916         */
917        protected List extractRPCCatalogs( RPCWebEvent rpcEvent )
918                                throws CatalogClientException {
919    
920            List<String> catalogs = new ArrayList<String>( 10 );
921            try {
922                RPCParameter[] params = extractRPCParameters( rpcEvent );
923                RPCParameter[] rpcCatalogs = (RPCParameter[]) params[0].getValue();
924                for ( int i = 0; i < rpcCatalogs.length; i++ ) {
925                    catalogs.add( (String) rpcCatalogs[i].getValue() );
926                }
927            } catch ( Exception e ) {
928                throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_ERROR_EXTRACT_CATNAME" ) );
929            }
930            return catalogs;
931        }
932    
933        /**
934         * Extracts the <code>RPCStruct</code> from the indicated parameter in the params element of the passed
935         * <code>RPCWebEvent</code>.
936         * 
937         * @param rpcEvent
938         *            The RPCWebEvent, that contains the RPCStruct to extract.
939         * @param index
940         *            The index of the parameter from which to extract the RPCStruct (starting with 0).
941         * @return Returns the <code>RPCStruct</code> from the indicated params element.
942         * @throws CatalogClientException
943         */
944        protected RPCStruct extractRPCStruct( RPCWebEvent rpcEvent, int index )
945                                throws CatalogClientException {
946            RPCStruct rpcStruct;
947            try {
948                RPCParameter[] params = extractRPCParameters( rpcEvent );
949                rpcStruct = (RPCStruct) params[index].getValue();
950            } catch ( Exception e ) {
951                throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_ERROR_EXTRACT_STRUCT_FROM_RPC",
952                                                                       e.getMessage() ) );
953            }
954            return rpcStruct;
955        }
956    
957        /**
958         * Extracts the member of the passed name from the passed struct.
959         * 
960         * @param struct
961         *            The rpcStruct to extract the passed member from.
962         * @param member
963         *            The Member to extract from the passed rpcStruct.
964         * @return Returns the member value object.
965         * @throws CatalogClientException
966         */
967        protected Object extractRPCMember( RPCStruct struct, String member )
968                                throws CatalogClientException {
969            RPCMember rpcMember;
970            try {
971                rpcMember = struct.getMember( member );
972            } catch ( Exception e ) {
973                throw new CatalogClientException( Messages.getMessage( "IGEO_STD_CSW_ERROR_EXTRACT_MEMBER", member,
974                                                                       e.getMessage() ) );
975            }
976            return rpcMember.getValue();
977        }
978    
979        /**
980         * Checks if the metadata is of series, dataset, application or service type
981         * 
982         * @param docElem
983         * @return "service": if the metadata is a service "dataset": if the metadata is a dataset "series" : if the
984         *         metadata is a datasetCollection (series)
985         */
986        protected String getMetadataType( Element docElem ) {
987            try {
988                return XMLTools.getAttrValue( XMLTools.getRequiredNode( docElem, "./gmd:hierarchyLevel/gmd:MD_ScopeCode",
989                                                                        nsContext ), null, "codeListValue", null );
990            } catch ( Exception e ) {
991                return null;
992            }
993        }
994    
995        /**
996         * Checks whether the metadata document is of a known type. The known types are "series", "dataset", "service" and
997         * "application"
998         * 
999         * @param docElem
1000         * @return true if it is a known metadata type, false otherwise
1001         */
1002        protected boolean isKnownMetadataType( Element docElem ) {
1003            String scopeCode = getMetadataType( docElem );
1004            if ( "series".equals( scopeCode ) || "dataset".equals( scopeCode ) || "service".equals( scopeCode )
1005                 || "application".equals( scopeCode ) ) {
1006                return true;
1007            }
1008            return false;
1009        }
1010    
1011        /**
1012         * @param docElem
1013         * @return xPath to the record identifier
1014         */
1015        protected String getXPathToId( Element docElem ) {
1016            String mdType = getMetadataType( docElem );
1017            if ( "series".equals( mdType ) || "dataset".equals( mdType ) || "application".equals( mdType ) ) {
1018                return config.getXPathToDataIdentifier();
1019            } else if ( "service".equals( mdType ) ) {
1020                return config.getXPathToServiceIdentifier();
1021            }
1022            return null;
1023        }
1024    
1025        /**
1026         * @param docElement
1027         * @return xPath to record title
1028         */
1029        protected String getXPathToTitle( Element docElement ) {
1030            String mdType = getMetadataType( docElement );
1031            if ( "series".equals( mdType ) || "dataset".equals( mdType ) || "application".equals( mdType ) ) {
1032                return config.getXPathToDataTitle();
1033            } else if ( "service".equals( mdType ) ) {
1034                return config.getXPathToServiceTitle();
1035            }
1036            return null;
1037        }
1038    
1039        // /**
1040        // * Extracts the member of the passed name from the passed struct.
1041        // *
1042        // * @param struct The rpcStruct to extract the passed member from.
1043        // * @param memberName The Member to extract from the passed rpcStruct.
1044        // * @return Returns the member value object.
1045        // * @throws CatalogClientException
1046        // */
1047        // protected Object extractRPCMemberValue( RPCStruct struct, String memberName )
1048        // throws CatalogClientException {
1049        // String memberValue = null;
1050        // try {
1051        // memberValue = (String) struct.getMember( memberName ).getValue();
1052        // } catch (Exception e) {
1053        // throw new CatalogClientException( "Cannot extract member value "+ memberName +" from
1054        // RPCStruct: "
1055        // + e.getMessage() );
1056        // }
1057        // return memberValue;
1058        // }
1059    
1060    }