View Javadoc

1   /*
2    * Copyright 2004 Charles Blaxland
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package mnemosyne.core;
17  
18  import mnemosyne.guid.Guid;
19  import mnemosyne.lock.Lock;
20  import mnemosyne.util.CloneException;
21  import mnemosyne.util.PersistenceRuntimeException;
22  import mnemosyne.util.Util;
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  
26  import java.io.*;
27  import java.util.Set;
28  
29  /***
30   * // TODO - Review synchronization
31   * @version $Id: PersistentObjectImpl.java,v 1.1.1.1 2004/08/07 06:41:11 charlesblaxland Exp $
32   */
33  public class PersistentObjectImpl implements PersistentObject, Serializable
34  {
35      private static final Log log = LogFactory.getLog(PersistentObjectImpl.class);
36  
37      private Guid guid;
38      private transient VersionManager versionMgr;
39      private transient Enhancer enhancer;
40      private transient VersionCollection committedVersions = new ListVersionCollection();
41      private transient Object writeVersion;
42      private transient Object deserializedVersion;
43      private transient boolean deserializedVersionIsAnUnresolvedReference = false;
44      private transient Lock lock;
45  
46      // TODO - remove when we can detect mutating methods
47      private transient boolean isMutableMethodsWorkaroundEnabled = true;
48  
49      public PersistentObjectImpl(Guid guid, VersionManager versionMgr, Enhancer enhancer, Lock lock)
50      {
51          this.guid = guid;
52          this.versionMgr = versionMgr;
53          this.enhancer = enhancer;
54          this.lock = lock;
55      }
56  
57      public void initialize(Object initialObject)
58      {
59          PersistentContext context = PersistentContext.get(versionMgr);
60          Version currentVersion = context.getVersion();
61          committedVersions.addVersion(currentVersion, initialObject);
62          deserializedVersion = null;
63      }
64  
65      public void initializeAsNewObject(Object initialObject)
66      {
67          PersistentContext context = PersistentContext.get(versionMgr);
68          Transaction transaction = context.getTransaction();
69          lock.acquireWriteLock(transaction);
70          writeVersion = initialObject;
71          deserializedVersion = null;
72      }
73  
74      public Guid getGuid()
75      {
76          return guid;
77      }
78  
79      public Object findTargetObject()
80      {
81          return findTargetObject(false);
82      }
83  
84      public Object findWritableTargetObject()
85      {
86          return findTargetObject(true);
87      }
88  
89      protected synchronized Object findTargetObject(boolean isForMutatingCall)
90      {
91          if (writeVersion == null && committedVersions.size() == 0 && deserializedVersion != null)
92          {
93              return deserializedVersion;
94          }
95  
96          Object target = null;
97          PersistentContext context = PersistentContext.get(versionMgr);
98          Version currentVersion = context.getVersion();
99  
100         // TODO - these lines to be removed when we can detect mutating methods
101         if (isMutableMethodsWorkaroundEnabled)
102             isForMutatingCall = context.isInTransaction();
103 
104         if (context.isInTransaction())
105         {
106             log.trace("Thread is in a transaction");
107             Transaction transaction = context.getTransaction();
108             if (isForMutatingCall)
109             {
110                 log.trace("Method is mutable, obtaining write lock");
111                 lock.acquireWriteLock(transaction);
112 
113                 // TODO - there is an issue here if another transaction modifies and commits this object
114                 // after this transaction has started, but before it attempts to write to it.
115                 // In this situation, committedVersions.retrieveVersion(currentVersion) will no
116                 // longer retrieve the latest version. Transaction should fail in this case.
117 
118                 if (writeVersion == null)
119                 {
120                     log.trace("Cloning object " + guid);
121                     Object objForCurrentVersion = committedVersions.retrieveVersion(currentVersion);
122                     writeVersion = clonePersistentObjectVersion(objForCurrentVersion);
123                     transaction.addModifiedObject(this);
124                 }
125 
126                 target = writeVersion;
127             }
128             else
129             {
130                 log.trace("Method is not mutable, acquiring read lock");
131                 lock.acquireReadLock(transaction);
132                 if (writeVersion != null)
133                 {
134                     target = writeVersion;
135                 }
136                 else
137                 {
138                     target = committedVersions.retrieveVersion(currentVersion);
139                 }
140             }
141         }
142         else
143         {
144             if (!isForMutatingCall)
145             {
146                 target = committedVersions.retrieveVersion(currentVersion);
147             }
148             else
149             {
150                 throw new NoTransactionException("Attempt to modify persistent object outside transaction");
151             }
152         }
153 
154         return target;
155     }
156 
157     public Set prepareForCommit()
158     {
159         return enhancer.enhance(writeVersion);
160     }
161 
162     public synchronized void commit(Version versionToCommitAs)
163     {
164         committedVersions.addVersion(versionToCommitAs, writeVersion);
165         writeVersion = null;
166         releaseLocks();
167     }
168 
169     public synchronized void rollback()
170     {
171         if (committedVersions.size() == 0 && writeVersion != null)
172         {
173             // There are as yet no committed versions, so presumably this is a "new" persistent object.
174             // In this situation we also reset the PersistentObject of the new object to null,
175             // otherwise calls on it would be treated incorrectly as calls on a persistent object.
176             Persistable persistable = (Persistable) writeVersion;
177             persistable.setPersistentObject(null);
178         }
179         writeVersion = null;
180         releaseLocks();
181     }
182 
183     private void releaseLocks()
184     {
185         PersistentContext context = PersistentContext.get(versionMgr);
186         Transaction transaction = context.getTransaction();
187         lock.releaseLock(transaction);
188     }
189 
190     private void writeObject(ObjectOutputStream s) throws IOException
191     {
192         s.defaultWriteObject();
193         PersistentContext context = PersistentContext.get(versionMgr);
194         Transaction transaction = context.getTransaction();
195         if (context.isInTransaction() && !transaction.hasModifiedOrAddedObject(this))
196         {
197             s.writeObject(findTargetObject().getClass());
198         }
199         else
200         {
201             Object clonedTarget = clonePersistentObjectVersion(findTargetObject());
202             ((Persistable) clonedTarget).setArchiveVersion(true);
203             s.writeObject(clonedTarget);
204         }
205     }
206 
207     private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException
208     {
209         s.defaultReadObject();
210         Object obj = s.readObject();
211         if (obj instanceof Class)
212         {
213             Class clazz = (Class) obj;
214             try
215             {
216                 // TODO - apparently there's a way to create new instances even if they don't have a default constructor
217                 // using JDK 1.4 undocumented features.
218                 obj = clazz.newInstance();
219                 deserializedVersionIsAnUnresolvedReference = true;
220             }
221             catch (InstantiationException e)
222             {
223                 throw new PersistenceRuntimeException("Failed to create new instance of class " + clazz.getName() + ".  Check that it has a public default constructor.");
224             }
225             catch (IllegalAccessException e)
226             {
227                 throw new PersistenceRuntimeException("Failed to create new instance of class " + clazz.getName() + ".  Check that it has a public default constructor.");
228             }
229         }
230 
231         deserializedVersion = obj;
232     }
233 
234     protected Object readResolve()
235     {
236         PersistentObjectFactory factory = ArchiveContext.get().getPersistentObjectFactory();
237         PersistentObject result = factory.createPersistentObject(guid);
238         result.initialize(deserializedVersion);
239         Persistable persistable = (Persistable) deserializedVersion;
240         persistable.setPersistentObject(result);
241 
242         AggregatedTransaction aggregatedTransaction = ArchiveContext.get().getAggregatedTransaction();
243 
244         if (deserializedVersionIsAnUnresolvedReference)
245         {
246             result = aggregatedTransaction.addUnresolvedReference(result);
247         }
248         else
249         {
250             if (aggregatedTransaction.isComplete())
251             {
252                 result = aggregatedTransaction.resolveObject(result);
253             }
254             else
255             {
256                 result = aggregatedTransaction.addModifiedObject(result);
257             }
258         }
259 
260         return result;
261     }
262 
263     private Object clonePersistentObjectVersion(Object obj)
264     {
265         Object clonedObj = null;
266         try
267         {
268             clonedObj = Util.callCloneViaReflection(obj);
269         }
270         catch (CloneException e)
271         {
272             throw new NotPersistableException("Object not persistable, as the clone method in this object raised an exception", e);
273         }
274 
275         return clonedObj;
276     }
277 
278     public int hashCode()
279     {
280         return guid.hashCode();
281     }
282 
283     public boolean equals(Object obj)
284     {
285         if (obj instanceof PersistentObjectImpl)
286         {
287             PersistentObjectImpl other = (PersistentObjectImpl) obj;
288             return guid.equals(other.guid);
289         }
290         else
291         {
292             return false;
293         }
294     }
295 
296     public String toString()
297     {
298         return guid.toString();
299     }
300 
301     public void setMutableMethodsWorkaroundEnabled(boolean mutableMethodsWorkaroundEnabled)
302     {
303         isMutableMethodsWorkaroundEnabled = mutableMethodsWorkaroundEnabled;
304     }
305 }