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 }