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