KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > applications > xmlimporter > Transaction


1 /*
2
3 This software is OSI Certified Open Source Software.
4 OSI Certified is a certification mark of the Open Source Initiative.
5
6 The license (Mozilla version 1.0) can be read at the MMBase site.
7 See http://www.MMBase.org/license
8
9 */

10 package org.mmbase.applications.xmlimporter;
11
12 import java.io.*;
13 import java.util.*;
14
15 import org.mmbase.module.Module;
16 import org.mmbase.module.core.MMObjectBuilder;
17 import org.mmbase.module.core.MMObjectNode;
18 import org.mmbase.module.core.MMBase;
19 import org.mmbase.module.core.TemporaryNodeManager;
20 import org.mmbase.module.core.TemporaryNodeManagerInterface;
21 import org.mmbase.module.core.TransactionManager;
22 import org.mmbase.module.core.TransactionManagerException;
23 import org.mmbase.applications.xmlimporter.SimilarObjectFinder;
24 import org.mmbase.applications.xmlimporter.ObjectMerger;
25 import org.mmbase.util.logging.Logger;
26 import org.mmbase.util.logging.Logging;
27
28 /**
29  * This class models a Temporary Cloud Transaction. The class:
30  * <ul>
31  * <li> is used for MMBase xml import (org.mmbase.applications.xmlimporter.TransactionParser).
32  * <li> can be used by java classes by directly calling methods
33  * </ul>
34  * <br />See also the xml description in the tcp 2.0 project.
35  *
36  * @author Rob van Maris: Finalist IT Group
37  * @since MMBase-1.5
38  * @version $Id: Transaction.java,v 1.6 2005/10/06 14:14:41 michiel Exp $
39  */

40 public class Transaction implements Runnable JavaDoc {
41
42     /** Logger instance. */
43     private static Logger log = Logging.getLoggerInstance(Transaction.class.getName());
44
45     /** The mmbase module. */
46     private static MMBase mmbase;
47
48     /** The temporary node manager. */
49     private static TemporaryNodeManagerInterface tmpNodeManager;
50
51     /** The transaction manager. */
52     private static TransactionManager transactionManager;
53
54     /** Base for unique id generation. */
55     private static long uniqueId = System.currentTimeMillis();
56
57     /** All user-related data. */
58     private UserTransactionInfo uti;
59
60     /** Transaction information for current user. */
61     protected Consultant consultant;
62
63     /** Key used by TransactionManager. */
64     private String JavaDoc key;
65
66     /** All non-anonymous object contexts in this transaction, mapped by id. */
67     private HashMap namedObjectContexts = new HashMap();
68
69     /** All object contexts in this transaction, mapped by key. */
70     private HashMap tmpObjects = new HashMap();
71
72     /** Ordered list of all object contexts in this transaction.
73      * It reflects the order in which the objects are created
74      * in the transaction. */

75     private List lstTmpObjects = new LinkedList();
76
77     /** User specified id. */
78     private String JavaDoc id;
79
80     /** Commit-on-close setting. */
81     private boolean commitOnClose;
82
83     /** Transaction timeout in seconds. */
84     private long timeOut;
85
86     /** Report file. */
87     private File reportFile;
88
89     /** Buffer to hold duplicates of this transaction. */
90     private StringBuffer JavaDoc reportBuffer = new StringBuffer JavaDoc();
91
92     /** Map for holding merge result objects. Key is the deleted object. */
93     private Map mergedObjects = new HashMap();
94
95
96     /** Flag, to be set when MergeExceptions occur.
97      * Committing this transaction will not proceed when this flag is set,
98      * but instead the contents of the reportBuffer will be written to a file.
99      */

100     private boolean resolvedDuplicates = true;
101
102     /** Thread to monitor timeout. */
103     private Thread JavaDoc kicker;
104
105     /** Is the transaction finished or timedout */
106     private boolean finished = false;
107
108     /**
109      * Creates new Transaction.
110      * @param timeOut if the transactions is not finished after the timeout
111      * (in seconds) the transaction is cancelled.
112      * @param uti transaction info for current user.
113      * @param key TransactionManager key for this transaction.
114      * @param id TransactionHandler id for this transactions.
115      * @param commitOnClose - The user-specified commit-on-close setting.
116      * True if this transaction is to be committed
117      * when the user leaves it's context, false otherwise.
118      * @param reportFile The file to use as reportfile.
119      * @param consultant The intermediate import object. Used to set and get status from and set and get objects to and from.
120      */

121     protected Transaction(UserTransactionInfo uti, String JavaDoc key, String JavaDoc id,
122     boolean commitOnClose, long timeOut, File reportFile, Consultant consultant) {
123
124         this.uti = uti;
125         this.key = key;
126         this.id = id;
127         this.commitOnClose = commitOnClose;
128         this.timeOut = timeOut;
129         this.reportFile = reportFile;
130         this.consultant = consultant;
131         start();
132     }
133
134     /**
135      * Creates new Transaction.
136      * @param timeOut if the transactions is not finished after the timeout
137      * (in seconds) the transaction is cancelled.
138      * @param uti transaction info for current user.
139      * @param key TransactionManager key for this transaction.
140      * @param id TransactionHandler id for this transactions.
141      * @param commitOnClose - The user-specified commit-on-close setting.
142      * True if this transaction is to be committed
143      * when the user leaves it's context, false otherwise.
144      */

145     protected Transaction(UserTransactionInfo uti, String JavaDoc key, String JavaDoc id,
146     boolean commitOnClose, long timeOut) {
147
148         this.uti = uti;
149         this.key = key;
150         this.id = id;
151         this.commitOnClose = commitOnClose;
152         this.timeOut = timeOut;
153         start();
154     }
155
156     /**
157      * Finds the TransactionManager module.
158      * The first time this method is called it looks up the
159      * modules that transactions might use.
160      * @return the TransactionManager module.
161      */

162     private static synchronized TransactionManager getTransactionManager() {
163         if (transactionManager == null) {
164             mmbase=(MMBase) Module.getModule("MMBASEROOT");
165             tmpNodeManager = new TemporaryNodeManager(mmbase);
166             transactionManager = new TransactionManager(mmbase,tmpNodeManager);
167         }
168         return transactionManager;
169     }
170
171     /**
172      * Create a Transaction.
173      * @param uti - The UserTransactionInfo it belongs to.
174      * @param id - The user-specified id, null for anonymous transaction.
175      * @param commitOnClose - The user-specified commit-on-close setting.
176      * True if this transaction is to be committed
177      * when the user leaves it's context, false otherwise.
178      * @param timeOut - The user-specified time-out setting.
179      * @return - New transaction.
180      * @throws TransactionHandlerException - When failing to create the new transaction.
181      */

182     public static Transaction createTransaction(UserTransactionInfo uti,
183     String JavaDoc id, boolean commitOnClose, long timeOut)
184     throws TransactionHandlerException {
185
186         // If no id is specified, it's an "anonymous" transaction.
187
// Generate a unique id wich will be hidden for the user.
188
boolean anonymous = (id == null);
189         if (anonymous) {
190             id = "AnTr" + uniqueId++; // Anonymous Transaction.
191
}
192
193         // Check transaction does not exist already.
194
if (uti.knownTransactionContexts.get(id) != null) {
195             throw new TransactionHandlerException(
196             "Transaction id already exists: id = \"" + id + "\"");
197         }
198
199         // Create new transaction.
200
String JavaDoc key = null;
201         try {
202             key = getTransactionManager().create(uti.user, id);
203         } catch (TransactionManagerException e) {
204             throw new TransactionHandlerException(e.getMessage());
205         }
206
207         Transaction transaction
208         = new Transaction(uti, key, id, commitOnClose, timeOut);
209
210         // If not anonymous transaction,
211
// register it in the list of all transaction of the user.
212
if (!anonymous) {
213             uti.knownTransactionContexts.put(id, transaction);
214         }
215
216         if (log.isDebugEnabled()) {
217             log.debug("Transaction created: " + key);
218         }
219         return transaction;
220     }
221
222     /**
223      * Create a Transaction.
224      * @param uti the UserTransactionInfo it belongs to.
225      * @param id the user-specified id, null for anonymous transaction.
226      * @param commitOnClose - The user-specified commit-on-close setting.
227      * True if this transaction is to be committed
228      * when the user leaves it's context, false otherwise.
229      * @param timeOut the user-specified time-out setting.
230      * @param reportFile The reportfile.
231      * @param consultant The intermediate import object. Used to set and get status from and set and get objects to and from.
232      * @return new transaction.
233      * @throws TransactionHandlerException When failing to create
234      * the new transaction.
235      */

236     public static Transaction createTransaction(UserTransactionInfo uti,
237     String JavaDoc id, boolean commitOnClose, long timeOut, File reportFile,
238     Consultant consultant)
239     throws TransactionHandlerException {
240
241         // If no id is specified, it's an "anonymous" transaction.
242
// Generate a unique id wich will be hidden for the user.
243
boolean anonymous = (id == null);
244         if (anonymous) {
245             id = "AnTr" + uniqueId++; // Anonymous Transaction.
246
}
247
248         // Check transaction does not exist already.
249
if (uti.knownTransactionContexts.get(id) != null) {
250             throw new TransactionHandlerException(
251             "Transaction id already exists: id = \"" + id + "\"");
252         }
253
254         // Create new transaction.
255
String JavaDoc key = null;
256         try {
257             key = getTransactionManager().create(uti.user, id);
258         } catch (TransactionManagerException e) {
259             throw new TransactionHandlerException(e.getMessage());
260         }
261
262         Transaction transaction;
263         if (consultant != null && consultant.interactive()) {
264             transaction = new InteractiveTransaction(uti, key, id, commitOnClose,
265             timeOut, reportFile, consultant);
266         } else {
267             transaction = new Transaction(uti, key, id, commitOnClose,
268             timeOut, reportFile, consultant);
269         }
270
271         // If not anonymous transaction,
272
// register it in the list of all transaction of the user.
273
if (!anonymous) {
274             uti.knownTransactionContexts.put(id, transaction);
275         }
276
277         if (log.isDebugEnabled()) {
278             log.debug("Transaction created: " + key);
279         }
280         return transaction;
281     }
282
283     /**
284      * Open previously created transaction.
285      * This assumes that the transaction is not anonymous.
286      * @return the transaction.
287      * @param commitOnClose - The user-specified commit-on-close setting.
288      * True if this transaction is to be committed
289      * when the user leaves it's context, false otherwise.
290      * @param uti the UserTransactionInfo it belongs to.
291      * @param id the user-specified id (not null).
292      * @throws TransactionHandlerException if the transaction does not exist.
293      */

294     public static Transaction openTransaction(UserTransactionInfo uti,
295     String JavaDoc id, boolean commitOnClose) throws TransactionHandlerException {
296
297         // Check transaction does exist.
298
if (!uti.knownTransactionContexts.containsKey(id)) {
299             throw new TransactionHandlerException(
300             "Transaction id does not exist: id = \"" + id + "\"");
301         }
302
303         // Retreive transaction, adjust commitOnClose setting.
304
Transaction tr = (Transaction) uti.knownTransactionContexts.get(id);
305         tr.commitOnClose = commitOnClose;
306         return tr;
307     }
308
309     /**
310      * This method should be called when leaving this transaction's context.
311      * This will commit the transaction, when commitOnClose is set.
312      * @throws TransactionHandlerException when a failure occured while
313      * committing the transaction.
314      */

315     public void leave() throws TransactionHandlerException {
316
317         if (log.isDebugEnabled()) {
318             log.debug("About to leave transaction: " + key);
319         }
320
321         if (commitOnClose) {
322             commit();
323         }
324     }
325
326     /**
327      * Commit this transaction.
328      * @throws TransactionHandlerException when a failure occured while
329      * committing the transaction:
330      * <UL>
331      * <LI> an exception thrown by MMBase TransactionManager.
332      * <LI> an error while accessing the reportfile.
333      * <LI> a transaction has timed out
334      * </UL>
335      */

336     public void commit() throws TransactionHandlerException {
337         if (consultant != null
338         && consultant.getImportStatus() == Consultant.IMPORT_TIMED_OUT) {
339             throw new TransactionHandlerException("Transaction with id="
340             + id + " is timed out after " + timeOut + " seconds.");
341         }
342
343         // Commit the transaction and stop thread.
344
try {
345             if (resolvedDuplicates) {
346                 // No MergeExceptions occurred: transaction can be committed.
347
transactionManager.commit(uti.user, id);
348                 if (log.isDebugEnabled()) {
349                     log.debug("transaction committed: " + key
350                     + "\n" + reportBuffer.toString());
351                 }
352             } else {
353                 // MergeExceptions occurred: don't commit, cancel transacton
354
// write duplicates to reportBuffer instead
355

356                 log.warn("transaction not committed: " + key
357                 + "\n" + reportBuffer.toString());
358
359                 // Append reportfile.
360
if (reportFile != null) {
361                     Writer out = null;
362                     try {
363                         out = new BufferedWriter(new OutputStreamWriter(
364                         new FileOutputStream(reportFile.getPath(),true)));
365                         out.write(reportBuffer.toString());
366                     } catch (Exception JavaDoc e) {
367                         throw new TransactionHandlerException(
368                         "Failed to append reportfile "+reportFile+": "+e);
369                     } finally {
370                         if (out != null) {
371                             try {
372                                 out.close();
373                             } catch (IOException e) {
374                                 throw new TransactionHandlerException(
375                                 "Failed to close reportfile "+reportFile+": "+e);
376                             }
377                         }
378                     }
379                 }
380                 else {
381                     if (log.isDebugEnabled()) {
382                         log.debug("reportFile NULL");
383                     }
384                 }
385
386             }
387         } catch (TransactionManagerException e) {
388             throw new TransactionHandlerException(e.getMessage());
389         } finally {
390             stop();
391         }
392
393         // Remove from user's transaction contexts.
394
uti.knownTransactionContexts.remove(id);
395     }
396
397     /**
398      * Delete this transaction.
399      * @throws TransactionHandlerException when:
400      * <UL>
401      * <LI>- a failure occured while deleting the transaction
402      * <LI>- transaction has timed out
403      * </UL>
404      */

405     public void delete() throws TransactionHandlerException {
406         if (consultant != null
407         && consultant.getImportStatus() == Consultant.IMPORT_TIMED_OUT) {
408             throw new TransactionHandlerException("Transaction with id="
409             + id + " is timed out after " + timeOut + " seconds.");
410         }
411         // Cancel the transaction and stop thread.
412
try {
413             transactionManager.cancel(uti.user, id);
414         } catch (TransactionManagerException e) {
415             throw new TransactionHandlerException(e.getMessage());
416         } finally {
417             stop();
418         }
419
420         // Remove from user's transaction contexts.
421
uti.knownTransactionContexts.remove(id);
422
423         if (log.isDebugEnabled()) {
424             log.debug("Transaction deleted: " + key);
425         }
426     }
427
428     /**
429      * Create object in the context of this transaction.
430      *
431      * @param objectId user-specified id for the new object
432      * (must be unique in this transaction context),
433      * or null for anonymous object.
434      * @param type type of the new object.
435      * @param disposeWhenNotReferenced flag: true if this object is
436      * to be dropped when it has no relations on commit, false otherwise.
437      * @return the new object in the temporary cloud.
438      * @throws TransactionHandlerException When failing to create
439      * the new object.
440      */

441     public TmpObject createObject(String JavaDoc objectId, String JavaDoc type,
442     boolean disposeWhenNotReferenced)
443     throws TransactionHandlerException {
444
445         // If no id is specified, it's an "anonymous" object.
446
// Generate a unique id wich will be hidden for the user.
447
boolean anonymous = (objectId == null);
448         if (anonymous) {
449             objectId = "AnOb" + uniqueId++; // Anonymous Object.
450
}
451
452         // Check object does not exist already.
453
if (namedObjectContexts.get(objectId) != null) {
454             throw new TransactionHandlerException(
455             "Object id already exists: id = \"" + objectId + "\"");
456         }
457
458         // Create new object, and add to temporary cloud.
459
tmpNodeManager.createTmpNode(type, uti.user.getName(), objectId);
460         TmpObject tmpObject =
461         new TmpObject(uti, objectId, false, disposeWhenNotReferenced);
462
463         // Add to transaction.
464
try {
465             transactionManager.addNode(key, uti.user.getName(), objectId);
466         } catch (TransactionManagerException e) {
467             tmpNodeManager.deleteTmpNode(uti.user.getName(), objectId);
468             throw new TransactionHandlerException(e.getMessage());
469         }
470         tmpObjects.put(tmpObject.getKey(), tmpObject);
471         lstTmpObjects.add(tmpObject);
472
473         // If not anonymous object,
474
// register it in the list of named objects in the transaction.
475
if (!anonymous) {
476             namedObjectContexts.put(objectId, tmpObject);
477         }
478
479         if (log.isDebugEnabled()) {
480             log.debug("Object created: " + tmpObject);
481         }
482         return tmpObject;
483     }
484
485     /**
486      * Create relation in the context of this transaction.
487      * @return the new relation object in the temporary cloud.
488      * @param objectId user-specified id for the new object
489      * (must be unique in this transaction context),
490      * or null for anonymous object.
491      * @param type type of the new relation.
492      * @param source the user-specified id of the source object.
493      * @param destination the user-specified id of the destination object.
494      * @throws TransactionHandlerException when
495      * <UL>
496      * <LI>an object with this id already exists in this transaction context
497      * <LI>a relation object can't be created
498      * <LI>an object can't be added to a transaction
499      * </UL>
500      */

501     public TmpObject createRelation(String JavaDoc objectId, String JavaDoc type, String JavaDoc source,
502     String JavaDoc destination) throws TransactionHandlerException {
503
504         // If no id is specified, it's an "anonymous" object.
505
// Generate a unique id wich will be hidden for the user.
506
boolean anonymous = (objectId == null);
507         if (anonymous) {
508             objectId = "AnRel" + uniqueId++; // Anonymous Relation.
509
}
510
511         // Check object does not exist already.
512
if (namedObjectContexts.get(objectId) != null) {
513             throw new TransactionHandlerException(
514             "Object id already exists: id = \"" + objectId + "\"");
515         }
516
517         // check if source and destination objects exist
518
if (namedObjectContexts.get(source) == null) {
519             throw new TransactionHandlerException(
520             "Unknown source object id: id = \"" + source + "\"");
521         }
522
523         if (namedObjectContexts.get(destination) == null) {
524             throw new TransactionHandlerException(
525             "Unknown destination object id: id = \"" + destination + "\"");
526         }
527
528         // Create new object, and add to temporary cloud.
529
try {
530             tmpNodeManager.createTmpRelationNode(
531             type, uti.user.getName(), objectId, source, destination);
532         } catch (Exception JavaDoc e) {
533             throw new TransactionHandlerException(
534             "Type \"" + type + "\" is not a proper relation.");
535         }
536         TmpObject tmpObject = new TmpObject(uti, objectId, true, false);
537
538         // Add to transaction.
539
try {
540             transactionManager.addNode(key, uti.user.getName(), objectId);
541         } catch (TransactionManagerException e) {
542             tmpNodeManager.deleteTmpNode(uti.user.getName(), objectId);
543             throw new TransactionHandlerException(e.getMessage());
544         }
545         tmpObjects.put(tmpObject.getKey(), tmpObject);
546         lstTmpObjects.add(tmpObject);
547
548         // If not anonymous object,
549
// register it in the list of named objects in the transaction.
550
if (!anonymous) {
551             namedObjectContexts.put(objectId, tmpObject);
552         }
553
554         if (log.isDebugEnabled()) {
555             log.debug("Relation created: " + tmpObject);
556         }
557         return tmpObject;
558     }
559
560     /**
561      * Open object in the context of this transaction.
562      * @param objectId the user-specified id of the object.
563      * @return the object in the temporary cloud.
564      * @throws TransactionHandlerException when the id does not exist
565      * in the context of this transactions.
566      */

567     public TmpObject openObject(String JavaDoc objectId)
568     throws TransactionHandlerException {
569
570         // Check object does exist.
571
if (namedObjectContexts.get(objectId) == null) {
572             throw new TransactionHandlerException(
573             "Object id does not exist: id = \"" + objectId + "\"");
574         }
575
576         TmpObject tmpObj = (TmpObject) namedObjectContexts.get(objectId);
577
578         if (log.isDebugEnabled()) {
579             log.debug("Opened object: " + tmpObj);
580         }
581
582         return tmpObj;
583     }
584
585     /**
586      * Create access object for an object in the persistent cloud,
587      * in the context of this transaction.
588      *
589      * If an accessObjects with the same objectId and mmbaseId then the already
590      * existing accessObject is returned. This because in a stylesheet it's hard
591      * to establish if an object already exists.
592      *
593      * @param objectId user-specified id of the new object
594      * (must be unique in this transaction context).
595      * @param mmbaseId the mmbase id for the persistent object.
596      * @return the access object in the temporary cloud.
597      * @throws TransactionHandlerException When failing to create
598      * the access object.
599      */

600     public TmpObject accessObject(String JavaDoc objectId, int mmbaseId)
601     throws TransactionHandlerException {
602         // If no id is specified, it's an "anonymous" object.
603
// Generate a unique id wich will be hidden for the user.
604
boolean anonymous = (objectId == null);
605         if (anonymous) {
606             objectId = "AnOb_" + uniqueId++; // Anonymous Object.
607
}
608
609         // Check object does not exist already. If an accessObjects with the
610
// same objectId and mmbaseId then the already existing accessObject is
611
// returned. This because in a stylesheet it's hard to establish if an
612
// object already exists.
613
if (namedObjectContexts.get(objectId) != null) {
614             TmpObject namedObject
615             = (TmpObject) namedObjectContexts.get(objectId);
616             if (namedObject.getMMBaseId() == mmbaseId) {
617                 // Already accessed with this id,
618
// return existing access object.
619
return namedObject;
620             }
621
622             // Id already in use for another object.
623
throw new TransactionHandlerException(
624             "Object id already used for another object: id = \""
625             + objectId + "\"");
626         }
627
628         // Get persistent object, and add to temporary cloud.
629
TmpObject tmpObject = null;
630         try {
631             tmpNodeManager.getObject(
632             uti.user.getName(), objectId, Integer.toString(mmbaseId));
633             tmpObject = new TmpObject(uti, objectId, mmbaseId);
634         } catch (Exception JavaDoc e) {
635             // Exception occurs when object not found in persistent cloud.
636
throw new TransactionHandlerException(
637             "Can't find object with mmbase id " + mmbaseId + " - "
638             + e.getMessage());
639         }
640
641         // Add to transaction.
642
try {
643             transactionManager.addNode(key, uti.user.getName(), objectId);
644         } catch (TransactionManagerException e) {
645             tmpNodeManager.deleteTmpNode(uti.user.getName(), objectId);
646             throw new TransactionHandlerException(e.getMessage());
647         }
648         tmpObjects.put(tmpObject.getKey(), tmpObject);
649         lstTmpObjects.add(tmpObject);
650
651         // If not anonymous object,
652
// register it in the list of named objects in the transaction.
653
if (!anonymous) {
654             namedObjectContexts.put(objectId, tmpObject);
655         }
656
657         if (log.isDebugEnabled()) {
658             log.debug("Access object created: " + tmpObject);
659         }
660         return tmpObject;
661     }
662
663     /**
664      * Deletes object from the context of this transaction, as
665      * well as its relations.
666      * Note that, when the object is an relation, this may
667      * affect its source and/or destination object as well: if it
668      * (the source/destination) is an input object that has its
669      * disposedWhenNotReferenced flag set and this is the last
670      * relation that references it, it is deleted as well.
671      * @param tmpObject the object.
672      * @throws TransactionHandlerException When failing to delete
673      * the object.
674      */

675     public void deleteObject(TmpObject tmpObject)
676     throws TransactionHandlerException {
677
678         // Remove from tmp cloud.
679
try {
680             transactionManager.removeNode(
681             key, uti.user.getName(), tmpObject.getId());
682         } catch (TransactionManagerException e) {
683             throw new TransactionHandlerException(e.getMessage());
684         }
685         tmpNodeManager.deleteTmpNode(uti.user.getName(), tmpObject.getId());
686         tmpObjects.remove(tmpObject.getKey());
687         lstTmpObjects.remove(tmpObject);
688
689         // Remove from list of named objects in the transaction.
690
namedObjectContexts.remove(tmpObject.getId());
691
692         // Delete its relations as well.
693
Iterator i = getRelations(tmpObject).iterator();
694         while (i.hasNext()) {
695             TmpObject relation = (TmpObject) i.next();
696             if (stillExists(relation)) {
697                 if (log.isDebugEnabled()) {
698                     log.debug("About to delete relation " + relation
699                     + " because the referenced object "
700                     + tmpObject.getKey() + " is deleted.");
701                 }
702                 deleteObject(relation);
703             }
704         }
705
706         // If this is a relation, take care of source and destination as well.
707
if (tmpObject.isRelation()) {
708             // Delete source when this is required, based on its
709
// disposeWhenNotReferenced flag.
710
Object JavaDoc _snumber = tmpObject.getField(TmpObject._SNUMBER);
711             if (!_snumber.equals("")) {
712                 TmpObject source = (TmpObject) tmpObjects.get(_snumber);
713                 dropIfRequested(source);
714             }
715
716             // Delete destination when this is required, based on
717
// its disposeWhenNotReferenced flag.
718
Object JavaDoc _dnumber = tmpObject.getField(TmpObject._DNUMBER);
719             if (!_dnumber.equals("")) {
720                 TmpObject destination = (TmpObject) tmpObjects.get(_dnumber);
721                 dropIfRequested(destination);
722             }
723         }
724
725         if (log.isDebugEnabled()) {
726             log.debug("Object deleted: " + tmpObject);
727         }
728     }
729
730     /**
731      * Mark object in the context of this transaction for deletion.
732      * @param deleteRelations - Set to true if all relations are to be deleted too, set to false otherwise.
733      * @param tmpObject - The object.
734      * @throws TransactionHandlerException - When a failure occurred.
735      */

736     public void markDeleteObject(TmpObject tmpObject, boolean deleteRelations)
737     throws TransactionHandlerException {
738         // Mark accessed mmbase object for deletion from persistent cloud.
739
try {
740             transactionManager.deleteObject(
741             key, uti.user.getName(), tmpObject.getId());
742         } catch (TransactionManagerException e) {
743             throw new TransactionHandlerException(e.getMessage());
744         }
745
746         // Check its relations.
747
Iterator iRelations = getRelations(tmpObject).iterator();
748         while (iRelations.hasNext()) {
749             TmpObject relation = (TmpObject) iRelations.next();
750             if (relation.isAccessObject()) {
751                 // Relation in persistent cloud.
752
if (!deleteRelations) {
753                     // The object cannot be deleted, because it has relations
754
// attached, wich are not allowed to be deleted.
755
throw new TransactionHandlerException(
756                     "Object has relation(s) attached to it "
757                     + "(use deleteRelations=\"true\").");
758                 } else {
759                     // Mark relation for delete as well.
760
markDeleteObject(relation, true);
761                 }
762             }
763         }
764
765         // Remove from temporary cloud.
766
tmpNodeManager.deleteTmpNode(uti.user.getName(), tmpObject.getId());
767         tmpObjects.remove(tmpObject.getKey());
768         lstTmpObjects.remove(tmpObject);
769
770         // Remove from list of named objects in the transaction.
771
namedObjectContexts.remove(tmpObject.getId());
772
773         if (log.isDebugEnabled()) {
774             log.debug("Access object marked for deletion: "
775             + tmpObject);
776         }
777     }
778
779     /**
780      * Merges all objects in this transaction of a given type.
781      * @param objectType The type of the objects to merge.
782      * @param finder SimilarObjectFinder instance that prescribes the
783      * actions necessary to find similar objects.
784      * @param merger ObjectMerger instance that prescribes the actions
785      * necessary to merge similar objects.
786      * @throws TransactionHandlerException When a failure occurred.
787      */

788     public void mergeObjects(String JavaDoc objectType, SimilarObjectFinder finder,
789     ObjectMerger merger) throws TransactionHandlerException {
790
791         MMObjectBuilder builder = mmbase.getMMObject(objectType);
792
793         // Create snapshot of all objects in this transaction.
794
TmpObject[] objects = (TmpObject[])lstTmpObjects.toArray(new TmpObject[0]);
795
796         // For all objects in the snapshot of this transaction.
797
for (int i = 0; i < objects.length; i++) {
798             TmpObject tempObj1 = objects[i];
799
800             // Check object still exists and is the specified type.
801
if ( stillExists(tempObj1) &&
802             tempObj1.getNode().getName().equals(objectType)) {
803
804                 // Search for similar object.
805
List similarObjects = finder.findSimilarObject(this, tempObj1);
806
807                 if (similarObjects.size() == 1) {
808                     // One object found, merge the objects.
809
TmpObject tempObj2 = (TmpObject) similarObjects.get(0);
810                     // Merge objects (deletes one of both as well).
811
// Note the order: tempObj2 comes from the part of the
812
// transaction that is already merged or is an access object.
813
merge(tempObj2, tempObj1, merger);
814                 } else if (similarObjects.size() > 1) {
815                     // More than one object found: ambiguity.
816
log.warn("More than one similar object found: " + similarObjects);
817
818                     resolvedDuplicates = resolvedDuplicates && handleDuplicates(
819                     tempObj1, similarObjects, merger);
820                     if (!resolvedDuplicates) {
821                         break;
822                     }
823                 } else if (similarObjects.size() == 0) {
824                     // No object found: delete current object when not allowed to add.
825
if (!merger.isAllowedToAdd(tempObj1)) {
826                         deleteObject(tempObj1);
827                     }
828                 }
829             }
830         }
831
832         if (log.isDebugEnabled()) {
833             log.debug("All objects of type " + objectType + " merged.");
834         }
835     }
836
837     /**
838      * Handles sitiuations where more then one similar objects are found to
839      * merge with. This implementation writes the transaction to a file
840      * includng comments on the duplicates found. The transaction is cancelled.
841      * The file can be used to examine the duplicates and process the
842      * transaction later on.
843      * @param tempObj the original object.
844      * @param similarObjects the similar objects.
845      * @param merger the merger.
846      * @return True if duplicates are resolved.
847      * This implementation always returns false because duplicates are not resolved.
848      * Throws TransactionHandlerException When failing to handle the duplicates
849      * as desired.
850      * @throws TransactionHandlerException When a failure occurred.
851      */

852     protected boolean handleDuplicates(TmpObject tempObj, List similarObjects,
853     ObjectMerger merger) throws TransactionHandlerException {
854         // set duplicates flag
855
if (consultant != null ) {
856             consultant.setDuplicatesFound(true);
857         }
858       appendReportBuffer("\n"+"<!-- *** DUPLICATES FOUND START *** -->\n");
859       appendReportBuffer("what follows:\n");
860       appendReportBuffer("- the original object\n");
861       appendReportBuffer("- similar object blocks\n");
862       appendReportBuffer("where a similar object block holds: " +
863                          "a mergecandidate and a mergeresult\n");
864       appendReportBuffer("and a mergeresult is thew result of a merge of " +
865                          "original object and mergecandidate -->\n\n");
866
867       appendReportBuffer("<!-- *** original object *** -->\n");
868       appendReportBuffer(tempObj.toXML()+"\n");
869
870       Iterator iter = similarObjects.iterator();
871       while (iter.hasNext() ) {
872          TmpObject similarObject = (TmpObject)iter.next();
873          appendReportBuffer("<!-- *** start similar object block *** -->\n\n");
874          appendReportBuffer("<!-- *** mergeCandidate *** -->\n");
875          appendReportBuffer(similarObject.toXML() + "\n");
876          appendReportBuffer("<!-- *** mergeResult *** -->\n");
877          appendReportBuffer(caculateMerge(similarObject, tempObj, merger).toXML() + "\n");
878          appendReportBuffer("<!-- *** end similar object block *** -->\n\n");
879       }
880       appendReportBuffer("<!-- *** DUPLICATES FOUND END *** -->\n\n");
881       return false;
882     }
883
884     /**
885      * Calculates the fields that would result from merging two
886      * temporary objects in this transaction. This does not affect the
887      * actual objects in the transaction. A new TmpObject is created
888      * outside this transaction, to hold the result.
889      * @param merger ObjectMerger instance that prescribes the actions
890      * necessary to merge similar objects.
891      * @param tempObj1 First object.
892      * @param tempObj2 Second object.
893      * @return A TmpObject instance that holds the calculated fields. This
894      * object exists outside this transaction.
895      */

896     protected TmpObject caculateMerge(TmpObject tempObj1, TmpObject tempObj2, ObjectMerger merger){
897         // If the only the second object is an access object, swap the objects.
898
// After that we can be certain that if only one of the objects is
899
// an access object, it is the first one. The first object will be
900
// the merge target, the second will be deleted afterward.
901
if (!tempObj1.isAccessObject() && tempObj2.isAccessObject()) {
902             TmpObject to = tempObj1;
903             tempObj1 = tempObj2;
904             tempObj2 = to;
905         }
906
907         if (log.isDebugEnabled()) {
908             log.debug("About to calculate merge objects: " + tempObj1.getKey()
909             + "(target) and " + tempObj2.getKey());
910         }
911
912         // Merge fields.
913
Iterator fieldNames = tempObj1.getNode().getBuilder().getFieldNames().iterator();
914         while (fieldNames.hasNext()) {
915             String JavaDoc fieldName = (String JavaDoc) fieldNames.next();
916
917             // Merge field for all fields except "number" and "owner".
918
if (!fieldName.equals("number") && !fieldName.equals("owner")) {
919                 merger.mergeField(tempObj1, tempObj2, fieldName);
920             }
921         }
922
923         if (log.isDebugEnabled()) {
924             log.debug("Calculate merge finished for objects:\n target: " + tempObj1.getKey()
925             + " with object: " + tempObj2.getKey());
926         }
927         return tempObj1;
928     }
929
930     /**
931      * Merges two temporary objects in this transaction.
932      * Afterwards one of these will contain the merged object and the other
933      * one will have been deleted.
934      * If neither of the objects is an access object, the first one will
935      * become the merged object, and the second will be deleted.
936      * @param merger ObjectMerger instance that prescribes the actions
937      * necessary to merge similar objects.
938      * @param tempObj1 First object.
939      * @param tempObj2 Second object.
940      * @throws TransactionHandlerException When a failure occurred.
941      */

942     public void merge(TmpObject tempObj1, TmpObject tempObj2, ObjectMerger merger) throws TransactionHandlerException {
943         // If the only the second object is an access object, swap the objects.
944
// After that we can be certain that if only one of the objects is
945
// an access object, it is the first one.
946
// The first object will be the merge target, the second will
947
// be deleted afterward.
948
if (!tempObj1.isAccessObject() && tempObj2.isAccessObject()) {
949             TmpObject to = tempObj1;
950             tempObj1 = tempObj2;
951             tempObj2 = to;
952         }
953
954         if (log.isDebugEnabled()) {
955             log.debug("About to merge objects: " + tempObj1.getKey()
956             + "(target) and " + tempObj2.getKey());
957         }
958
959         // Merge fields.
960
Iterator fieldNames = tempObj1.getNode().getBuilder().getFieldNames().iterator();
961         while (fieldNames.hasNext()) {
962             String JavaDoc fieldName = (String JavaDoc) fieldNames.next();
963
964             // Merge field for all fields except "number" and "owner".
965
if (!fieldName.equals("number") && !fieldName.equals("owner")) {
966                 merger.mergeField(tempObj1, tempObj2, fieldName);
967             }
968         }
969         if (log.isDebugEnabled()) {
970             log.debug("Fields merged of objects: " + tempObj1.getKey()
971             + "(target) and " + tempObj2.getKey());
972         }
973
974         // Merge relations.
975
merger.mergeRelations(tempObj1, tempObj2,
976         getRelations(tempObj1), getRelations(tempObj2));
977
978         if (log.isDebugEnabled()) {
979             log.debug("Relations merged of objects: " + tempObj1.getKey()
980             + "(target) and " + tempObj2.getKey());
981         }
982         // Remove duplicate relations from the merged object.
983
List relations = getRelations(tempObj1);
984         for (int i = 0; i < relations.size(); i++) {
985
986             // All relations of tempObj1.
987
TmpObject relation1 = (TmpObject) relations.get(i);
988
989             // Check if this relation still exists.
990
if (!stillExists(relation1)) {
991                 continue;
992             }
993
994             // If it duplicates an existing relation, drop one of them.
995
for (int i2 = i + 1; i2 < relations.size(); i2++) {
996                 TmpObject relation2 = (TmpObject) relations.get(i2);
997
998                 if (log.isDebugEnabled()) {
999                     log.debug("Relation1: " + relation1);
1000                    log.debug("Relation2: " + relation2);
1001                    log.debug("equalRelations: " + equalRelations(relation1, relation2));
1002                }
1003
1004                if (stillExists(relation2)
1005                && equalRelations(relation1, relation2)
1006                && merger.areDuplicates(relation1, relation2)) {
1007                    // Equal relation found, drop this one if it is
1008
// not an access object. If it is, drop the other one.
1009
if (!relation1.isAccessObject()) {
1010                        if (log.isDebugEnabled()) {
1011                            log.debug("About to delete duplicate relation: "
1012                            + relation1.getKey());
1013                        }
1014                        deleteObject(relation1);
1015                    } else {
1016                        if (relation2.isAccessObject()) {
1017                            if (log.isDebugEnabled()) {
1018                                log.debug("About to mark for deletion "
1019                                + "duplicate relation: "
1020                                + relation2.getKey());
1021                            }
1022                            markDeleteObject(relation2, false);
1023                        } else {
1024                            if (log.isDebugEnabled()) {
1025                                log.debug("About to delete duplicate relation: "
1026                                + relation2.getKey());
1027                            }
1028                            deleteObject(relation2);
1029                        }
1030                    }
1031                }
1032            }
1033        }
1034
1035        // Dispose of tempObj2.
1036
if (tempObj2.isAccessObject()) {
1037            // Delete tempObj2 from persistent cloud.
1038
if (log.isDebugEnabled()) {
1039                log.debug("About to mark object " + tempObj2.getKey()
1040                + "for deletion, having merged it with "
1041                + tempObj1.getKey());
1042            }
1043            markDeleteObject(tempObj2, true);
1044        } else {
1045            // Delete tempObj2 from temporary cloud.
1046
if (log.isDebugEnabled()) {
1047                log.debug("About to delete object " + tempObj2.getKey()
1048                + ", having merged it with " + tempObj1.getKey());
1049            }
1050            deleteObject(tempObj2);
1051        }
1052
1053        if (log.isDebugEnabled()) {
1054            log.debug("Objects merged: " + tempObj1.getKey()
1055            + "(target) and " + tempObj2.getKey());
1056        }
1057
1058        // Put merge result in map. Key is the deleted object. Used for tracking
1059
// the relation between a deleted original object and the object tha
1060
// the object that contains the merge result.
1061
mergedObjects.put(tempObj2, tempObj1);
1062    }
1063
1064    /**
1065     * Gets an access object for a specified node in the persistent
1066     * cloud. If such an access object already exists in this transaction,
1067     * this object is returned, otherwise a new access object is created.
1068     * @param mmbaseId MMBase number of the specified node.
1069     * @return the access object in this transaction context,
1070     * or null if such an access object does not exist.
1071     * @throws TransactionHandlerException When unable to create
1072     * the access object.
1073     */

1074    public TmpObject getAccessObject(int mmbaseId)
1075    throws TransactionHandlerException {
1076        // No need to search if mmbaseId is not a valid MMBase Id.
1077
if (mmbaseId == -1) {
1078            return null;
1079        }
1080        // Search through the objects in this transaction.
1081
Iterator i = lstTmpObjects.iterator();
1082        while (i.hasNext()) {
1083            TmpObject tmpObject = (TmpObject) i.next();
1084            if (tmpObject.getMMBaseId() == mmbaseId) {
1085                // Found, return it.
1086
return tmpObject;
1087            }
1088        }
1089
1090        // Not found, create new access object.
1091
return accessObject(null, mmbaseId);
1092    }
1093
1094    /**
1095     * For an object in this transaction, looks up all its relations,
1096     * and returns access objects for these.
1097     * @param tmpObject An object in the temporary cloud (can
1098     * be an access objects).
1099     * @return List of access objects for the found relations.
1100     * @exception TransactionHandlerException When a failure occurred.
1101     */

1102    public List getRelations(TmpObject tmpObject)
1103    throws TransactionHandlerException {
1104
1105        List accessObjects = new ArrayList();
1106
1107        // If it's an access object, access all its relations in persistent cloud.
1108
if (tmpObject.isAccessObject()) {
1109            Vector relations = tmpObject.getRelationsInPersistentCloud();
1110            Iterator i = relations.iterator();
1111            while (i.hasNext()) {
1112                MMObjectNode relation = (MMObjectNode) i.next();
1113
1114                // Get access object for relation.
1115
// It may have been accessed before in this transaction, and
1116
// have its source/destination changed in the process.
1117
// For this reason source/destination have to be tested again
1118
// for this object. This will be done in the next step.
1119
getAccessObject(relation.getIntValue("number"));
1120            }
1121        }
1122
1123        // Visit all its relations in temporary cloud, add them to the list.
1124
// This includes the access objects that were created for all
1125
// relations found in the persistent cloud.
1126
Iterator i2 = lstTmpObjects.iterator();
1127        while (i2.hasNext()) {
1128            TmpObject tmpObj2 = (TmpObject) i2.next();
1129            if (tmpObj2.isRelation()
1130            && (tmpObject.isSourceOf(tmpObj2)
1131            || tmpObject.isDestinationOf(tmpObj2))) {
1132
1133                // Relation: add
1134
accessObjects.add(tmpObj2);
1135            }
1136        }
1137        return accessObjects;
1138    }
1139
1140    /**
1141     * Drops an object from the temporary cloud, based on its
1142     * disposeWhenNotReferenced flag - i.e. drop it only when it
1143     * is not an access object, is has no relations and the flag
1144     * is set.
1145     * @param tmpObject An object in the temporary cloud.
1146     * @throws TransactionHandlerException When a failure occurred.
1147     */

1148    void dropIfRequested(TmpObject tmpObject)
1149    throws TransactionHandlerException {
1150        if (stillExists(tmpObject) // not deleted already
1151
&& !tmpObject.isAccessObject() // is not access object
1152
&& getRelations(tmpObject).size() == 0 // is unreferenced
1153
&& tmpObject.getDisposeWhenNotReferenced()) { // flag is set
1154

1155            // Delete.
1156
if (log.isDebugEnabled()) {
1157                log.debug("About to delete object " + tmpObject.getKey()
1158                + " because it has become unreferenced.");
1159            }
1160            deleteObject(tmpObject);
1161        }
1162    }
1163
1164    /**
1165     * Test if two objects in the temporary cloud represent the same relation
1166     * (are of same relation type and have the same source and destination objects).
1167     * This takes into account that an (access)
1168     * object in the temporary cloud may represent an object in the
1169     * persistent cloud.
1170     * @param tmpObj1 The first object.
1171     * @param tmpObj2 The second object.
1172     * @return True if both objects represent the same relation,
1173     * false otherwise.
1174     */

1175    protected boolean equalRelations(TmpObject tmpObj1, TmpObject tmpObj2) {
1176        // Test if they're both relations.
1177
if (!tmpObj1.isRelation() || !tmpObj2.isRelation()) {
1178            return false;
1179        }
1180
1181        // Test same relationtype.
1182
if (tmpObj1.getNode().getIntValue(TmpObject.RNUMBER)
1183        != tmpObj2.getNode().getIntValue(TmpObject.RNUMBER)) {
1184            return false;
1185        }
1186
1187        // Test same source.
1188
String JavaDoc sourceId = tmpObj1.getNode().getStringValue(TmpObject._SNUMBER);
1189        if (!sourceId.equals("")) {
1190            // Source of tmpObj1 is temporary node.
1191
TmpObject source = (TmpObject) tmpObjects.get(sourceId);
1192            if (!source.isSourceOf(tmpObj2)) {
1193                return false;
1194            }
1195        } else {
1196            sourceId = tmpObj2.getNode().getStringValue(TmpObject._SNUMBER);
1197            if (!sourceId.equals("")) {
1198                // Source of tmpObj2 is temporary node.
1199
TmpObject source = (TmpObject) tmpObjects.get(sourceId);
1200                if (!source.isSourceOf(tmpObj1)) {
1201                    return false;
1202                }
1203            } else {
1204                // Sources of both is persistent node.
1205
if (!tmpObj1.getNode().getStringValue(TmpObject.SNUMBER).equals(
1206                tmpObj2.getNode().getStringValue(TmpObject.SNUMBER))) {
1207                    return false;
1208                }
1209            }
1210        }
1211
1212        // Test same destination.
1213
String JavaDoc destinationId
1214        = tmpObj1.getNode().getStringValue(TmpObject._DNUMBER);
1215        if (!destinationId.equals("")) {
1216            // Destination of tmpObj1 is temporary node.
1217
TmpObject destination = (TmpObject) tmpObjects.get(destinationId);
1218            if (!destination.isDestinationOf(tmpObj2)) {
1219                return false;
1220            }
1221        } else {
1222            destinationId = tmpObj2.getNode().getStringValue(TmpObject._DNUMBER);
1223            if (!destinationId.equals("")) {
1224                // Destination of tmpObj2 is temporary node.
1225
TmpObject destination = (TmpObject) tmpObjects.get(destinationId);
1226                if (!destination.isDestinationOf(tmpObj1)) {
1227                    return false;
1228                }
1229            } else {
1230                // Destinations of both is persistent node.
1231
if (!tmpObj1.getNode().getStringValue(TmpObject.DNUMBER).equals(
1232                tmpObj2.getNode().getStringValue(TmpObject.DNUMBER))) {
1233                    return false;
1234                }
1235            }
1236        }
1237
1238        return true;
1239    }
1240
1241    /**
1242     * Tests if this object still exists in the temporary cloud.
1243     * @param tmpObject A temporary object.
1244     * @return True if the object still exists in the temporary
1245     * cloud, false otherwise.
1246     */

1247    protected boolean stillExists(TmpObject tmpObject) {
1248        return lstTmpObjects.contains(tmpObject);
1249    }
1250
1251    /**
1252     * Key accessor.
1253     * @return TransactionManager key.
1254     */

1255    public String JavaDoc getKey() {
1256        return key;
1257    }
1258
1259    /**
1260     * Gets HashMap of all non-anonymous object contexts, mapped by their id.
1261     * @return the object context map.
1262     */

1263    HashMap getObjectContexts() {
1264        return namedObjectContexts;
1265    }
1266
1267    /**
1268     * Gets (unmodifiable) list of all temporary objects in the transaction.
1269     * @return List of all temporary objects in the transaction.
1270     */

1271    public List getTmpObjects() {
1272        return Collections.unmodifiableList(lstTmpObjects);
1273    }
1274
1275    /**
1276     * Gets merged object, resulting from previous merge operations.
1277     * After a merge the result is put in a map with the deleted object as key
1278     * so it can be looked up by this method.
1279     * @param tempObj1 The original object.
1280     * @return The object that contains the result of the previous merge operation.
1281     * This can be tempObj1 itself or the object tempObj1 is merged with.
1282     */

1283    public TmpObject getMergedObject(TmpObject tempObj1) {
1284        TmpObject tempObj2 = (TmpObject) mergedObjects.get(tempObj1);
1285        if (tempObj2 == null) {
1286            tempObj2 = tempObj1;
1287        }
1288        return tempObj2;
1289    }
1290
1291    /**
1292     * Add text to reportBufferFile of this transaction.
1293     * @param str Text to add to reportBuffer.
1294     */

1295    public void appendReportBuffer(String JavaDoc str) {
1296        reportBuffer.append(str);
1297    }
1298
1299    /**
1300     * Start the Transaction. If it is not stopped explicitly
1301     * (commit or delete), it will timeout eventually.
1302     */

1303    protected void start() {
1304        if (kicker == null) {
1305            kicker = new Thread JavaDoc(this, "TR " + key);
1306            kicker.start();
1307        }
1308    }
1309
1310    /**
1311     * Stop the Transaction.
1312     */

1313    protected synchronized void stop() {
1314
1315        kicker = null;
1316        finished = true;
1317        this.notify();
1318
1319        log.info("Stop transaction: " + new Date().toString());
1320    }
1321
1322    /**
1323     * Wait assynchronously for the transaction to time out.
1324     * This can be ended by invoking stop().
1325     */

1326    public void run() {
1327        synchronized(this) {
1328            try {
1329                wait(timeOut*1000);
1330            } catch (InterruptedException JavaDoc e) {
1331            }
1332            uti.knownTransactionContexts.remove(id);
1333
1334            if (!finished) {
1335                if (consultant != null) {
1336                    consultant.setImportStatus(Consultant.IMPORT_TIMED_OUT);
1337                }
1338                log.warn("Transaction with id=" + id + " is timed out after "
1339                + timeOut + " seconds.");
1340            }
1341        }
1342    }
1343}
1344
Popular Tags