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 }