001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/ogcwebservices/csw/manager/Manager.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2006 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/deegree/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Aennchenstr. 19
030     53115 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041    
042    
043     ---------------------------------------------------------------------------*/
044    package org.deegree.ogcwebservices.csw.manager;
045    
046    import java.io.IOException;
047    import java.io.StringReader;
048    import java.io.StringWriter;
049    import java.net.URISyntaxException;
050    import java.net.URL;
051    import java.util.ArrayList;
052    import java.util.Collection;
053    import java.util.HashMap;
054    import java.util.Iterator;
055    import java.util.List;
056    import java.util.Map;
057    
058    import javax.xml.transform.TransformerException;
059    
060    import org.deegree.framework.log.ILogger;
061    import org.deegree.framework.log.LoggerFactory;
062    import org.deegree.framework.util.StringTools;
063    import org.deegree.framework.xml.NamespaceContext;
064    import org.deegree.framework.xml.XMLFragment;
065    import org.deegree.framework.xml.XMLParsingException;
066    import org.deegree.framework.xml.XMLTools;
067    import org.deegree.framework.xml.XSLTDocument;
068    import org.deegree.ogcbase.CommonNamespaces;
069    import org.deegree.ogcwebservices.EchoRequest;
070    import org.deegree.ogcwebservices.MissingParameterValueException;
071    import org.deegree.ogcwebservices.OGCWebServiceException;
072    import org.deegree.ogcwebservices.csw.configuration.CatalogueConfiguration;
073    import org.deegree.ogcwebservices.csw.configuration.CatalogueDeegreeParams;
074    import org.deegree.ogcwebservices.csw.manager.HarvestRepository.ResourceType;
075    import org.deegree.ogcwebservices.wfs.WFService;
076    import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities;
077    import org.deegree.ogcwebservices.wfs.operation.transaction.TransactionResponse;
078    import org.w3c.dom.Element;
079    import org.xml.sax.SAXException;
080    
081    /**
082     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a>
083     * 
084     * @author last edited by: $Author: rbezema $
085     * 
086     * @version 2.0, $Revision: 7480 $, $Date: 2007-06-06 13:03:40 +0200 (Mi, 06 Jun 2007) $
087     */
088    
089    public class Manager {
090    
091        private static final ILogger LOG = LoggerFactory.getLogger( Manager.class );
092    
093        private static Map<ResourceType, AbstractHarvester> harvester = null;
094    
095        private static XSLTDocument IN_XSL = null;
096    
097        private static XSLTDocument OUT_XSL = null;
098    
099        private WFService wfsService;
100    
101        /**
102         * initializes a Manager instance
103         * 
104         * @param wfsService
105         * @param cswConfiguration
106         * @throws MissingParameterValueException
107         */
108        public Manager( WFService wfsService, CatalogueConfiguration cswConfiguration ) throws MissingParameterValueException {
109            this.wfsService = wfsService;
110    
111            // try {
112            CatalogueDeegreeParams cdp = cswConfiguration.getDeegreeParams();
113            URL url = cdp.getTransformationInputXSLT().getLinkage().getHref();
114            IN_XSL = new XSLTDocument();
115            try {
116                IN_XSL.load( url );
117            } catch ( IOException e ) {
118                String s = "If a CS-W is defined to handle Transaction and/or Harvest requests, XSLT scripts for request transformations (e.g. mapping the input schema to gml) must be defined in the deegreeParams section of the capabilities document. While trying to read an xslt script from: '" + url.toString()
119                           + "' (which was defined for the transformation of incoming request), the following error occurred: "
120                           + e.getMessage();
121                LOG.logError( s, e );
122                throw new MissingParameterValueException( getClass().getName(), s );
123            } catch ( SAXException e ) {
124                String s = "The xslt script (transforming incoming requests) read from the location: '" + url
125                           + "' could not be parsed because: "
126                           + e.getMessage();
127                LOG.logError( s, e );
128                throw new MissingParameterValueException( getClass().getName(), s );
129            }
130            url = cdp.getTransformationOutputXSLT().getLinkage().getHref();
131            OUT_XSL = new XSLTDocument();
132            try {
133                OUT_XSL.load( url );
134            } catch ( IOException e ) {
135                String s = "If a CS-W is defined to handle Transaction and/or Harvest requests, XSLT scripts for request transformations (e.g. mapping the input schema to gml) must be defined in the deegreeParams section of the capabilities document. While trying to read an xslt script from: '" + url.toString()
136                           + "' (which was defined for the transformation of the response), the following error occurred: "
137                           + e.getMessage();
138                LOG.logError( s, e );
139                throw new MissingParameterValueException( getClass().getName(), s );
140            } catch ( SAXException e ) {
141                String s = "The xslt script (transforming the response) read from the location: '" + url
142                           + "' could not be parsed because: "
143                           + e.getMessage();
144                LOG.logError( s, e );
145                throw new MissingParameterValueException( getClass().getName(), s );
146            }
147            WFSCapabilities capa = wfsService.getCapabilities();
148    
149            initHarvester();
150    
151            LOG.logInfo( "CSW Manager initialized with WFS resource, wfs version:" + capa.getVersion() );
152        }
153    
154        /**
155         * initializes a static Map containing a harvester for other coatalogues, for services and for single CSW-profile
156         * documents.
157         */
158        public static void initHarvester() {
159            if ( harvester == null ) {
160                harvester = new HashMap<ResourceType, AbstractHarvester>();
161                harvester.put( ResourceType.catalogue, CatalogueHarvester.getInstance() );
162                harvester.put( ResourceType.service, ServiceHarvester.getInstance() );
163                harvester.put( ResourceType.csw_profile, CSWProfileHarvester.getInstance() );
164            }
165        }
166    
167        /**
168         * starts all known/registered harvester. This method can be used to start harvesting using requests e.g. if a
169         * server has been shutdown and restarted.
170         */
171        public static void startAllHarvester() {
172            initHarvester();
173            Collection con = harvester.values();
174            for ( Iterator iter = con.iterator(); iter.hasNext(); ) {
175                AbstractHarvester h = (AbstractHarvester) iter.next();
176                if ( !h.isRunning() ) {
177                    h.startHarvesting();
178                }
179            }
180        }
181    
182        /**
183         * stpos all known/registered harvester. This method can be used to stop harvesting using requests e.g. if a server
184         * has shall be shut down.
185         * 
186         */
187        public static void stopAllHarvester() {
188            if ( harvester != null ) {
189                Collection con = harvester.values();
190                for ( Iterator iter = con.iterator(); iter.hasNext(); ) {
191                    AbstractHarvester h = (AbstractHarvester) iter.next();
192                    if ( h.isRunning() ) {
193                        LOG.logInfo( "stop harvesting for: " + h.getClass().getName() );
194                        h.stopHarvesting();
195                    }
196                }
197            }
198        }
199    
200        /**
201         * @param request
202         * @return An echorequest (e.g. a response to an harvest request).
203         * @throws OGCWebServiceException if something went wrong while processing the request
204         */
205        public EchoRequest harvestRecords( Harvest request ) throws OGCWebServiceException {
206    
207            try {
208                HarvesterFactory hf = new HarvesterFactory( harvester );
209                AbstractHarvester h = hf.findHarvester( request );
210                h.addRequest( request );
211                if ( !h.isRunning() ) {
212                    h.startHarvesting();
213                }
214                if ( request.getHarvestInterval() == null ) {
215                    // h.removeRequest( request );
216                }
217            } catch ( Exception e ) {
218                LOG.logError( "could not perform harvest operation", e );
219                throw new OGCWebServiceException( getClass().getName(),
220                                                  "could not perform harvest operation" + e.getMessage() );
221            }
222    
223            return new EchoRequest( request.getId(), null );
224        }
225    
226        /**
227         * performs a transaction request by transforming and forwarding it to the WFS used as backend
228         * 
229         * @param request
230         * @return Created by sending the request to the underlying wfs.
231         * @throws XMLParsingException
232         * @throws OGCWebServiceException
233         */
234        public TransactionResult transaction( Transaction request ) throws OGCWebServiceException {
235    
236            XMLFragment wfsTransactionDocument = null;
237            try {
238                XMLFragment transactionDocument = new XMLFragment( XMLFactory.export( request ).getRootElement() );
239                StringWriter sww = new StringWriter( 15000 );
240                transactionDocument.write( sww );
241                transactionDocument.load( new StringReader( sww.getBuffer().toString() ), XMLFragment.DEFAULT_URL );
242                wfsTransactionDocument = IN_XSL.transform( transactionDocument );
243                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
244                    LOG.logDebug( "The (first) resulting wfs:Transaction document: \n " + wfsTransactionDocument.getAsPrettyString() );
245                }
246            } catch ( SAXException saxe ) {
247                String msg = "Can't create an appropriate CSW-Transaction from the request because: " + saxe.getMessage();
248                LOG.logError( msg, saxe );
249                throw new OGCWebServiceException( msg );
250            } catch ( IOException ioe ) {
251                String msg = "Can't create an appropriate CSW-Transaction from the request because: " + ioe.getMessage();
252                LOG.logError( msg, ioe );
253                throw new OGCWebServiceException( msg );
254            } catch ( TransformerException te ) {
255                String msg = "Can't create an appropriate CSW-Transaction from the request because: " + te.getMessage();
256                LOG.logError( msg, te );
257                throw new OGCWebServiceException( msg );
258            } catch ( XMLParsingException xmle ) {
259                String msg = "Can't create an appropriate CSW-Transaction from the request because: " + xmle.getMessage();
260                LOG.logError( msg, xmle );
261                throw new OGCWebServiceException( msg );
262            }
263    
264            org.deegree.ogcwebservices.wfs.operation.transaction.Transaction wfstrans = null;
265            try {
266                LOG.logDebug( "Creating a wfs transaction from the document" );
267                wfstrans = org.deegree.ogcwebservices.wfs.operation.transaction.Transaction.create( request.getId(),
268                                                                                                    wfsTransactionDocument.getRootElement() );
269            } catch ( OGCWebServiceException ogcwe ) {
270                LOG.logError( ogcwe.getMessage(), ogcwe );
271                String msg = "Cannot create a required object representation (which is used for the interaction with the backend) of your request because: " + ogcwe.getMessage();
272                throw new OGCWebServiceException( msg );
273            }
274    
275            Object wfsResponse = null;
276            try {
277                LOG.logDebug( "Sending the wfs transaction to the wfservice." );
278                wfsResponse = wfsService.doService( wfstrans );
279            } catch ( OGCWebServiceException e ) {
280                // String msg = "Generated WFS Transaction request failed: " + e.getMessage();
281                String msg = "Generated WFS Transaction request failed: " + e.getMessage();
282                LOG.logError( msg, e );
283                throw new OGCWebServiceException( "While handling your request, the interaction with the csw-backend failed because: " + e.getMessage() );
284            }
285    
286            if ( !( wfsResponse instanceof org.deegree.ogcwebservices.wfs.operation.transaction.TransactionResponse ) ) {
287                String msg = "Unexpected result type '" + wfsResponse.getClass().getName()
288                             + "' from WFS (must be TransactionResponse)."
289                             + " Maybe a FeatureType is not correctly registered!?";
290                LOG.logError( msg );
291                throw new OGCWebServiceException( "The csw-backend gave an unanticipated response, this could mean, that your featuretypes aren't registered correctly! Please make sure the given featuretypes are correct." );
292            }
293    
294            TransactionResponse transResp = (TransactionResponse) wfsResponse;
295            XMLFragment wfsTransRespDoc = null;
296            try {
297                LOG.logDebug( "Parsing the wfs response." );
298                wfsTransRespDoc = org.deegree.ogcwebservices.wfs.XMLFactory.export( transResp );
299            } catch ( IOException e ) {
300                String msg = "export of WFS Transaction response as XML failed: " + e.getMessage();
301                LOG.logError( msg, e );
302                throw new OGCWebServiceException( msg );
303            }
304    
305            // --------------------------------------------------------------
306            // the following section will replace the feature ids returned by
307            // the WFS for Insert requests by the ID of the inserted metadata sets
308            List<String> ids = new ArrayList<String>();
309            List<Operation> ops = request.getOperations();
310            for ( int i = 0; i < ops.size(); i++ ) {
311                if ( ops.get( i ) instanceof Insert ) {
312                    try {
313                        ids = extractIdentifiers( ids, (Insert) ops.get( i ) );
314                    } catch ( Exception e ) {
315                        LOG.logError( e.getMessage(), e );
316                        throw new OGCWebServiceException( getClass().getName(), e.getMessage() );
317                    }
318                }
319            }
320            try {
321                if ( ids.size() > 0 ) {
322                    wfsTransRespDoc = replaceIds( wfsTransRespDoc, ids );
323                }
324            } catch ( Exception e ) {
325                LOG.logError( e.getMessage(), e );
326                throw new OGCWebServiceException( getClass().getName(), e.getMessage() );
327            }
328            // ---------------------------------------------------------------
329    
330            TransactionResultDocument cswTransactionDocument = null;
331            try {
332                XMLFragment tmp = OUT_XSL.transform( wfsTransRespDoc );
333                cswTransactionDocument = new TransactionResultDocument();
334                cswTransactionDocument.setRootElement( tmp.getRootElement() );
335            } catch ( TransformerException e ) {
336                String msg = "Can't transform GetRecord request to WFS GetFeature request: " + e.getMessage();
337                LOG.logError( msg, e );
338                throw new OGCWebServiceException( msg );
339            }
340            TransactionResult result = null;
341            try {
342                result = cswTransactionDocument.parseTransactionResponse( request );
343            } catch ( XMLParsingException e ) {
344                throw new OGCWebServiceException( "could not create TransactionResponse" );
345            }
346            return result;
347    
348        }
349    
350        /**
351         * replaces the id values of WFS Insert result with corresponding metadata identifieres
352         * 
353         * @param wfsTransRespDoc
354         * @param ids
355         * @return an xmlFragment with the gml:Feature ids replaced with the id' s given in the list
356         * @throws URISyntaxException
357         * @throws XMLParsingException
358         */
359        private XMLFragment replaceIds( XMLFragment wfsTransRespDoc, List<String> ids ) throws XMLParsingException {
360    
361            List nodes = XMLTools.getRequiredNodes( wfsTransRespDoc.getRootElement(),
362                                                    "./wfs:InsertResults/wfs:Feature/ogc:FeatureId",
363                                                    CommonNamespaces.getNamespaceContext() );
364            for ( int i = 0; i < nodes.size(); i++ ) {
365                Element elem = (Element) nodes.get( i );
366                elem.setAttribute( "fid", ids.get( i ) );
367            }
368    
369            return wfsTransRespDoc;
370        }
371    
372        /**
373         * extracts all identifiers of the records to be inserted in correct order
374         * 
375         * @param ids
376         * @param insert
377         * @return a list of identifiers which should be keys to xpaths which are defined in the messages.properties
378         * @throws XMLParsingException
379         * @throws URISyntaxException 
380         */
381        private List<String> extractIdentifiers( List<String> ids, Insert insert ) throws XMLParsingException {
382            List<Element> records = insert.getRecords();
383    
384            NamespaceContext nsc = CommonNamespaces.getNamespaceContext();
385    
386             //nsc.addNamespace( "dc", new URI( "http://www.purl.org/dc/elements/1.1/" ) );
387            for ( int i = 0; i < records.size(); i++ ) {
388                String xpath = getIdentifierXPath( records.get( i ) );
389                String fileIdentifier = XMLTools.getRequiredNodeAsString( records.get( i ), xpath, nsc );
390                ids.add( fileIdentifier );
391            }
392            return ids;
393        }
394    
395        /**
396         * returns the XPath the metadata records identifier
397         * 
398         * @param metaData
399         * @return the XPath the metadata records identifier
400         */
401        private String getIdentifierXPath( Element metaData ) {
402    
403            // default is iso 19115
404            String xpath = "iso19115:fileIdentifier/smXML:CharacterString";
405            if ( metaData != null ) {
406                String nspace = metaData.getNamespaceURI();
407                nspace = StringTools.replace( nspace, "http://", "", true );
408                xpath = Messages.getString( "Identifier_" + nspace );
409            }
410            return xpath;
411        }
412    
413        /**
414         * @return the wfsService.
415         */
416        public WFService getWfsService() {
417            return wfsService;
418        }
419    
420    }