001    /*----------------------------------------------------------------------------
002     This file is part of deegree, http://deegree.org/
003     Copyright (C) 2001-2009 by:
004       Department of Geography, University of Bonn
005     and
006       lat/lon GmbH
007    
008     This library is free software; you can redistribute it and/or modify it under
009     the terms of the GNU Lesser General Public License as published by the Free
010     Software Foundation; either version 2.1 of the License, or (at your option)
011     any later version.
012     This library is distributed in the hope that it will be useful, but WITHOUT
013     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
014     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
015     details.
016     You should have received a copy of the GNU Lesser General Public License
017     along with this library; if not, write to the Free Software Foundation, Inc.,
018     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019    
020     Contact information:
021    
022     lat/lon GmbH
023     Aennchenstr. 19, 53177 Bonn
024     Germany
025     http://lat-lon.de/
026    
027     Department of Geography, University of Bonn
028     Prof. Dr. Klaus Greve
029     Postfach 1147, 53001 Bonn
030     Germany
031     http://www.geographie.uni-bonn.de/deegree/
032    
033     e-mail: info@deegree.org
034    ----------------------------------------------------------------------------*/
035    
036    package org.deegree.ogcwebservices.csw.iso_profile.ebrim;
037    
038    import java.io.IOException;
039    import java.net.URI;
040    import java.net.URISyntaxException;
041    import java.security.InvalidParameterException;
042    import java.util.ArrayList;
043    import java.util.HashMap;
044    import java.util.List;
045    import java.util.Map;
046    import java.util.UUID;
047    
048    import org.deegree.datatypes.QualifiedName;
049    import org.deegree.framework.log.ILogger;
050    import org.deegree.framework.log.LoggerFactory;
051    import org.deegree.framework.util.TimeTools;
052    import org.deegree.framework.xml.XMLParsingException;
053    import org.deegree.framework.xml.XMLTools;
054    import org.deegree.io.datastore.schema.MappedFeatureType;
055    import org.deegree.model.feature.Feature;
056    import org.deegree.model.feature.FeatureCollection;
057    import org.deegree.model.feature.FeatureFactory;
058    import org.deegree.model.feature.FeatureProperty;
059    import org.deegree.model.filterencoding.ComplexFilter;
060    import org.deegree.model.filterencoding.Expression;
061    import org.deegree.model.filterencoding.Literal;
062    import org.deegree.model.filterencoding.OperationDefines;
063    import org.deegree.model.filterencoding.PropertyIsCOMPOperation;
064    import org.deegree.model.filterencoding.PropertyName;
065    import org.deegree.ogcbase.CommonNamespaces;
066    import org.deegree.ogcbase.PropertyPath;
067    import org.deegree.ogcbase.PropertyPathFactory;
068    import org.deegree.ogcwebservices.OGCWebServiceException;
069    import org.deegree.ogcwebservices.csw.manager.Insert;
070    import org.deegree.ogcwebservices.csw.manager.Manager;
071    import org.deegree.ogcwebservices.csw.manager.Operation;
072    import org.deegree.ogcwebservices.csw.manager.Transaction;
073    import org.deegree.ogcwebservices.csw.manager.TransactionResult;
074    import org.deegree.ogcwebservices.wfs.WFService;
075    import org.deegree.ogcwebservices.wfs.XMLFactory;
076    import org.deegree.ogcwebservices.wfs.operation.FeatureResult;
077    import org.deegree.ogcwebservices.wfs.operation.GetFeatureDocument;
078    import org.deegree.ogcwebservices.wfs.operation.GetFeatureWithLock;
079    import org.deegree.ogcwebservices.wfs.operation.Query;
080    import org.deegree.ogcwebservices.wfs.operation.GetFeature.RESULT_TYPE;
081    import org.deegree.ogcwebservices.wfs.operation.LockFeature.ALL_SOME_TYPE;
082    import org.deegree.ogcwebservices.wfs.operation.transaction.TransactionDocument;
083    import org.deegree.ogcwebservices.wfs.operation.transaction.TransactionOperation;
084    import org.deegree.ogcwebservices.wfs.operation.transaction.TransactionResponse;
085    import org.deegree.ogcwebservices.wfs.operation.transaction.Insert.ID_GEN;
086    import org.w3c.dom.Document;
087    import org.w3c.dom.Element;
088    import org.w3c.dom.Node;
089    
090    /**
091     * The <code>InsertTransactionHandler</code> class will cut an csw/wrs ebrim insert transaction into four differend
092     * transactions, some of which are handled as wfs transactions. For each record in an Insert Transaction the basic
093     * workflow is following:
094     * <ol>
095     * <li>find out if the to id of the to inserted record is allready in the wfs database</li>
096     * <li>if so, set it's app:status value to "invalid"</li>
097     * <li>insert / update the records</li>
098     * <li>create an audittrail, that is an app:AuditableEvent of the insertion</li>
099     * </ol>
100     * 
101     * 
102     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
103     * 
104     * @author last edited by: $Author: bezema $
105     * 
106     * @version $Revision: 1.7 $, $Date: 2007-06-21 13:54:33 $
107     * 
108     */
109    
110    public class InsertTransactionHandler {
111    
112        private static ILogger LOG = LoggerFactory.getLogger( InsertTransactionHandler.class );
113    
114        private Transaction originalTransaction;
115    
116        private Insert insert;
117    
118        private URI appURI;
119    
120        private String userName;
121    
122        /**
123         * Creates an TransactionHandler which will be able to handle csw/ebrim inserts as defined in the wrs spec.
124         * 
125         * @param originalTransaction
126         *            parsed from the incoming HttpServletRequest.
127         * @param insert
128         *            InsertOperation to be handled (as part of the original Transaction) may not be null;
129         * @param appURI
130         *            defining a namespace in which the wfs RegistryObjects Recide.
131         * @param userName
132         *            of the users which wants to insert registryObjects, if not set it will be set to anonymous.
133         */
134        public InsertTransactionHandler( Transaction originalTransaction, Insert insert, URI appURI, String userName ) {
135            if ( originalTransaction == null ) {
136                throw new InvalidParameterException( "The transaction parameter may not be null" );
137            }
138            if ( insert == null ) {
139                throw new InvalidParameterException( "The insert parameter may not be null" );
140            }
141            this.originalTransaction = originalTransaction;
142            this.insert = insert;
143            if ( appURI == null ) {
144                try {
145                    appURI = new URI( "http://www.deegree.org/app" );
146                } catch ( URISyntaxException e ) {
147                    // nothing to do here.
148                }
149            } else {
150                this.appURI = appURI;
151            }
152            if ( userName == null || "".equals( userName ) ) {
153                userName = "anonymous";
154            }
155            this.userName = userName;
156    
157        }
158    
159        /**
160         * This method will handle the insert (given from
161         * 
162         * @param transactionManager
163         *            which can handle the csw transactions and allows the access to a localwfs, if null an
164         *            InvalidParameterException will be thrown.
165         * @param resultValues
166         *            an array[3] in which the number of insertions (resultValues[0]) and/or updates (resultValues[2]) will
167         *            be saved. If resultValues.length != 3 an InvalidParameterException will be thrown.
168         * @return the brief representation of the inserted (not updated) elements.
169         * @throws OGCWebServiceException
170         */
171        public List<Element> handleInsertTransaction( Manager transactionManager, int[] resultValues )
172                                throws OGCWebServiceException {
173            if ( transactionManager == null ) {
174                throw new InvalidParameterException( "The transactionManager may not be null" );
175            }
176            if ( resultValues.length != 3 ) {
177                throw new InvalidParameterException( "The length of the resultValues array must be 3" );
178            }
179    
180            List<Element> records = insert.getRecords();
181    
182            // Some properterypaths which are used for the creation of a complex filter.
183            QualifiedName registryObject = new QualifiedName( "app", "RegistryObject", appURI );
184            Expression iduriExpr = new PropertyName( new QualifiedName( "app", "liduri", appURI ) );
185    
186            Expression statusExpr = new PropertyName( new QualifiedName( "app", "status", appURI ) );
187            PropertyIsCOMPOperation validOperator = new PropertyIsCOMPOperation( OperationDefines.PROPERTYISEQUALTO,
188                                                                                 statusExpr, new Literal( "valid" ) );
189    
190            PropertyIsCOMPOperation emptyOperator = new PropertyIsCOMPOperation( OperationDefines.PROPERTYISEQUALTO,
191                                                                                 statusExpr, new Literal( "" ) );
192            ComplexFilter vFilter = new ComplexFilter( validOperator );
193            ComplexFilter eFilter = new ComplexFilter( emptyOperator );
194            ComplexFilter statusFilter = new ComplexFilter( vFilter, eFilter, OperationDefines.OR );
195    
196            FeatureCollection featureCollectionOnId = null;
197            WFService localWFS = transactionManager.getWFService();
198    
199            List<Element> briefRecords = new ArrayList<Element>( records.size() );
200    
201            /**
202             * Iterate over all records and for each record do the following, <code>
203             * 1) find out if the to id of the to inserted record is allready in the wfs database
204             * 2) if so, set it's app:status value to "invalid"
205             * 3) insert / update the records
206             * 4) create an audittrail, that is an app:AuditableEvent of the insertion
207             * </code>
208             */
209    
210            for ( int recordCount = 0; recordCount < records.size(); ++recordCount ) {
211                Element record = records.get( recordCount );
212                String auditableEventType = "Created";
213                String oldID = record.getAttribute( "id" );
214                if ( oldID == null || "".equals( oldID ) ) {
215                    throw new OGCWebServiceException( "You are trying to insert a(n) " + record.getNodeName()
216                                                      + " which has no 'id' attribute set, this is a required attribute." );
217                }
218                String predecessorID = oldID;
219                String logicalID = record.getAttribute( "lid" );
220                if ( logicalID == null || "".equals( logicalID ) ) {
221                    // throw new OGCWebServiceException( "You are trying to insert a(n) " + record.getNodeName()
222                    // + " which has no 'lid' attribute set, for this registry, this is a required attribute." );
223                    LOG.logDebug( " no lid given, setting attribute to value of id" );
224                    logicalID = oldID;
225                    record.setAttribute( "lid", oldID );
226                }
227    
228                String home = record.getAttribute( "home" );
229                if ( home == null ) {
230                    home = "";
231                }
232    
233                // Expression idLiteral = new Literal( oldID );
234                Expression idLiteral = new Literal( logicalID );
235                PropertyIsCOMPOperation idOperator = new PropertyIsCOMPOperation( OperationDefines.PROPERTYISEQUALTO,
236                                                                                  iduriExpr, idLiteral );
237                ComplexFilter idFilter = new ComplexFilter( idOperator );
238                ComplexFilter idAndStatusFilter = new ComplexFilter( idFilter, statusFilter, OperationDefines.AND );
239                try {
240                    // FeatureResult fr = sendWFSGetFeature( localWFS, registryObject, idFilter );
241                    FeatureResult fr = sendWFSGetFeature( localWFS, registryObject, idAndStatusFilter );
242                    if ( fr != null ) {
243                        featureCollectionOnId = (FeatureCollection) fr.getResponse();
244                    }
245                } catch ( OGCWebServiceException e ) {
246                    throw new OGCWebServiceException( "The insertion of " + record.getNodeName() + " failed because: "
247                                                      + e.getMessage() );
248                }
249                if ( featureCollectionOnId == null || "".equals( featureCollectionOnId.getId() ) ) {
250                    throw new OGCWebServiceException( "The insertion of " + record.getNodeName() + " failed." );
251                }
252    
253                String lockId = featureCollectionOnId.getAttribute( "lockId" );
254                LOG.logDebug( " InsertHandler, the GetFeature lock is: " + lockId );
255                if ( lockId == null || "".equals( lockId ) ) {
256                    throw new OGCWebServiceException( "Couldn't get a lock for " + record.getNodeName()
257                                                      + ". This object can therefore not be inserted." );
258                }
259                String numbOfFeatures = featureCollectionOnId.getAttribute( "numberOfFeatures" );
260                int featureCount = 0;
261                try {
262                    featureCount = Integer.parseInt( numbOfFeatures );
263                    LOG.logDebug( " InsertHandler: the number of features in the GetFeatureWithLock was: " + featureCount );
264                } catch ( NumberFormatException nfe ) {
265                    // nottin
266                }
267                // Check the number of hits we've found, if the id allready exists it means we want to set the status of the
268                // object to invalid.
269                // String newID = id;
270                if ( featureCount > 1 ) {
271                    throw new OGCWebServiceException( "The lid of this element: " + record.getNodeName()
272                                                      + " is not unique. This object can therefore not be inserted." );
273                } else if ( featureCount == 1 ) {
274                    int totalUpdated = changeStatusOfObject( lockId, registryObject, idAndStatusFilter,
275                                                             record.getNodeName(), localWFS );
276    
277                    Feature f = featureCollectionOnId.getFeature( 0 );
278                    if ( f == null ) {
279                        LOG.logError( "No feature found!!!!!" );
280                    } else {
281                        FeatureProperty iduriProperty = f.getDefaultProperty( new QualifiedName( "app", "iduri", appURI ) );
282                        if ( iduriProperty == null ) {
283                            LOG.logError( "The id of this element: "
284                                          + record.getNodeName()
285                                          + " is not found in the registry. No association of type 'predecessor' will be inserted!." );
286                        } else {
287                            predecessorID = (String) iduriProperty.getValue();
288                            if ( predecessorID == null || "".equals( predecessorID.trim() ) ) {
289                                LOG.logError( "The registry helds an id of this element: "
290                                              + record.getNodeName()
291                                              + " but it is empty. An association of type 'predecessor' will be inserted with to the oldID!." );
292                                predecessorID = oldID;
293                            } else {
294                                LOG.logDebug( " setting predecessorID to id of the registry (" + predecessorID + ")." );
295                            }
296                            LOG.logDebug( " wcsFilter: total updated wfs:records (should be >= 1) = " + totalUpdated );
297                            if ( totalUpdated == 1 ) {
298                                auditableEventType = "Versioned";
299                            }
300                        }
301                    }
302                }
303    
304                // send the insertion to wcs and insert the auditable event
305    
306                String newID = UUID.randomUUID().toString();
307    
308                if ( "Versioned".equals( auditableEventType ) ) {
309                    record.setAttribute( "id", newID );
310                }
311    
312                List<Element> tmpRecords = new ArrayList<Element>( 1 );
313                tmpRecords.add( record );
314    
315                Insert ins = new Insert( insert.getHandle(), tmpRecords );
316                List<Operation> tmpOp = new ArrayList<Operation>( 1 );
317                tmpOp.add( ins );
318                Transaction transaction = new Transaction( originalTransaction.getVersion(), originalTransaction.getId(),
319                                                           originalTransaction.getVendorSpecificParameters(), tmpOp, false );
320                TransactionResult tmpInsertResult = null;
321                try {
322                    tmpInsertResult = transactionManager.transaction( transaction );
323                } catch ( OGCWebServiceException ogws ) {
324                    throw new OGCWebServiceException( "CSW Insert Transaction: Error while inserting '"
325                                                      + record.getNodeName() + "' with id='" + oldID + "' because: "
326                                                      + ogws.getMessage() );
327                }
328    
329                if ( tmpInsertResult == null || tmpInsertResult.getTotalInserted() != 1 ) {
330                    throw new OGCWebServiceException(
331                                                      "The insertion of the element: "
332                                                                              + record.getNodeName()
333                                                                              + " failed, because the transactionresult is null or the number of inserted objects wasn't 1." );
334                }
335    
336                if ( featureCount == 1 ) {
337                    // update
338                    resultValues[2]++;
339                } else {
340                    // insert
341                    resultValues[0]++;
342                }
343                // First create the necessary Features
344                List<Feature> newObjectsInDB = new ArrayList<Feature>();
345                newObjectsInDB.add( createAuditableEvent( localWFS, oldID, home, auditableEventType, userName ) );
346                if ( "Versioned".equals( auditableEventType ) ) {
347                    newObjectsInDB.add( createAssociation( localWFS, newID, predecessorID ) );
348    
349                    // Now update all following associations which referenced the oldID.
350                    for ( int i = ( recordCount + 1 ); i < records.size(); ++i ) {
351                        Element tmpRec = records.get( i );
352                        if ( CommonNamespaces.OASIS_EBRIMNS.toASCIIString().equals( tmpRec.getNamespaceURI() )
353                             && "Association".equals( tmpRec.getLocalName() ) ) {
354                            String sourceObject = tmpRec.getAttribute( "sourceObject" );
355                            String targetObject = tmpRec.getAttribute( "targetObject" );
356                            if ( oldID.equals( sourceObject ) ) {
357                                LOG.logDebug( " Updating 'rim:Association/@sourceObject' Attribute to new id: " + newID
358                                              + " after an update of registryObject: " + record.getLocalName() );
359                                tmpRec.setAttribute( "sourceObject", newID );
360                            }
361                            if ( oldID.equals( targetObject ) ) {
362                                LOG.logDebug( " Updating 'rim:Association/@targetObject' Attribute to new id: " + newID
363                                              + " after an update of registryObject: " + record.getLocalName() );
364                                tmpRec.setAttribute( "targetObject", newID );
365                            }
366                        }
367                    }
368                }
369                insertFeatures( localWFS, newObjectsInDB, record.getNodeName() );
370                // sendAuditableEvent( transactionManager.getWfsService(), record.getNodeName(), auditableEventType,
371                // username,
372                // id, home );
373                // create a brief record description of the inserted record.
374                briefRecords.add( generateBriefRecord( record ) );
375            }
376            return briefRecords;
377        }
378    
379        /**
380         * 
381         * @param localWFS
382         * @param registryObject
383         * @param filter
384         * @return the FeatureResult of the given filter or <code>null</code> if something went wrong.
385         * @throws OGCWebServiceException
386         */
387        private FeatureResult sendWFSGetFeature( WFService localWFS, QualifiedName registryObject, ComplexFilter filter )
388                                throws OGCWebServiceException {
389            Query q = Query.create( registryObject, filter );
390            GetFeatureWithLock gfwl = GetFeatureWithLock.create( "1.1.0", "0", "no_handle", RESULT_TYPE.RESULTS,
391                                                                 "text/xml; subtype=gml/3.1.1", -1, 0, -1, -1,
392                                                                 new Query[] { q }, null, 300000l, ALL_SOME_TYPE.ALL );
393            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
394                try {
395                    GetFeatureDocument gd = XMLFactory.export( gfwl );
396                    LOG.logDebug( "The getFeature with lock request: " + gd.getAsPrettyString() );
397                } catch ( IOException e ) {
398                    LOG.logError( "InsertTransactionHandler: An error occurred while trying to get a debugging output for the generated GetFeatureDocument: "
399                                  + e.getMessage() );
400                } catch ( XMLParsingException e ) {
401                    LOG.logError( "InsertTransactionHandler: An error occurred while trying to get a debugging output for the generated GetFeatureDocument: "
402                                  + e.getMessage() );
403                }
404            }
405    
406            Object response = localWFS.doService( gfwl );
407            if ( response instanceof FeatureResult ) {
408                LOG.logDebug( "InsertHandler tried to get A feature with Lock, with a valid response from the localwfs" );
409                return (FeatureResult) response;
410            }
411            return null;
412        }
413    
414        /**
415         * This method will create a WFSTransaction containing one update operation, which will set the app:status of the
416         * app:RegistryObject found using the complexFilter to superseded.
417         * 
418         * @param newId
419         *            of the registryObject
420         * @param lockId
421         *            which was set while querying the app:RegistryObject for it's app:status
422         * @return the number of updated records, this value should only be 1 or 0.
423         * @throws OGCWebServiceException
424         *             if something went wrong.
425         */
426        private int changeStatusOfObject( String lockId, QualifiedName registryObject, ComplexFilter filter,
427                                          String originalRecordNodeName, WFService localWFS )
428                                throws OGCWebServiceException {
429            List<TransactionOperation> operations = new ArrayList<TransactionOperation>();
430            Map<PropertyPath, FeatureProperty> properties = new HashMap<PropertyPath, FeatureProperty>();
431    
432            // the new status value, e.g. app:RegistryObject/app:status=invalid
433            QualifiedName status = new QualifiedName( "app", "status", appURI );
434            PropertyPath statusPP = PropertyPathFactory.createPropertyPath( registryObject );
435            statusPP.append( PropertyPathFactory.createPropertyPathStep( status ) );
436    
437            // // the new id value e.g app:RegistryObject/app:iduri=newId
438            // QualifiedName iduri = new QualifiedName( "app", "iduri", appURI );
439            // PropertyPath iduriPP = PropertyPathFactory.createPropertyPath( registryObject );
440            // iduriPP.append( PropertyPathFactory.createAttributePropertyPathStep( iduri ) );
441    
442            // Adding the properties (e.g. the status=ivalid and the iduri=newId) to the wfs:UpdateOperation.
443            properties.put( statusPP, FeatureFactory.createFeatureProperty( status, "superseded" ) );
444            // properties.put( iduriPP, FeatureFactory.createFeatureProperty( iduri, newId ) );
445    
446            operations.add( new org.deegree.ogcwebservices.wfs.operation.transaction.Update( "no_handle", registryObject,
447                                                                                             properties, filter ) );
448            org.deegree.ogcwebservices.wfs.operation.transaction.Transaction wfsTransaction = new org.deegree.ogcwebservices.wfs.operation.transaction.Transaction(
449                                                                                                                                                                    "0",
450                                                                                                                                                                    "1.1.0",
451                                                                                                                                                                    null,
452                                                                                                                                                                    lockId,
453                                                                                                                                                                    operations,
454                                                                                                                                                                    true,
455                                                                                                                                                                    null );
456            int totalUpdated = 0;
457            try {
458                Object response = localWFS.doService( wfsTransaction );
459                if ( response instanceof TransactionResponse ) {
460                    totalUpdated = ( (TransactionResponse) response ).getTotalUpdated();
461                }
462            } catch ( OGCWebServiceException e ) {
463                throw new OGCWebServiceException( "The insertion of " + originalRecordNodeName + " failed: "
464                                                  + e.getMessage() );
465            }
466            return totalUpdated;
467    
468        }
469    
470        /**
471         * creates a brief representation of the given RegistryObject element, with following values (wrs spec):
472         * <ul>
473         * <li>rim:RegistryObject/@id</li>
474         * <li>rim:RegistryObject/@lid</li>
475         * <li>rim:RegistryObject/@objectType</li>
476         * <li>rim:RegistryObject/@status</li>
477         * <li>rim:RegistryObject/rim:VersionInfo</li>
478         * </ul>
479         * 
480         * @return a brief record description of the given ebrim:RegistryObject
481         */
482        private Element generateBriefRecord( Element record ) {
483            Document doc = XMLTools.create();
484            Element resultElement = doc.createElement( "csw:result" );
485            Element a = (Element) doc.importNode( record, false );
486            resultElement.appendChild( a );
487            List<Node> attribs = null;
488            try {
489                attribs = XMLTools.getNodes( a, "./@*", CommonNamespaces.getNamespaceContext() );
490            } catch ( XMLParsingException e1 ) {
491                LOG.logError(
492                              "InsertTransactionHandler: an error occurred while creating a briefrecord for registryObject: "
493                                                      + record.getNodeName(), e1 );
494            }
495            // NamedNodeMap attribs = a.getAttributes();
496            if ( attribs != null ) {
497                for ( Node attribute : attribs ) {
498                    // Attr attribute = (Attr) attribs.item( i );
499                    String localName = attribute.getLocalName();
500    
501                    LOG.logDebug( "From: " + a.getNodeName() + " found attribute (localname): " + localName );
502                    if ( !( "id".equals( localName ) || "lid".equals( localName ) || "objectType".equals( localName ) || "status".equals( localName ) ) ) {
503                        // resultElement.setAttributeNode( (Attr)attribs.item(i) );
504                        LOG.logDebug( " From: " + a.getNodeName() + " removing attribute (localname): " + localName );
505                        String namespace = attribute.getBaseURI();
506                        // a.removeChild( attribs.item(i) );
507                        a.removeAttributeNS( namespace, localName );
508                    }
509                }
510            }
511            Element versionInfo = null;
512            try {
513                versionInfo = XMLTools.getElement( record, "rim:VersionInfo", CommonNamespaces.getNamespaceContext() );
514                if ( versionInfo != null ) {
515                    Node vi = doc.importNode( versionInfo, true );
516                    a.appendChild( vi );
517                }
518            } catch ( XMLParsingException e ) {
519                LOG.logError(
520                              "InsertTransactionHandler: an error occurred while creating a briefrecord for registryObject: "
521                                                      + record.getNodeName(), e );
522            }
523            return a;
524        }
525    
526        /**
527         * Creates an association of type "urn:adv:registry:AssociationType:predecessor" which associates an old
528         * (updated/superseded) registry object with a new registry object.
529         * 
530         * @param localWFS
531         *            which will be talked to directly (superseding the csw).
532         * @param newRegisterID
533         *            the id of the new object inserted in the db
534         * @param oldRegisterID
535         *            the id of the old updated object, superseded in the db
536         */
537        private Feature createAssociation( WFService localWFS, String newRegisterID, String oldRegisterID ) {
538    
539            QualifiedName registryObject = new QualifiedName( "app", "RegistryObject", appURI );
540            QualifiedName associationType = new QualifiedName( "app", "Association", appURI );
541    
542            MappedFeatureType rootFT = localWFS.getMappedFeatureType( registryObject );
543            MappedFeatureType associationFT = localWFS.getMappedFeatureType( associationType );
544    
545            List<FeatureProperty> featureProperties = new ArrayList<FeatureProperty>();
546    
547            // Generate the Auditable Event complex subfeature
548    
549            QualifiedName associationTypeProp = new QualifiedName( "app", "associationType", appURI );
550            featureProperties.add( FeatureFactory.createFeatureProperty( associationTypeProp,
551                                                                         "urn:adv:registry:AssociationType:predecessor" ) );
552    
553            QualifiedName sourceObject = new QualifiedName( "app", "sourceObject", appURI );
554            featureProperties.add( FeatureFactory.createFeatureProperty( sourceObject, newRegisterID ) );
555    
556            QualifiedName targetObject = new QualifiedName( "app", "targetObject", appURI );
557            featureProperties.add( FeatureFactory.createFeatureProperty( targetObject, oldRegisterID ) );
558    
559            Feature associationFeature = FeatureFactory.createFeature( null, associationFT, featureProperties );
560    
561            // Creation of the RegistryObject
562            featureProperties.clear();
563    
564            // type
565            QualifiedName type = new QualifiedName( "app", "type", appURI );
566            featureProperties.add( FeatureFactory.createFeatureProperty( type, "Association" ) );
567    
568            QualifiedName iduri = new QualifiedName( "app", "iduri", appURI );
569            featureProperties.add( FeatureFactory.createFeatureProperty( iduri, UUID.randomUUID().toString() ) );
570    
571            // objecttype
572            QualifiedName objectType = new QualifiedName( "app", "objectType", appURI );
573            featureProperties.add( FeatureFactory.createFeatureProperty( objectType,
574                                                                         "urn:oasis:names:tc:ebxml-regrep:ObjectType:RegistryObject:Association" ) );
575    
576            // status
577            QualifiedName status = new QualifiedName( "app", "status", appURI );
578            featureProperties.add( FeatureFactory.createFeatureProperty( status, "valid" ) );
579    
580            // create the auditable Event property with the feature
581            QualifiedName association = new QualifiedName( "app", "association", appURI );
582            featureProperties.add( FeatureFactory.createFeatureProperty( association, associationFeature ) );
583    
584            Feature rootFeature = FeatureFactory.createFeature( null, rootFT, featureProperties );
585            return rootFeature;
586        }
587    
588        /**
589         * Creates an auditable event for the given objectid
590         * 
591         * TODO shouldn't the slots of the original inserted Object not be handled?
592         * 
593         * @param localWFS
594         *            which will be talked to directly (superseding the csw).
595         * @param affectedObjectId
596         *            of the object which has been inserted or updated
597         * @param affectedHome
598         *            of the object which has been inserted or updated
599         * @param auditEventType
600         *            should be one of 'Created' or 'Updated' (see the ebrim spec)
601         * @param username
602         *            of the person doing the insertion
603         */
604        private Feature createAuditableEvent( WFService localWFS, String affectedObjectId, String affectedHome,
605                                              String auditEventType, String username ) {
606            String requestId = originalTransaction.getId();
607    
608            QualifiedName registryObject = new QualifiedName( "app", "RegistryObject", appURI );
609            QualifiedName auditableEventType = new QualifiedName( "app", "AuditableEvent", appURI );
610            QualifiedName objectRefType = new QualifiedName( "app", "ObjectRef", appURI );
611    
612            MappedFeatureType rootFT = localWFS.getMappedFeatureType( registryObject );
613            MappedFeatureType auditableEventFT = localWFS.getMappedFeatureType( auditableEventType );
614            MappedFeatureType objectRefFT = localWFS.getMappedFeatureType( objectRefType );
615    
616            List<FeatureProperty> featureProperties = new ArrayList<FeatureProperty>();
617    
618            // Creating the Objectref
619            QualifiedName replacedURI = new QualifiedName( "app", "iduri", appURI );
620            featureProperties.add( FeatureFactory.createFeatureProperty( replacedURI, affectedObjectId ) );
621    
622            QualifiedName replacedHome = new QualifiedName( "app", "home", appURI );
623            featureProperties.add( FeatureFactory.createFeatureProperty( replacedHome, affectedHome ) );
624    
625            QualifiedName createReplica = new QualifiedName( "app", "createReplica", appURI );
626            featureProperties.add( FeatureFactory.createFeatureProperty( createReplica, "false" ) );
627    
628            Feature objectRefFeature = FeatureFactory.createFeature( null, objectRefFT, featureProperties );
629    
630            // Generate the Auditable Event complex subfeature
631            featureProperties.clear();
632    
633            QualifiedName eventType = new QualifiedName( "app", "eventType", appURI );
634            featureProperties.add( FeatureFactory.createFeatureProperty( eventType, auditEventType ) );
635    
636            QualifiedName timestamp = new QualifiedName( "app", "timestamp", appURI );
637            featureProperties.add( FeatureFactory.createFeatureProperty( timestamp, TimeTools.getISOFormattedTime() ) );
638    
639            QualifiedName usernameQName = new QualifiedName( "app", "username", appURI );
640            featureProperties.add( FeatureFactory.createFeatureProperty( usernameQName, username ) );
641    
642            QualifiedName requestIdQName = new QualifiedName( "app", "requestId", appURI );
643            featureProperties.add( FeatureFactory.createFeatureProperty( requestIdQName, requestId ) );
644    
645            // add the affected ObjectsFeatureType to the affectedObjects property
646            QualifiedName affectedObjects = new QualifiedName( "app", "affectedObjects", appURI );
647            featureProperties.add( FeatureFactory.createFeatureProperty( affectedObjects, objectRefFeature ) );
648    
649            Feature auditEventFeature = FeatureFactory.createFeature( null, auditableEventFT, featureProperties );
650    
651            // Creation of the RegistryObject
652            featureProperties.clear();
653    
654            // type
655            QualifiedName type = new QualifiedName( "app", "type", appURI );
656            featureProperties.add( FeatureFactory.createFeatureProperty( type, "AuditableEvent" ) );
657    
658            QualifiedName iduri = new QualifiedName( "app", "iduri", appURI );
659            featureProperties.add( FeatureFactory.createFeatureProperty( iduri, UUID.randomUUID().toString() ) );
660    
661            // objecttype
662            QualifiedName objectType = new QualifiedName( "app", "objectType", appURI );
663            featureProperties.add( FeatureFactory.createFeatureProperty( objectType,
664                                                                         "urn:oasis:names:tc:ebxml-regrep:ObjectType:RegistryObject:AuditableEvent" ) );
665    
666            // status
667            QualifiedName status = new QualifiedName( "app", "status", appURI );
668            featureProperties.add( FeatureFactory.createFeatureProperty( status, "valid" ) );
669    
670            // create the auditable Event property with the feature
671            QualifiedName auditableEvent = new QualifiedName( "app", "auditableEvent", appURI );
672            featureProperties.add( FeatureFactory.createFeatureProperty( auditableEvent, auditEventFeature ) );
673    
674            Feature rootFeature = FeatureFactory.createFeature( null, rootFT, featureProperties );
675    
676            return rootFeature;
677        }
678    
679        /**
680         * Puts an auditable event for the given objectid into the database, thus resulting in an AuditTrail for the
681         * inserted/updated Object.
682         * 
683         * 
684         * @param localWFS
685         *            which will be talked to directly (superseding the csw).
686         * @param featuresToInsert
687         *            an array of features (either an auditableEvent or an auditableEvent and an Association (if an update
688         *            occurred) ).
689         * @param originalInsertObjectName
690         *            the name of the object to be inserted (used for debug messages)
691         * @throws OGCWebServiceException
692         */
693        private void insertFeatures( WFService localWFS, List<Feature> featuresToInsert, String originalInsertObjectName )
694                                throws OGCWebServiceException {
695            String requestId = originalTransaction.getId();
696            if ( featuresToInsert.size() == 0 ) {
697                LOG.logError( "CSW (Ebrim) InsertTransactionHandler: there were no features to insert, this may not be (at least an auditableEvent feature should be inserted)!" );
698                return;
699            }
700            Feature[] fA = new Feature[featuresToInsert.size()];
701            for ( int i = 0; i < fA.length; ++i ) {
702                fA[i] = featuresToInsert.get( i );
703            }
704            FeatureCollection fc = FeatureFactory.createFeatureCollection( requestId, fA );
705    
706            org.deegree.ogcwebservices.wfs.operation.transaction.Insert wfsInsert = new org.deegree.ogcwebservices.wfs.operation.transaction.Insert(
707                                                                                                                                                     "no_handle",
708                                                                                                                                                     ID_GEN.GENERATE_NEW,
709                                                                                                                                                     null,
710                                                                                                                                                     fc );
711            List<TransactionOperation> ops = new ArrayList<TransactionOperation>( 1 );
712            ops.add( wfsInsert );
713            org.deegree.ogcwebservices.wfs.operation.transaction.Transaction transaction = new org.deegree.ogcwebservices.wfs.operation.transaction.Transaction(
714                                                                                                                                                                 originalTransaction.getId(),
715                                                                                                                                                                 "1.1.0",
716                                                                                                                                                                 null,
717                                                                                                                                                                 null,
718                                                                                                                                                                 ops,
719                                                                                                                                                                 true,
720                                                                                                                                                                 null );
721    
722            try {
723                localWFS.doService( transaction );
724            } catch ( OGCWebServiceException e ) {
725                String features = "AuditableEvent ";
726                if ( fA.length > 1 )
727                    features += "and an Association ";
728                throw new OGCWebServiceException( "Could not insert an " + features
729                                                  + "for the insertion/update of the RegistryObject: "
730                                                  + originalInsertObjectName + " because: " + e.getMessage() );
731            }
732    
733            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
734                try {
735                    TransactionDocument doc = XMLFactory.export( transaction );
736                    LOG.logDebug( " The auditable event created for the insertion of '" + originalInsertObjectName
737                                  + "' is:\n" + doc.getAsPrettyString() );
738                } catch ( IOException e ) {
739                    LOG.logError( "InsertTransactionHandler: An error occurred while trying to create an auditable Event for insertion of the '"
740                                  + originalInsertObjectName + "'. Errormessage: " + e.getMessage() );
741                } catch ( XMLParsingException e ) {
742                    LOG.logError( "InsertTransactionHandler: An error occurred while trying to create an auditable Event for insertion of the '"
743                                  + originalInsertObjectName + "'. Errormessage: " + e.getMessage() );
744                }
745            }
746    
747        }
748    
749    }