    001    // $HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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    }