Source Code Cross Referenced for TransactionManager.java in  » ERP-CRM-Financial » jmoney » net » sf » jmoney » isolation » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » ERP CRM Financial » jmoney » net.sf.jmoney.isolation 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*
0002:         *
0003:         *  JMoney - A Personal Finance Manager
0004:         *  Copyright (c) 2005 Nigel Westbury <westbury@users.sourceforge.net>
0005:         *
0006:         *
0007:         *  This program is free software; you can redistribute it and/or modify
0008:         *  it under the terms of the GNU General Public License as published by
0009:         *  the Free Software Foundation; either version 2 of the License, or
0010:         *  (at your option) any later version.
0011:         *
0012:         *  This program is distributed in the hope that it will be useful,
0013:         *  but WITHOUT ANY WARRANTY; without even the implied warranty of
0014:         *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0015:         *  GNU General Public License for more details.
0016:         *
0017:         *  You should have received a copy of the GNU General Public License
0018:         *  along with this program; if not, write to the Free Software
0019:         *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
0020:         *
0021:         */
0022:
0023:        package net.sf.jmoney.isolation;
0024:
0025:        import java.util.AbstractCollection;
0026:        import java.util.Collection;
0027:        import java.util.HashMap;
0028:        import java.util.HashSet;
0029:        import java.util.Iterator;
0030:        import java.util.Map;
0031:        import java.util.Set;
0032:        import java.util.Vector;
0033:
0034:        import net.sf.jmoney.JMoneyPlugin;
0035:        import net.sf.jmoney.model2.AbstractDataOperation;
0036:        import net.sf.jmoney.model2.Account;
0037:        import net.sf.jmoney.model2.DataManager;
0038:        import net.sf.jmoney.model2.Entry;
0039:        import net.sf.jmoney.model2.EntryInfo;
0040:        import net.sf.jmoney.model2.ExtendableObject;
0041:        import net.sf.jmoney.model2.ExtendablePropertySet;
0042:        import net.sf.jmoney.model2.ExtensionPropertySet;
0043:        import net.sf.jmoney.model2.IListManager;
0044:        import net.sf.jmoney.model2.IObjectKey;
0045:        import net.sf.jmoney.model2.ISessionChangeFirer;
0046:        import net.sf.jmoney.model2.IValues;
0047:        import net.sf.jmoney.model2.ListKey;
0048:        import net.sf.jmoney.model2.ListPropertyAccessor;
0049:        import net.sf.jmoney.model2.ObjectCollection;
0050:        import net.sf.jmoney.model2.PropertySet;
0051:        import net.sf.jmoney.model2.ReferencePropertyAccessor;
0052:        import net.sf.jmoney.model2.ScalarPropertyAccessor;
0053:        import net.sf.jmoney.model2.Session;
0054:        import net.sf.jmoney.model2.SessionChangeListener;
0055:        import net.sf.jmoney.model2.SessionInfo;
0056:        import net.sf.jmoney.model2.Transaction;
0057:        import net.sf.jmoney.model2.TransactionInfo;
0058:
0059:        import org.eclipse.core.commands.ExecutionException;
0060:        import org.eclipse.core.commands.operations.IOperationHistory;
0061:        import org.eclipse.core.commands.operations.IUndoContext;
0062:        import org.eclipse.core.commands.operations.IUndoableOperation;
0063:        import org.eclipse.core.runtime.Assert;
0064:        import org.eclipse.core.runtime.IStatus;
0065:        import org.eclipse.core.runtime.Status;
0066:
0067:        /**
0068:         * A transaction manager must be set before the datastore can be modified.
0069:         * An exception will be throw if an attempt is made to modify the datastore
0070:         * (setting a property, or creating or deleting an extendable object) when
0071:         * no transaction manager is set in the session.
0072:         * <P>
0073:         * Changes to the datastore are stored in the transaction manager.  They
0074:         * are not applied to the underlying datastore until the transaction is
0075:         * committed.  Read accesses (property getters and queries) are passed on
0076:         * to any transaction manager which will modify the results to reflect
0077:         * changes stored in the transaction.
0078:         * 
0079:         * @author Nigel Westbury
0080:         */
0081:        public class TransactionManager extends DataManager {
0082:            private DataManager baseDataManager;
0083:
0084:            // TODO: At some time, review this and ensure that we
0085:            // really do need the session object here.
0086:            private Session uncommittedSession;
0087:
0088:            /**
0089:             * Maps IObjectKey to Map, where IObjectKey is the key in the comitted
0090:             * datastore and each Map maps PropertyAccessor to the value of that
0091:             * property. Every object that has been modified by this transaction manager
0092:             * (a scalar property in the object has been changed) will have an entry in
0093:             * this map. The map keys are objects from the datastore and will contain
0094:             * the property values that are currently committed in the datastore. The
0095:             * map values are objects that contain details of the changes (changed
0096:             * scalar property values, or an indication that the object has been
0097:             * deleted).
0098:             * <P>
0099:             * If a value of a property is a reference to another object then an
0100:             * UncommittedObjectKey is stored as the value. By doing this, the
0101:             * referenced object does not need to be materialized unless necessary.
0102:             * <P>
0103:             * Deleted objects will also have an entry in this map. If an object
0104:             * contains list properties and the object is deleted then all the objects
0105:             * in the lists will also be added to this map with a ModifiedObject that
0106:             * has a 'deleted' indication. This is necessary because this list is used
0107:             * to determine if an object has been deleted, to ensure that we do not
0108:             * attempt to modify a property value in an object that has in fact been
0109:             * deleted.
0110:             */
0111:            // TODO: Now we have the allObjects map, this map can be removed.  The code
0112:            // should be significantly simplified now that we don't have to re-apply
0113:            // the changes each time an object is requested.
0114:            // (Of course, the complications now need to be added to the listener code
0115:            // that needs to keep the objects in allObjects map up to date
0116:            // and tell our listeners.
0117:            Map<IObjectKey, ModifiedObject> modifiedObjects = new HashMap<IObjectKey, ModifiedObject>();
0118:
0119:            /**
0120:             * Every extendable object that has ever been created in this transaction and passed
0121:             * to the user is in this map.  This is required because all DataManager
0122:             * objects must guarantee that there is only ever a single instance of an
0123:             * object in existence.
0124:             * 
0125:             * Objects that were created in this transaction are not in this map.
0126:             * There is only one instance of such objects in any case, so only that one
0127:             * instance would be returned.
0128:             * 
0129:             * The object key is the key in the committed datastore.  Only objects
0130:             * that exist in the underlying datastore are in this map, and it is
0131:             * just easier to use the committed key than to use an uncommitted key.  
0132:             */
0133:            Map<IObjectKey, ExtendableObject> allObjects = new HashMap<IObjectKey, ExtendableObject>();
0134:
0135:            /**
0136:             * Every list that has been modified by this transaction manager
0137:             * (objects added to the list or objects removed from the list)
0138:             * will have an entry in this set.  This enables the transaction
0139:             * manager to easily find the added and deleted objects when committing
0140:             * the changes.
0141:             */
0142:            Set<DeltaListManager> modifiedLists = new HashSet<DeltaListManager>();
0143:
0144:            /**
0145:             * true if a nested transaction is in the process of applying its
0146:             * changes to our data
0147:             */
0148:            // TODO: use this flag to ensure that performRefresh is called correctly.
0149:            private boolean insideTransaction = false;
0150:
0151:            /**
0152:             * Construct a transaction manager for use with the given session.
0153:             * The transaction manager does not become the active transaction
0154:             * manager for the session until it is specifically set as the
0155:             * active transaction manager.  By separating the construction of
0156:             * the transaction manager from the activating of the transaction manager,
0157:             * a transaction manager can be created and listeners can be set up to
0158:             * listen to session changes within the transaction manager during an
0159:             * initialization stage even though the transaction is not made active
0160:             * until changes are made.
0161:             *  
0162:             * @param session the session object from the committed datastore
0163:             */
0164:            public TransactionManager(DataManager baseDataManager) {
0165:                this .baseDataManager = baseDataManager;
0166:                this .uncommittedSession = getCopyInTransaction(baseDataManager
0167:                        .getSession());
0168:
0169:                /*
0170:                 * Listen for changes to the base data.  Note that
0171:                 * a weak reference is maintained to this listener
0172:                 * so we don't have to worry about removing the listener,
0173:                 * which is just as well because this object may be left
0174:                 * for the garbage collector without knowing when it is
0175:                 * no longer being used.
0176:                 */
0177:                baseDataManager
0178:                        .addChangeListenerWeakly(new MySessionChangeListener());
0179:            }
0180:
0181:            /**
0182:             * @return a session object representing an uncommitted
0183:             * 			session object managed by this transaction manager
0184:             */
0185:            @Override
0186:            public Session getSession() {
0187:                return uncommittedSession;
0188:            }
0189:
0190:            /**
0191:             * Indicate whether there are any uncommitted changes being held
0192:             * by this transaction manager.  This method is useful when the user
0193:             * does something like selecting another transaction or closing a
0194:             * dialog box and it is not clear whether the user wants to commit
0195:             * or to cancel changes.
0196:             *
0197:             * @return true if property values have been changed or objects
0198:             * 			have been created or deleted within the context of this
0199:             * 			transaction manager since the last commit
0200:             */
0201:            public boolean hasChanges() {
0202:                return !modifiedObjects.isEmpty() || !modifiedLists.isEmpty();
0203:            }
0204:
0205:            /**
0206:             * Given an instance of an object in the datastore
0207:             * (i.e. committed), obtain a copy of the object that 
0208:             * is in the version of the datastore managed by this
0209:             * transaction manager.
0210:             * <P>
0211:             * Updates to property values in the returned object will
0212:             * not be applied to the datastore until the changes held
0213:             * by this transaction manager are committed to the datastore.
0214:             * 
0215:             * @param an object that exists in the datastore (committed)
0216:             * @return a copy of the given object, being an uncommitted version
0217:             * 			of the object in this transaction, or null if the
0218:             * 			given object has been deleted in this transaction 
0219:             */
0220:            public <E extends ExtendableObject> E getCopyInTransaction(
0221:                    final E committedObject) {
0222:                /*
0223:                 * As a convenience to the caller, this method accepts null objects. If
0224:                 * a reference is null in the committed version of the data then it
0225:                 * should of course be null in the uncommitted version of the data.
0226:                 */
0227:                if (committedObject == null) {
0228:                    return null;
0229:                }
0230:
0231:                if (committedObject.getDataManager() != baseDataManager) {
0232:                    throw new RuntimeException(
0233:                            "Invalid call to getCopyInTransaction.  The object passed must belong to the data manager that is the base data manager of this transaction manager.");
0234:                }
0235:
0236:                // First look in our map to see if this object has already been
0237:                // modified within the context of this transaction manager.
0238:                // If it has, return the modified version.
0239:                // Also check to see if the object has been deleted within the
0240:                // context of this transaction manager.  If it has then we raise
0241:                // an error.  
0242:
0243:                // Both these situations are not likely to happen
0244:                // because usually one object is copied into the transaction
0245:                // manager and all other objects are obtained by traversing
0246:                // from that object.  However, it is good to check.
0247:
0248:                ExtendableObject objectInTransaction = allObjects
0249:                        .get(committedObject.getObjectKey());
0250:                if (objectInTransaction != null) {
0251:                    // TODO: decide if and how we check for deleted objects.
0252:                    //			if (objectInTransaction.isDeleted()) {
0253:                    //				throw new RuntimeException("Attempt to get copy of object, but that object has been deleted in the transaction");
0254:                    //			}
0255:                    return (E) committedObject.getClass().cast(
0256:                            objectInTransaction);
0257:                }
0258:
0259:                ExtendablePropertySet<? extends E> propertySet = PropertySet
0260:                        .getPropertySet((Class<? extends E>) committedObject
0261:                                .getClass());
0262:
0263:                ListKey<? super  E> committedListKey = committedObject
0264:                        .getParentListKey();
0265:                ListKey<? super  E> listKey;
0266:                if (committedListKey == null) {
0267:                    listKey = null;
0268:                } else {
0269:                    IObjectKey committedParentKey = committedListKey
0270:                            .getParentKey();
0271:                    UncommittedObjectKey parentKey = new UncommittedObjectKey(
0272:                            this , committedParentKey);
0273:                    listKey = new ListKey(parentKey, committedListKey
0274:                            .getListPropertyAccessor());
0275:                }
0276:
0277:                final UncommittedObjectKey key = new UncommittedObjectKey(this ,
0278:                        committedObject.getObjectKey());
0279:
0280:                /*
0281:                 * If the object has been modified by this transaction, it will be in
0282:                 * the allObjects map (which contains all objects ever returned by this
0283:                 * transaction)
0284:                 */
0285:
0286:                IValues values = new IValues() {
0287:
0288:                    public Collection<ExtensionPropertySet<?>> getNonDefaultExtensions() {
0289:                        // TODO: This is not correct.  Extensions may contain properties
0290:                        // that are references to other extendable objects.
0291:                        // These need to be converted to versions in this transaction,
0292:                        // as is done in the getReferencedObjectKey below.
0293:                        return committedObject.getExtensions();
0294:                    }
0295:
0296:                    public IObjectKey getReferencedObjectKey(
0297:                            ReferencePropertyAccessor propertyAccessor) {
0298:                        IObjectKey committedObjectKey = propertyAccessor
0299:                                .invokeObjectKeyField(committedObject);
0300:                        return (committedObjectKey == null) ? null
0301:                                : new UncommittedObjectKey(
0302:                                        TransactionManager.this ,
0303:                                        committedObjectKey);
0304:                    }
0305:
0306:                    public <V> V getScalarValue(
0307:                            ScalarPropertyAccessor<V> propertyAccessor) {
0308:                        return committedObject
0309:                                .getPropertyValue(propertyAccessor);
0310:                    }
0311:
0312:                    public <E2 extends ExtendableObject> IListManager<E2> getListManager(
0313:                            IObjectKey listOwnerKey,
0314:                            ListPropertyAccessor<E2> listAccessor) {
0315:                        return new DeltaListManager<E2>(
0316:                                TransactionManager.this , committedObject, key,
0317:                                listAccessor);
0318:                    }
0319:                };
0320:
0321:                // We can now create the object.
0322:                E copyInTransaction = (E) committedObject.getClass().cast(
0323:                        propertySet.constructImplementationObject(key, listKey,
0324:                                values));
0325:
0326:                /*
0327:                 * Now we have created a version of this object that is valid in this datastore,
0328:                 * put it in the map so that we can guarantee that we always return the same
0329:                 * instance in future.
0330:                 */
0331:                allObjects.put(committedObject.getObjectKey(),
0332:                        copyInTransaction);
0333:
0334:                // We do not copy lists owned by the object at this time.
0335:                // If a list is iterated, we must return copies in this transaction.
0336:                // Originals may never be materialized.
0337:
0338:                // If a list is iterated multiple times, a new set
0339:                // is instantiated.  This is consistent with list processing
0340:                // when objects are stored in database - do not cache the
0341:                // list because it must then be kept up to date and the
0342:                // list may no longer be needed, plus lists are not in general
0343:                // iterated more than once because the consumer should keep its
0344:                // own data up to date using listeners.
0345:
0346:                // This is ok because the API contract states
0347:                // that if the user gets the same object
0348:                // twice then the user must listen for changes and refresh
0349:                // the other objects.  This is something the user has to
0350:                // do anyway because objects could be out of date due to
0351:                // changes to the committed version.
0352:
0353:                // Therefore, a list is really a delta from the committed list.
0354:
0355:                // How does the delta get the committed list?
0356:                // We could pass an iterator here, but that is a once
0357:                // off.  We must pass a dynamic collection (a collection
0358:                // that is a view of the committed items in the list)
0359:
0360:                return copyInTransaction;
0361:            }
0362:
0363:            /**
0364:             * @param account
0365:             * @return
0366:             */
0367:            @Override
0368:            public boolean hasEntries(Account account) {
0369:                return !new ModifiedAccountEntriesList(account).isEmpty();
0370:            }
0371:
0372:            /**
0373:             * @param account
0374:             * @return
0375:             */
0376:            @Override
0377:            public Collection<Entry> getEntries(Account account) {
0378:                return new ModifiedAccountEntriesList(account);
0379:            }
0380:
0381:            /**
0382:             * Apply the changes that are stored in this transaction manager.
0383:             * <P>
0384:             * When changes are committed, they are seen in the datastore
0385:             * and also in the version of the datastore as seen through other
0386:             * transaction managers.
0387:             * <P>
0388:             * All datastore listeners and all listeners which are listening
0389:             * for changes ......
0390:             * 
0391:             */
0392:            public void commit() {
0393:                baseDataManager.startTransaction();
0394:
0395:                // Add all the new objects, but set references to other
0396:                // new objects to null because the other new object may
0397:                // not have yet been added to the database and thus no
0398:                // reference can be set to the other object.
0399:                for (DeltaListManager<?> modifiedList : modifiedLists) {
0400:                    commitObjectsInList(modifiedList);
0401:                }
0402:
0403:                // Update all the updated objects
0404:                for (Map.Entry<IObjectKey, ModifiedObject> mapEntry : modifiedObjects
0405:                        .entrySet()) {
0406:                    IObjectKey committedKey = mapEntry.getKey();
0407:                    ModifiedObject newValuesMap = mapEntry.getValue();
0408:
0409:                    if (!newValuesMap.isDeleted()) {
0410:                        Map<ScalarPropertyAccessor, Object> propertyMap = newValuesMap
0411:                                .getMap();
0412:                        /* Actually this does not work.  We must go through the setters because we are 'outside' the datastore.
0413:                         * The values would not otherwise be set in the objects themselves.
0414:                        
0415:                         ExtendableObject committedObject = committedKey.getObject();
0416:                         PropertySet<?> actualPropertySet = PropertySet.getPropertySet(committedObject.getClass());
0417:
0418:                         int count = actualPropertySet.getScalarProperties3().size();
0419:                         Object [] newValues = new Object[count];
0420:                         Object [] oldValues = new Object[count];
0421:                        
0422:                         // TODO: It may be better if we save the data from the committed
0423:                         // object early on, before any changes.  This really depends on
0424:                         // how we decide to cope with conflicts between the committed and
0425:                         // this data manager.
0426:                         int index = 0;
0427:                         for (ScalarPropertyAccessor<?> accessor: actualPropertySet.getScalarProperties3()) {
0428:                         Object value = committedObject.getPropertyValue(accessor);
0429:                         if (value instanceof ExtendableObject) {
0430:                         ExtendableObject referencedObject = (ExtendableObject)value;
0431:                         oldValues[index] = referencedObject.getObjectKey();
0432:                         } else {
0433:                         oldValues[index] = value;
0434:                         }
0435:                        
0436:                         if (propertyMap.containsKey(accessor)) {
0437:                         Object newValue = propertyMap.get(accessor);
0438:                         if (newValue instanceof UncommittedObjectKey) {
0439:                         UncommittedObjectKey referencedKey = (UncommittedObjectKey)newValue;
0440:                         newValues[index] = referencedKey.getCommittedObjectKey();
0441:                         } else {
0442:                         newValues[index] = value;
0443:                         }
0444:                         } else {
0445:                         // No change in the property value
0446:                         newValues[index] = oldValues[index];
0447:                         }
0448:                        
0449:                         index++;
0450:                         }
0451:                         */
0452:
0453:                        for (Map.Entry<ScalarPropertyAccessor, Object> mapEntry2 : propertyMap
0454:                                .entrySet()) {
0455:                            ScalarPropertyAccessor<?> accessor = mapEntry2
0456:                                    .getKey();
0457:                            Object newValue = mapEntry2.getValue();
0458:
0459:                            // TODO: If we create a method in IObjectKey for updating properties
0460:                            // then we don't have to instantiate the committed object here.
0461:                            // The advantages are small, however, because the key is likely to
0462:                            // need to read the old properties from the database before setting
0463:                            // the new property values.
0464:                            ExtendableObject committedObject = committedKey
0465:                                    .getObject();
0466:
0467:                            if (newValue instanceof  UncommittedObjectKey) {
0468:                                UncommittedObjectKey referencedKey = (UncommittedObjectKey) newValue;
0469:                                // TODO: We should not have to instantiate an instance of the
0470:                                // referenced object in the committed datastore.  However, there
0471:                                // is no method to set the key as the value.
0472:                                // We should add such a mechanism.
0473:                                newValue = referencedKey
0474:                                        .getCommittedObjectKey().getObject();
0475:                            }
0476:                            setProperty(committedObject, accessor, newValue);
0477:                        }
0478:                    }
0479:                }
0480:
0481:                /*
0482:                 * Delete all object marked for deletion. This is a two-step process.
0483:                 * The first step involves iterating over all the objects marked for
0484:                 * deletion and seeing if they contain any references to other objects
0485:                 * that are also marked for deletion. If any such references are found,
0486:                 * the reference is set to null. The second step involves actually
0487:                 * deleting the objects. The reason why we must do this two-step process
0488:                 * is that there may be circular references between objects marked for
0489:                 * deletion, and the underlying database may raise a reference constaint
0490:                 * violation if an object is deleted while other objects contain
0491:                 * references to it.
0492:                 * 
0493:                 * This code is not perfect and needs more work to make it perfect.
0494:                 * Firstly, there may be a problem if the underlying database does not
0495:                 * allow null values for a particular reference. In that case, we should
0496:                 * ignore the failure to set the value to null. We set what we can to
0497:                 * null and then go on to delete all the values. Secondly, we may have
0498:                 * to make multiple passes while attempting to delete all the objects
0499:                 * because if one contains a non-nullable reference to the other then it
0500:                 * must be deleted first. It should be theoretically possible to delete
0501:                 * the objects, because the database got into this state in the first
0502:                 * case, and we can remove the objects by reversing the steps taken to
0503:                 * get to this state.
0504:                 */
0505:
0506:                /*
0507:                 * Step 1: Update all the objects marked for deletion, setting any
0508:                 * references to other objects also marked for deletion to be null
0509:                 * references.
0510:                 */
0511:                for (Map.Entry<IObjectKey, ModifiedObject> mapEntry : modifiedObjects
0512:                        .entrySet()) {
0513:                    IObjectKey committedKey = mapEntry.getKey();
0514:                    ModifiedObject newValues = mapEntry.getValue();
0515:
0516:                    if (newValues.isDeleted()) {
0517:                        ExtendableObject deletedObject = committedKey
0518:                                .getObject();
0519:
0520:                        ExtendablePropertySet<?> propertySet = PropertySet
0521:                                .getPropertySet(deletedObject.getClass());
0522:                        for (ScalarPropertyAccessor<?> accessor : propertySet
0523:                                .getScalarProperties3()) {
0524:                            Object value = deletedObject
0525:                                    .getPropertyValue(accessor);
0526:                            if (value instanceof  ExtendableObject) {
0527:                                ExtendableObject referencedObject = (ExtendableObject) value;
0528:                                IObjectKey committedReferencedKey = referencedObject
0529:                                        .getObjectKey();
0530:                                ModifiedObject referencedNewValues = modifiedObjects
0531:                                        .get(committedReferencedKey);
0532:                                if (referencedNewValues != null
0533:                                        && referencedNewValues.isDeleted()) {
0534:                                    // This is a reference to an object that is marked for deletion.
0535:                                    // Set the reference to null
0536:                                    deletedObject.setPropertyValue(accessor,
0537:                                            null);
0538:                                }
0539:                            }
0540:                        }
0541:                    }
0542:                }
0543:
0544:                /*
0545:                 * Step 2: Delete the deleted objects
0546:                 */
0547:                for (DeltaListManager<?> modifiedList : modifiedLists) {
0548:
0549:                    ExtendableObject parent = modifiedList.committedParent;
0550:
0551:                    for (IObjectKey objectToDelete : modifiedList
0552:                            .getDeletedObjects()) {
0553:                        parent.getListPropertyValue(modifiedList.listAccessor)
0554:                                .remove(objectToDelete.getObject());
0555:                    }
0556:                }
0557:
0558:                baseDataManager.commitTransaction();
0559:
0560:                // Clear out the changes in the object. These changes are the
0561:                // delta between the datastore and the uncommitted view.
0562:                // Now that the changes have been committed, these changes
0563:                // must be cleared.
0564:                for (DeltaListManager<?> modifiedList : modifiedLists) {
0565:                    modifiedList.addedObjects.clear();
0566:                    modifiedList.deletedObjects.clear();
0567:                }
0568:                modifiedLists.clear();
0569:                modifiedObjects.clear();
0570:            }
0571:
0572:            /**
0573:             * This method does the same as the commit() method but the changes
0574:             * may be undone and redone.  This support is available with no
0575:             * coding needed by the caller other than to pass the label to be
0576:             * used to describe the operation.
0577:             * 
0578:             * @param label the label to be used to describe this operation
0579:             * 			for undo/redo purposes
0580:             */
0581:            public void commit(String label) {
0582:                IUndoContext undoContext = baseDataManager.getSession()
0583:                        .getUndoContext();
0584:                IOperationHistory history = JMoneyPlugin.getDefault()
0585:                        .getWorkbench().getOperationSupport()
0586:                        .getOperationHistory();
0587:
0588:                IUndoableOperation operation = new AbstractDataOperation(
0589:                        baseDataManager.getSession(), label) {
0590:                    @Override
0591:                    public IStatus execute() throws ExecutionException {
0592:                        commit();
0593:                        return Status.OK_STATUS;
0594:                    }
0595:                };
0596:
0597:                operation.addContext(undoContext);
0598:                try {
0599:                    history.execute(operation, null, null);
0600:                } catch (ExecutionException e) {
0601:                    // TODO Auto-generated catch block
0602:                    e.printStackTrace();
0603:                }
0604:            }
0605:
0606:            private <V> void setProperty(ExtendableObject committedObject,
0607:                    ScalarPropertyAccessor<V> accessor, Object newValue) {
0608:                committedObject.setPropertyValue(accessor, accessor
0609:                        .getClassOfValueObject().cast(newValue));
0610:            }
0611:
0612:            /**
0613:             * Add a new uncommitted object to the committed datastore.
0614:             * 
0615:             * All objects in any list properties in the object are also added, hence
0616:             * this method is recursive.
0617:             * 
0618:             * @param newObject the uncommitted version of the new object
0619:             * @param parent the committed version of the parent into which this new
0620:             * 			object is to be inserted
0621:             * @param listAccessor the list into which this new object is to be
0622:             * 			inserted
0623:             * @return the committed version of the object
0624:             */
0625:            private <E extends ExtendableObject> E commitNewObject(
0626:                    final E newObject, ExtendableObject parent,
0627:                    ListPropertyAccessor<E> listAccessor,
0628:                    boolean isDescendentInsert) {
0629:                ExtendablePropertySet<? extends E> actualPropertySet = listAccessor
0630:                        .getElementPropertySet().getActualPropertySet(
0631:                                (Class<? extends E>) newObject.getClass());
0632:
0633:                /**
0634:                 * Holds references to new objects that have never been committed, so
0635:                 * that such references can be set later after all the new objects have
0636:                 * been committed.
0637:                 */
0638:                final ModifiedObject deferredReferences = new ModifiedObject();
0639:
0640:                IValues values = new IValues() {
0641:
0642:                    public Collection<ExtensionPropertySet<?>> getNonDefaultExtensions() {
0643:                        return newObject.getExtensions();
0644:                    }
0645:
0646:                    public IObjectKey getReferencedObjectKey(
0647:                            ReferencePropertyAccessor<? extends ExtendableObject> propertyAccessor) {
0648:                        ExtendableObject referencedObject = newObject
0649:                                .getPropertyValue(propertyAccessor);
0650:                        if (referencedObject == null) {
0651:                            return null;
0652:                        }
0653:                        UncommittedObjectKey uncommittedKey = (UncommittedObjectKey) referencedObject
0654:                                .getObjectKey();
0655:                        IObjectKey committedKey = uncommittedKey
0656:                                .getCommittedObjectKey();
0657:                        if (committedKey != null) {
0658:                            return committedKey;
0659:                        } else {
0660:                            /*
0661:                             * The property value is a reference to an object that has
0662:                             * not have yet been committed to the datastore. Such values
0663:                             * cannot be set in the datastore. We therefore set the
0664:                             * property to null for the time being but add it to our
0665:                             * list of properties to be set later.
0666:                             */
0667:                            deferredReferences.put(propertyAccessor,
0668:                                    uncommittedKey);
0669:                            return null;
0670:                        }
0671:                    }
0672:
0673:                    public <V> V getScalarValue(
0674:                            ScalarPropertyAccessor<V> propertyAccessor) {
0675:                        return newObject.getPropertyValue(propertyAccessor);
0676:                    }
0677:
0678:                    public <E2 extends ExtendableObject> IListManager<E2> getListManager(
0679:                            IObjectKey listOwnerKey,
0680:                            ListPropertyAccessor<E2> listAccessor) {
0681:                        /*
0682:                         * Create an empty list manager. The implementation depends on
0683:                         * the data manager, thus we request a list manager from the
0684:                         * object key.
0685:                         */
0686:                        return listOwnerKey.constructListManager(listAccessor);
0687:                    }
0688:                };
0689:
0690:                // Create the object with the appropriate property values
0691:                ObjectCollection<? super  E> propertyValues = parent
0692:                        .getListPropertyValue(listAccessor);
0693:                final E newCommittedObject = propertyValues.createNewElement(
0694:                        actualPropertySet, values, isDescendentInsert);
0695:
0696:                // Update the uncommitted object key to indicate that there is now a committed
0697:                // version of the object in the datastore
0698:                ((UncommittedObjectKey) newObject.getObjectKey())
0699:                        .setCommittedObject(newCommittedObject);
0700:
0701:                /*
0702:                 * Fire the event to indicate that a new object has been created. Note
0703:                 * that the objectCreated event, as opposed to the objectInserted event,
0704:                 * is fired as soon as any object is created and before it's children
0705:                 * are created. A later call to objectCreated will be made for each
0706:                 * descendant.
0707:                 */
0708:                parent.getDataManager().fireEvent(new ISessionChangeFirer() {
0709:                    public void fire(SessionChangeListener listener) {
0710:                        listener.objectCreated(newCommittedObject);
0711:                    }
0712:                });
0713:
0714:                // Commit all the child objects in the list properties.
0715:                for (ListPropertyAccessor<?> subListAccessor : actualPropertySet
0716:                        .getListProperties3()) {
0717:                    commitChildren(newObject, newCommittedObject,
0718:                            subListAccessor);
0719:                }
0720:
0721:                // If there are property changes that must be applied later, add the property
0722:                // change map to the list
0723:                if (!deferredReferences.isEmpty()) {
0724:                    modifiedObjects.put(newCommittedObject.getObjectKey(),
0725:                            deferredReferences);
0726:                }
0727:
0728:                return newCommittedObject;
0729:            }
0730:
0731:            private <E extends ExtendableObject> void commitChildren(
0732:                    ExtendableObject newObject,
0733:                    ExtendableObject newCommittedObject,
0734:                    ListPropertyAccessor<E> subListAccessor) {
0735:                for (E childObject : newObject
0736:                        .getListPropertyValue(subListAccessor)) {
0737:                    commitNewObject(childObject, newCommittedObject,
0738:                            subListAccessor, true);
0739:                }
0740:            }
0741:
0742:            private <E extends ExtendableObject> void commitObjectsInList(
0743:                    DeltaListManager<E> modifiedList) {
0744:                ExtendableObject parent = modifiedList.committedParent;
0745:
0746:                for (ExtendableObject newUntypedObject : modifiedList
0747:                        .getAddedObjects()) {
0748:                    E newObject = modifiedList.listAccessor
0749:                            .getElementPropertySet().getImplementationClass()
0750:                            .cast(newUntypedObject);
0751:
0752:                    final ExtendableObject newCommittedObject = commitNewObject(
0753:                            newObject, parent, modifiedList.listAccessor, false);
0754:
0755:                    /*
0756:                     * Now we can fire the notifications of newly inserted objects. This
0757:                     * is done now after all the descendants of the object have been
0758:                     * created.
0759:                     * 
0760:                     * Note that if this object references other objects that are added
0761:                     * in this same transaction but that have not yet been added then
0762:                     * the reference will be null. A later objectChanged event will be
0763:                     * fired when the reference is later set to the correct value.
0764:                     */
0765:                    parent.getDataManager().fireEvent(
0766:                            new ISessionChangeFirer() {
0767:                                public void fire(SessionChangeListener listener) {
0768:                                    listener.objectInserted(newCommittedObject);
0769:                                }
0770:                            });
0771:                }
0772:            }
0773:
0774:            class DeletedObject {
0775:                private ExtendableObject parent;
0776:                private ListPropertyAccessor<?> owningListProperty;
0777:
0778:                DeletedObject(ExtendableObject parent,
0779:                        ListPropertyAccessor owningListProperty) {
0780:                    this .parent = parent;
0781:                    this .owningListProperty = owningListProperty;
0782:                }
0783:
0784:                void deleteObject(ExtendableObject object) {
0785:                    parent.getListPropertyValue(owningListProperty).remove(
0786:                            object);
0787:                }
0788:            }
0789:
0790:            public Object getAdapter(Class adapter) {
0791:                // It is possible to implement query interfaces that execute
0792:                // an optimized query against the committed datastore and then
0793:                // adjusts the results with the uncommitted changes.
0794:                // However, none are currently implemented.
0795:                return null;
0796:            }
0797:
0798:            private class ModifiedAccountEntriesList extends
0799:                    AbstractCollection<Entry> {
0800:
0801:                Account account;
0802:
0803:                ModifiedAccountEntriesList(Account account) {
0804:                    this .account = account;
0805:                }
0806:
0807:                @Override
0808:                public int size() {
0809:                    Vector<Entry> addedEntries = new Vector<Entry>();
0810:                    Vector<IObjectKey> removedEntries = new Vector<IObjectKey>();
0811:                    buildAddedAndRemovedEntryLists(addedEntries, removedEntries);
0812:
0813:                    IObjectKey committedAccountKey = ((UncommittedObjectKey) account
0814:                            .getObjectKey()).getCommittedObjectKey();
0815:                    if (committedAccountKey == null) {
0816:                        // This is a new account created in this transaction
0817:                        Assert.isTrue(removedEntries.isEmpty());
0818:                        return addedEntries.size();
0819:                    } else {
0820:                        Account committedAccount = (Account) committedAccountKey
0821:                                .getObject();
0822:                        Collection<Entry> committedCollection = committedAccount
0823:                                .getEntries();
0824:                        return committedCollection.size() + addedEntries.size()
0825:                                - removedEntries.size();
0826:                    }
0827:                }
0828:
0829:                @Override
0830:                public Iterator<Entry> iterator() {
0831:                    // Build the list of differences between the committed
0832:                    // list and the list in this transaction.
0833:
0834:                    // This is done each time an iterator is requested.
0835:
0836:                    Vector<Entry> addedEntries = new Vector<Entry>();
0837:                    Vector<IObjectKey> removedEntries = new Vector<IObjectKey>();
0838:                    buildAddedAndRemovedEntryLists(addedEntries, removedEntries);
0839:
0840:                    IObjectKey committedAccountKey = ((UncommittedObjectKey) account
0841:                            .getObjectKey()).getCommittedObjectKey();
0842:                    if (committedAccountKey == null) {
0843:                        // This is a new account created in this transaction
0844:                        Assert.isTrue(removedEntries.isEmpty());
0845:                        return addedEntries.iterator();
0846:                    } else {
0847:                        Account committedAccount = (Account) committedAccountKey
0848:                                .getObject();
0849:                        Collection<Entry> committedCollection = committedAccount
0850:                                .getEntries();
0851:                        return new DeltaListIterator<Entry>(
0852:                                TransactionManager.this , committedCollection
0853:                                        .iterator(), addedEntries,
0854:                                removedEntries);
0855:                    }
0856:                }
0857:
0858:                private void buildAddedAndRemovedEntryLists(
0859:                        Vector<Entry> addedEntries,
0860:                        Vector<IObjectKey> removedEntries) {
0861:                    // Process all the new objects added within this transaction
0862:                    for (DeltaListManager<?> modifiedList : modifiedLists) {
0863:
0864:                        // Find all entries added to existing transactions
0865:                        if (modifiedList.listAccessor == TransactionInfo
0866:                                .getEntriesAccessor()) {
0867:                            for (ExtendableObject newObject : modifiedList
0868:                                    .getAddedObjects()) {
0869:                                Entry newEntry = (Entry) newObject;
0870:                                if (account.equals(newEntry.getAccount())) {
0871:                                    addedEntries.add(newEntry);
0872:                                }
0873:                            }
0874:                        }
0875:
0876:                        // Find all entries in new transactions.
0877:                        if (modifiedList.listAccessor == SessionInfo
0878:                                .getTransactionsAccessor()) {
0879:                            for (ExtendableObject newObject : modifiedList
0880:                                    .getAddedObjects()) {
0881:                                Transaction newTransaction = (Transaction) newObject;
0882:                                for (Entry newEntry : newTransaction
0883:                                        .getEntryCollection()) {
0884:                                    if (account.equals(newEntry.getAccount())) {
0885:                                        addedEntries.add(newEntry);
0886:                                    }
0887:                                }
0888:                            }
0889:                        }
0890:                    }
0891:
0892:                    /*
0893:                     * Process all the changed and deleted objects. (Deleted objects are
0894:                     * processed here and not from the deletedObjects list in modified
0895:                     * lists in the above code. This ensures that objects that are
0896:                     * deleted due to the deletion of the parent are also processed).
0897:                     */
0898:                    for (Map.Entry<IObjectKey, ModifiedObject> mapEntry : modifiedObjects
0899:                            .entrySet()) {
0900:                        IObjectKey committedKey = mapEntry.getKey();
0901:                        ModifiedObject newValues = mapEntry.getValue();
0902:
0903:                        ExtendableObject committedObject = committedKey
0904:                                .getObject();
0905:
0906:                        if (committedObject instanceof  Entry) {
0907:                            Entry entry = (Entry) committedObject;
0908:                            if (!newValues.isDeleted()) {
0909:                                Map<ScalarPropertyAccessor, Object> propertyMap = newValues
0910:                                        .getMap();
0911:
0912:                                // Object has changed property values.
0913:                                if (propertyMap.containsKey(EntryInfo
0914:                                        .getAccountAccessor())) {
0915:                                    boolean wasInIndex = account.equals(entry
0916:                                            .getAccount());
0917:                                    boolean nowInIndex = account
0918:                                            .equals(((IObjectKey) propertyMap
0919:                                                    .get(EntryInfo
0920:                                                            .getAccountAccessor()))
0921:                                                    .getObject());
0922:                                    if (wasInIndex) {
0923:                                        if (!nowInIndex) {
0924:                                            removedEntries.add(entry
0925:                                                    .getObjectKey());
0926:                                        }
0927:                                    } else {
0928:                                        if (nowInIndex) {
0929:                                            // Note that addedEntries must contain objects that
0930:                                            // are being managed by the transaction manager
0931:                                            // (not the committed versions).
0932:                                            addedEntries
0933:                                                    .add(getCopyInTransaction(entry));
0934:                                        }
0935:                                    }
0936:                                }
0937:                            } else {
0938:                                // Object has been deleted.
0939:                                if (entry.getAccount().equals(account)) {
0940:                                    removedEntries.add(entry.getObjectKey());
0941:                                }
0942:                            }
0943:                        }
0944:                    }
0945:                }
0946:            }
0947:
0948:            /**
0949:             * This method is called when a nested transaction manager is about to apply its
0950:             * changes to our data.
0951:             */
0952:            @Override
0953:            public void startTransaction() {
0954:                /*
0955:                 * This method is called only when transactions are nested. The nested
0956:                 * transaction will call this method before it applies the changes to
0957:                 * this transaction.
0958:                 */
0959:                insideTransaction = true;
0960:            }
0961:
0962:            /**
0963:             * This method is called when a nested transaction manager has completed
0964:             * making changes to our data.
0965:             */
0966:            @Override
0967:            public void commitTransaction() {
0968:                /*
0969:                 * This method is called only when transaction are nested.
0970:                 * The nested transaction will call this method after it has
0971:                 * applied the changes to this transaction.
0972:                 * 
0973:                 * Changes are applied to this object's data as the changes are
0974:                 * made and there is nothing we need do to 'commit' the changes.
0975:                 * However, we do need to fire the performRefresh event method
0976:                 * at this time.  This event notifies our listeners that a batch
0977:                 * of changes has completed and now is a good time to refresh
0978:                 * views.
0979:                 */
0980:                fireEvent(new ISessionChangeFirer() {
0981:                    public void fire(SessionChangeListener listener) {
0982:                        listener.performRefresh();
0983:                    }
0984:                });
0985:
0986:                insideTransaction = false;
0987:            }
0988:
0989:            /**
0990:             * This class contains the methods that merge changes
0991:             * from the base data into the data for this object,
0992:             * and also tell our listeners of such changes.
0993:             */
0994:            private class MySessionChangeListener implements 
0995:                    SessionChangeListener {
0996:
0997:                public void objectInserted(ExtendableObject newObject) {
0998:                    /*
0999:                     * The object may contain references to objects
1000:                     * that have been deleted in this view.  
1001:                     * 
1002:                     * In such a situation, the object deleted in this view
1003:                     * could be first 'undeleted'.  If the undeleted object references
1004:                     * other objects that were deleted by this view then those
1005:                     * objects are in turn undeleted in a recursive manner.
1006:                     * 
1007:                     * A simpler approach may be to ignore the new object until
1008:                     * the transaction is committed.  At that time, the commit fails
1009:                     * with a conflict exception.  
1010:                     */
1011:                    // TODO Implement this method
1012:                }
1013:
1014:                public void objectCreated(ExtendableObject newObject) {
1015:                    // TODO Auto-generated method stub
1016:
1017:                }
1018:
1019:                public void objectChanged(ExtendableObject changedObject,
1020:                        ScalarPropertyAccessor changedProperty,
1021:                        Object oldValue, Object newValue) {
1022:                    /*
1023:                     * The property may have been changed to reference an
1024:                     * object that has been deleted in this view.
1025:                     * 
1026:                     * In such a situation, the object could be 
1027:                     * first 'undeleted' in this view.  If the undeleted object references
1028:                     * other objects that were deleted by this view then those
1029:                     * objects are in turn undeleted in a recursive manner.
1030:                     * 
1031:                     * A simpler approach may be to ignore the new object until
1032:                     * the transaction is committed.  At that time, the commit fails
1033:                     * with a conflict exception.
1034:                     * 
1035:                     * We also need to consider what happens if this property change made
1036:                     * another property inapplicable, and that other property was changed
1037:                     * in this view, or if this property has been made inapplicable as
1038:                     * a result of a change in this view of another property.
1039:                     * All sorts of possibilities to consider. 
1040:                     */
1041:                    // TODO Implement this method
1042:                }
1043:
1044:                public void objectRemoved(ExtendableObject deletedObject) {
1045:                    /*
1046:                     * If an object is deleted from the base data then we
1047:                     * know there are no references to the object from the
1048:                     * base data.  However, it is possible that a reference
1049:                     * was created to this object in this version of the data.
1050:                     * 
1051:                     * If that is the case then we could just not remove the object
1052:                     * from this view of the data.  The object is removed from
1053:                     * this view of the data only if all references to it are
1054:                     * removed from this view.  If references still remain when
1055:                     * this view is committed then a conflict error occurs.
1056:                     * The user should be told that the object no longer exists
1057:                     * and the user must remove references to it before attempting
1058:                     * again to commit.
1059:                     * 
1060:                     * A simpler approach may be to ignore the deletion of the object until
1061:                     * the transaction is committed.  At that time, the commit fails
1062:                     * with a conflict exception.  
1063:                     */
1064:                    // TODO Implement this method
1065:                }
1066:
1067:                public void objectDestroyed(ExtendableObject deletedObject) {
1068:                    // TODO Auto-generated method stub
1069:
1070:                }
1071:
1072:                public void performRefresh() {
1073:                    // TODO Auto-generated method stub
1074:
1075:                }
1076:
1077:                public void sessionReplaced(Session oldSession,
1078:                        Session newSession) {
1079:                    // TODO This method is not applicable here.
1080:                    // Do we need a version of this listener that does
1081:                    // not have this method???
1082:                }
1083:
1084:                public void objectMoved(ExtendableObject movedObject,
1085:                        ExtendableObject originalParent,
1086:                        ExtendableObject newParent,
1087:                        ListPropertyAccessor originalParentListProperty,
1088:                        ListPropertyAccessor newParentListProperty) {
1089:                    /*
1090:                     * If an object was moved and the move conflicts with outstanding changes
1091:                     * held by this transaction then we have problems.
1092:                     * However, with the current JMoney feature set, this is unlikely
1093:                     * to happen, so just ignore.  Views working off transactions will not
1094:                     * see the move.  When the transaction commits, the changes should
1095:                     * not conflict with the move.
1096:                     */
1097:                }
1098:            }
1099:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.