View Javadoc

1   /* (c) Copyright 2003 Caleigo AB, All rights reserved. 
2    * 
3    * This library is free software; you can redistribute it and/or
4    * modify it under the terms of the GNU Lesser General Public
5    * License as published by the Free Software Foundation; either
6    * version 2.1 of the License, or (at your option) any later version.
7    * 
8    * This library is distributed in the hope that it will be useful,
9    * but WITHOUT ANY WARRANTY; without even the implied warranty of
10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11   * Lesser General Public License for more details.
12   * 
13   * You should have received a copy of the GNU Lesser General Public
14   * License along with this library; if not, write to the Free Software
15   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16   *  
17   */
18  
19  package org.caleigo.core.service;
20  
21  
22  import java.io.*;
23  import java.net.URL;
24  import java.util.*;
25  import java.util.zip.*;
26  
27  import org.caleigo.core.*;
28  import org.caleigo.core.exception.*;
29  import org.caleigo.toolkit.log.*;
30  import org.caleigo.toolkit.util.*;
31  
32  
33  /*** MemoryDataService provides a simple implementation of the IDataService
34   * interface that can be used to store entities in a non-persistent memory 
35   * cache. By overiding the loadEntitySelection and storeEntitySelection
36   * entity pesistance can be added in a simple way.
37   *
38   * Note that the class is not suitable to store sensitive data. The class
39   * does not verify foreign keys and should only be used for temporary
40   * storage and/or for test or demonstration purposes. 
41   * 
42   * Note also that transaction handlig is currently not supported.
43   * 
44   * @author  Dennis Zikovic
45   * @version 1.00
46   * 
47   */ /* 
48   *
49   * WHEN        WHO               WHY & WHAT
50   * ------------------------------------------------------------------------------
51   * 2003-01-02  Dennis Zikovic    Creation
52   */
53  public class MemoryDataService extends AbstractDataService
54  {
55      // Data members ------------------------------------------------------------
56      protected final Map mTableSelectionMap;
57  
58      private boolean mIsSyncroized = true;
59      private boolean mIsAutGenerateEnabled = true;
60  
61      // Static methods ----------------------------------------------------------
62  
63      /*** Help method that makes a complete copy of the source data to the 
64       * targeted MemoryDataService. The method returns true if the copy was
65       * succesfull.
66       */
67      public static boolean copyDataSource(IDataService source, MemoryDataService target)
68      {
69          if (source == null || target == null || source.getDataSourceDescriptor() != target.getDataSourceDescriptor())
70              return false;
71          boolean success = true;
72  
73          // Un-syncronize target for perfomance reasons.
74          boolean wasSyncronized = target.isSyncronized();
75          target.setSyncronized(false);
76  
77          // Dissable autogeneration of identity fields.
78          boolean wasAutGenerateEnabled = target.isAutGenerateEnabled();
79          target.setAutGenerateEnabled(false);
80  
81          try
82          {
83              // Skip cache if any.
84              if (source instanceof CachedDataService)
85                  source = ((CachedDataService) source).getSourceDataService();
86  
87              // Copy all tables.
88              ISelection entitySelection = null;
89              for (int entityTypeIndex = 0; entityTypeIndex < source.getDataSourceDescriptor().getEntityDescriptorCount(); entityTypeIndex++)
90              {
91                  // Copy each entity in table.
92                  entitySelection = source.loadSelection(source.getDataSourceDescriptor().getEntityDescriptor(entityTypeIndex), null);
93                  for (int entityIndex = 0; entityIndex < entitySelection.size(); entityIndex++)
94                  {
95                      entitySelection.getEntity(entityIndex).clearStatusFlag(IEntity.PERSISTENT);
96                      target.store(entitySelection.getEntity(entityIndex));
97                  }
98  
99                  // Syncronize target for each table.
100                 target.syncronize();
101             }
102         }
103         catch (Exception e)
104         {
105             Log.printError(null, "Failed to copy data source!", e);
106             success = false;
107         }
108 
109         // Re-syncronize target.
110         target.setSyncronized(wasSyncronized);
111 
112         // Re-enable identity generation.
113         target.setAutGenerateEnabled(wasAutGenerateEnabled);
114 
115         return success;
116     }
117 
118     // Constructors ------------------------------------------------------------
119     public MemoryDataService(IDataSourceDescriptor dataSourceDescriptor)
120     {
121         this(dataSourceDescriptor, dataSourceDescriptor.getSourceName());
122     }
123 
124     public MemoryDataService(IDataSourceDescriptor dataSourceDescriptor, Object serviceIdentity)
125     {
126         super(dataSourceDescriptor.getCodeName(), serviceIdentity, dataSourceDescriptor);
127         mTableSelectionMap = new HashMap();
128     }
129 
130     protected void finalize()
131     {
132         if (!isSyncronized())
133             this.syncronize();
134     }
135 
136     // IDataService implementation ---------------------------------------------
137 
138     /*** Returns a new IDataTransaction object that can be used to batch 
139      * data operations and wrap them in a transaction.
140      */
141     public IDataTransaction newTransaction()
142     {
143         return new DataTransaction();
144     }
145 
146     public boolean ping()
147     {
148         return true;
149     }
150 
151     // Action methods ----------------------------------------------------------
152 
153     /*** Can be called to syncronize changes against any persistent storage
154      * provided by a subclass. This mehod does only have to be called if
155      * MemoroDataService is not in a suncronized mode. Calling the method
156      * will have no effect if no persistent storage is provided by a subclass.
157      */
158     public synchronized boolean syncronize()
159     {
160         try
161         {
162             Iterator it = mTableSelectionMap.values().iterator();
163             ISelectionHolder holder;
164 
165             while (it.hasNext())
166             {
167                 holder = (ISelectionHolder) it.next();
168                 if (holder.isDirty())
169                 {
170                     this.storeTableSelection(holder.getSelection());
171                     holder.setDirty(false);
172                 }
173             }
174         }
175         catch (Exception e)
176         {
177             Log.printError(null, "Failed to sycronize MemoryDataService!", e);
178             return false;
179         }
180 
181         return true;
182     }
183 
184     /*** Creates a complete backup of the called MemoryDataService to the
185      * provided file. Returns true is the backup was successfull.
186      */
187     public boolean backupTo(File backupFile)
188     {
189         return this.backupTo(backupFile, null);
190     }
191 
192     /*** Creates a backup of the tables defined by the provided descriptor array
193      * in the called MemoryDataService to the provided backup file. 
194      * Returns true is the backup was successfull.
195      */
196     public boolean backupTo(File backupFile, IEntityDescriptor[] descriptorArray)
197     {
198         try
199         {
200             // Validate backup file.
201             if (backupFile.exists() && backupFile.isDirectory())
202                 return false;
203 
204             Log.print(this, "Commencing MemoryDataService backup operation to  \"" + backupFile.getName() + "\"");
205             long startTime = System.currentTimeMillis();
206 
207             // Initialize streams and writers.
208             ZipOutputStream zipOutput = new ZipOutputStream(new FileOutputStream(backupFile));
209             EntityWriter entityOutput = new EntityWriter(new OutputStreamWriter(zipOutput, "UTF-8"));
210 
211             // Write each table selection as a zip entry.
212             Iterator it = null;
213             if (descriptorArray == null)
214                 it = this.getDataSourceDescriptor().getEntityDescriptors();
215             else
216                 it = Iterators.iterate(descriptorArray);
217             //            mTableSelectionMap.values().iterator();
218             while (it.hasNext())
219             {
220                 IEntityDescriptor entityDescriptor = (IEntityDescriptor) it.next();
221                 try
222                 {
223                     ISelection selection = this.getTableSelection(entityDescriptor);
224     
225                     zipOutput.putNextEntry(new ZipEntry(entityDescriptor.getSourceName() + ".txt"));
226                     entityOutput.writeMappedSelection(selection);
227                     entityOutput.flush(); // Critical since OutputStreamWriter is buffered.   
228     
229                     Log.print(this, "  Stored " + selection.size() + " " + entityDescriptor.getCodeName() + " entities.");
230                 }
231                 catch(Exception e)
232                 {
233                     Log.printWarning(this, "Failed to store " + entityDescriptor.getCodeName() + " entities!");
234                 }
235             }
236 
237             // Close ouput.
238             entityOutput.close();
239 
240             Log.print(this, "Backup completed successfully in " + (System.currentTimeMillis() - startTime) + " ms.");
241         }
242         catch (Exception e)
243         {
244             Log.printError(null, "Failed to backup MemoryDataService!", e);
245             return false;
246         }
247         return true;
248     }
249 
250     /*** Restores data from a backup file created by the backupTo method.
251      * Changed data will be restored and deleted entities will be re-added.
252      */
253     public boolean restoreFrom(File backupFile)
254     {
255         return this.restoreFrom(backupFile, null, true);
256     }
257 
258     /*** Restores data from a backup file created by the backupTo method.
259       *  
260       * @param backupFile The file containing backup data to restore from.
261       * @param descriptorArray This array specifies what entity type that should
262       *  be restored. If set to null all entities will be restored.
263       * @param addDeleted The addDeleted flag defines if entities that have been 
264       *  delted should be re-added or not. Normally this should be set to true.
265       */
266     public boolean restoreFrom(File backupFile, IEntityDescriptor[] descriptorArray, boolean addDeleted)
267     {
268         boolean wasSyncronized = this.isSyncronized();
269         this.setSyncronized(false);
270 
271         try
272         {
273             // Validate backup file.
274             if (backupFile.exists() && backupFile.isDirectory())
275                 return false;
276 
277             Log.print(this, "Commencing MemoryDataService restore operation from \"" + backupFile.getName() + "\"");
278             long startTime = System.currentTimeMillis();
279 
280             // Initialize streams and readers.
281             ZipFile zipFile = new ZipFile(backupFile);
282 
283             // Read each table entry.
284             Enumeration enum = zipFile.entries();
285             while (enum.hasMoreElements())
286             {
287                 // Prepare entry entity reader.
288                 ZipEntry entry = (ZipEntry) enum.nextElement();
289                 EntityReader entityReader = new EntityReader(new InputStreamReader(zipFile.getInputStream(entry), "UTF-8"));
290 
291                 // Determine type of next entry.
292                 String sourceName = entry.getName();
293                 if (sourceName.indexOf('.') > 0)
294                     sourceName = sourceName.substring(0, sourceName.indexOf('.'));
295                 IEntityDescriptor entityDescriptor = null;
296                 for (int j = 0; entityDescriptor == null && j < this.getDataSourceDescriptor().getEntityDescriptorCount(); j++)
297                     if (this.getDataSourceDescriptor().getEntityDescriptor(j).getSourceName().equals(sourceName))
298                         entityDescriptor = this.getDataSourceDescriptor().getEntityDescriptor(j);
299                 if (entityDescriptor == null)
300                 {
301                     Log.print(this, "Failed restore table data identified as: " + sourceName);
302                     continue;
303                 }
304 
305                 // Check if entity type should be restored.
306                 boolean found = descriptorArray == null;
307                 for (int j = 0; !found && j < descriptorArray.length; j++)
308                     found = descriptorArray[j] == entityDescriptor;
309                 if (!found && descriptorArray != null)
310                     continue;
311 
312                 // Read table selection.        
313                 ISelection backupSelection = entityReader.readMappedSelection(entityDescriptor);
314 
315                 // Close entity reader.
316                 entityReader.close();
317 
318                 // Merge read selection with current selection in service.
319                 ISelection currentSelection = this.getTableSelection(entityDescriptor);
320                 int added = 0;
321                 int updated = 0;
322                 if (currentSelection.isEmpty() && addDeleted)
323                 {
324                     // If current selection is empty no check for existance is
325                     // neaded which saves performance
326                     for (int j = 0; j < backupSelection.size(); j++)
327                     {
328                         currentSelection.addEntity(backupSelection.getEntity(j));
329                         added++;
330                     }
331                 }
332                 else
333                 {
334                     for (int j = 0; j < backupSelection.size(); j++)
335                     {
336                         // Check if entity exists in current selection.
337                         int index = currentSelection.indexOf(backupSelection.getEntity(j));
338                         if (index >= 0)
339                         {
340                             currentSelection.getEntity(index).copyData(backupSelection.getEntity(j));
341                             if (currentSelection.getEntity(index).isDirty())
342                             {
343                                 updated++;
344                                 currentSelection.getEntity(index).setStatusFlag(IEntity.PERSISTENT);
345                                 currentSelection.getEntity(index).clearStatusFlag(IEntity.DIRTY | IEntity.EMPTY);
346                             }
347                         }
348                         else if (addDeleted)
349                         {
350                             currentSelection.addEntity(backupSelection.getEntity(j));
351                             added++;
352                         }
353                     }
354                 }
355 
356                 // Allow subclasses to store table changes to persistant memory.
357                 this.storeTableSelection(currentSelection);
358 
359                 Log.print(this, "  Restored " + entityDescriptor.getCodeName() + " (Added " + added + ", Updated " + updated + ")");
360             }
361 
362             Log.print(this, "Restore completed successfully in " + (System.currentTimeMillis() - startTime) + " ms.");
363         }
364         catch (Exception e)
365         {
366             Log.printError(null, "Failed to restore MemoryDataService!", e);
367             return false;
368         }
369         return true;
370     }
371     
372     public boolean restoreFromURL(URL backupURL)
373     {
374         return this.restoreFromURL(backupURL, null, true);
375     }
376     
377     public boolean restoreFromURL(URL backupURL, IEntityDescriptor[] descriptorArray, boolean addDeleted)
378     {
379         boolean wasSyncronized = this.isSyncronized();
380         this.setSyncronized(false);
381 
382         try
383         {
384             Log.print(this, "Commencing MemoryDataService restore operation from \"" + backupURL.getPath() + "\"");
385             long startTime = System.currentTimeMillis();
386 
387             // Initialize streams and readers.
388             ZipInputStream zipInputStream = new ZipInputStream(backupURL.openStream());
389 
390             // Read each table entry.
391             ZipEntry entry = null;
392             while ((entry = zipInputStream.getNextEntry()) != null)
393             {
394                 // Prepare entry entity reader.
395                 EntityReader entityReader = new EntityReader(new InputStreamReader(zipInputStream, "UTF-8"));
396 
397                 // Determine type of next entry.
398                 String sourceName = entry.getName();
399                 if (sourceName.indexOf('.') > 0)
400                     sourceName = sourceName.substring(0, sourceName.indexOf('.'));
401                 IEntityDescriptor entityDescriptor = null;
402                 for (int j = 0; entityDescriptor == null && j < this.getDataSourceDescriptor().getEntityDescriptorCount(); j++)
403                     if (this.getDataSourceDescriptor().getEntityDescriptor(j).getSourceName().equals(sourceName))
404                         entityDescriptor = this.getDataSourceDescriptor().getEntityDescriptor(j);
405                 if (entityDescriptor == null)
406                 {
407                     Log.print(this, "Failed restore table data identified as: " + sourceName);
408                     continue;
409                 }
410 
411                 // Check if entity type should be restored.
412                 boolean found = descriptorArray == null;
413                 for (int j = 0; !found && j < descriptorArray.length; j++)
414                     found = descriptorArray[j] == entityDescriptor;
415                 if (!found && descriptorArray != null)
416                     continue;
417 
418                 // Read table selection.        
419                 ISelection backupSelection = entityReader.readMappedSelection(entityDescriptor);
420 
421                 // Merge read selection with current selection in service.
422                 ISelection currentSelection = this.getTableSelection(entityDescriptor);
423                 int added = 0;
424                 int updated = 0;
425                 if (currentSelection.isEmpty() && addDeleted)
426                 {
427                     // If current selection is empty no check for existance is
428                     // neaded which saves performance
429                     for (int j = 0; j < backupSelection.size(); j++)
430                     {
431                         currentSelection.addEntity(backupSelection.getEntity(j));
432                         added++;
433                     }
434                 }
435                 else
436                 {
437                     for (int j = 0; j < backupSelection.size(); j++)
438                     {
439                         // Check if entity exists in current selection.
440                         int index = currentSelection.indexOf(backupSelection.getEntity(j));
441                         if (index >= 0)
442                         {
443                             currentSelection.getEntity(index).copyData(backupSelection.getEntity(j));
444                             if (currentSelection.getEntity(index).isDirty())
445                             {
446                                 updated++;
447                                 currentSelection.getEntity(index).setStatusFlag(IEntity.PERSISTENT);
448                                 currentSelection.getEntity(index).clearStatusFlag(IEntity.DIRTY | IEntity.EMPTY);
449                             }
450                         }
451                         else if (addDeleted)
452                         {
453                             currentSelection.addEntity(backupSelection.getEntity(j));
454                             added++;
455                         }
456                     }
457                 }
458 
459                 // Allow subclasses to store table changes to persistant memory.
460                 this.storeTableSelection(currentSelection);
461 
462                 Log.print(this, "  Restored " + entityDescriptor.getCodeName() + " (Added " + added + ", Updated " + updated + ")");
463             }
464             
465             // Close zip input stream
466             zipInputStream.close();
467 
468             Log.print(this, "Restore completed successfully in " + (System.currentTimeMillis() - startTime) + " ms.");
469         }
470         catch (Exception e)
471         {
472             Log.printError(null, "Failed to restore MemoryDataService!", e);
473             return false;
474         }
475         return true;
476     }
477 
478     // Access methods ----------------------------------------------------------
479 
480     public boolean isSyncronized()
481     {
482         return mIsSyncroized;
483     }
484 
485     public synchronized void setSyncronized(boolean syncronize)
486     {
487         mIsSyncroized = syncronize;
488 
489         if (mIsSyncroized)
490             this.syncronize();
491     }
492 
493     /*** Access method that returns true if autogeneration of identity fields
494      * with the autogen field flags set is enabled.
495      */
496     public boolean isAutGenerateEnabled()
497     {
498         return mIsAutGenerateEnabled;
499     }
500 
501     /*** Mutation method that controls true if autogeneration of identity fields
502      * with the autogen field flags should be enabled. This should normally
503      * allways be set to true (default). It can be usable to turn off generation
504      * when copying data between sources in which case the identity data must 
505      * be provided.
506      */
507     public synchronized void setAutGenerateEnabled(boolean enabled)
508     {
509         mIsAutGenerateEnabled = enabled;
510     }
511 
512     // Help methods ------------------------------------------------------------
513     protected void executeLoad(IEntity entity, Qualifier qualifier) throws DataServiceException
514     {
515         // Perform security access check.
516         if (DataAccessManager.getManager().getAccessLevel(entity.getEntityDescriptor()) == DataAccessManager.NONE)
517             throw new SecurityException("No read access for " + entity.getEntityDescriptor() + " entities!");
518 
519         // Access adressed table selection.
520         ISelection dbSelection = this.getTableSelection(entity.getEntityDescriptor());
521 
522         // Search for qualified entity.
523         IEntity dbEntity = null;
524         for (int j = 0; j < dbSelection.size(); j++)
525             if (qualifier.doesQualify(dbSelection.getEntity(j)))
526                 dbEntity = dbSelection.getEntity(j);
527 
528         // Copy found entity if any and update flags.
529         if (dbEntity != null)
530         {
531             // Reset entity if data READ access is not granted.
532             if (!DataAccessManager.getManager().hasReadAccess(entity))
533                 entity.clear();
534             else
535             {
536                 entity.copyData(dbEntity);
537                 entity.setStatusFlag(IEntity.PERSISTENT);
538                 entity.clearStatusFlag(IEntity.DIRTY | IEntity.EMPTY);
539             }
540         }
541         else
542         {
543             entity.setStatusFlag(IEntity.EMPTY);
544             entity.clearStatusFlag(IEntity.DIRTY | IEntity.PERSISTENT);
545         }
546     }
547 
548     protected void executeQuery(DataQuery query, ISelection selection) throws DataServiceException
549     {
550         // Perform security access check.
551         if (DataAccessManager.getManager().getAccessLevel(query.getEntityDescriptor()) == DataAccessManager.NONE)
552             throw new SecurityException("No read access for " + query.getEntityDescriptor() + " entities!");
553 
554         // Access adressed table selection and qualify sub selection.
555         ISelection dbSelection = this.getTableSelection(query.getEntityDescriptor());
556         if (dbSelection != null && query.getQualifier() != null)
557             dbSelection = dbSelection.createSubSelection(query.getQualifier());
558 
559         // Copy all qualified entities.
560         IEntity entity;
561         for (int j = 0; dbSelection != null && j < dbSelection.size(); j++)
562         {
563             entity = query.getEntityDescriptor().createEntity(dbSelection.getEntity(j));
564             entity.setStatusFlag(IEntity.PERSISTENT);
565             entity.clearStatusFlag(IEntity.DIRTY | IEntity.EMPTY);
566             selection.addEntity(entity);
567         }
568     }
569 
570     protected void executeInsert(IEntity entity) throws DataServiceException
571     {
572         // Perform security access check.
573         this.checkEntityAsStorable(entity);
574 
575         // Access adressed table selection.
576         ISelection dbSelection = this.getTableSelection(entity.getEntityDescriptor());
577 
578         // Generate and set autogenerated identity fields.
579         IFieldDescriptor field = null;
580         for (int j = 0; j < entity.getEntityDescriptor().getFieldCount(); j++)
581         {
582             field = entity.getEntityDescriptor().getFieldDescriptor(j);
583 
584             // Generate primary keys if auto flag is set.
585             if (mIsAutGenerateEnabled && field.isAutoGenerated() && field.isIdentityField() && field.getDataType() == DataType.INTEGER)
586             {
587                 int max = 0;
588                 for (int k = 0; k < dbSelection.size(); k++)
589                     if (!dbSelection.getEntity(k).isDataNull(field))
590                         max = Math.max(max, ((Integer) dbSelection.getEntity(k).getData(field)).intValue());
591                 entity.setData(field, new Integer(max + 1));
592             }
593         }
594 
595         // Copy and store entity.     
596         IEntity dbEntity = entity.getEntityDescriptor().createEntity();
597         dbEntity.copyData(entity);
598         dbEntity.setStatusFlag(IEntity.PERSISTENT);
599         dbEntity.clearStatusFlag(IEntity.DIRTY | IEntity.EMPTY);
600         dbSelection.addEntity(entity);
601 
602         // Register table change for persistance syncronization. 
603         this.markTableAsChanged(entity.getEntityDescriptor());
604 
605         // Register status change on stored entity.
606         entity.setStatusFlag(IEntity.PERSISTENT);
607         entity.clearStatusFlag(IEntity.DIRTY | IEntity.EMPTY);
608     }
609 
610     protected void executeUpdate(IEntity entity, Qualifier qualifier) throws DataServiceException
611     {
612         // Perform security access check.
613         this.checkEntityAsStorable(entity);
614 
615         // Access adressed table selection.
616         ISelection dbSelection = this.getTableSelection(entity.getEntityDescriptor());
617 
618         // Find targeted entity.
619         IEntity dbEntity = null;
620         for (int j = 0; j < dbSelection.size(); j++)
621             if (qualifier.doesQualify(dbSelection.getEntity(j)))
622                 dbEntity = dbSelection.getEntity(j);
623 
624         if (dbEntity != null)
625         {
626             // Copy dirty field data to db entity. 
627             dbEntity.copyData(entity);
628             for (int j = 0; j < entity.getEntityDescriptor().getFieldCount(); j++)
629                 if (entity.isFieldDirty(entity.getEntityDescriptor().getFieldDescriptor(j)))
630                     dbEntity.setData(entity.getEntityDescriptor().getFieldDescriptor(j), entity.getData(entity.getEntityDescriptor().getFieldDescriptor(j)));
631             dbEntity.setStatusFlag(IEntity.PERSISTENT);
632             dbEntity.clearStatusFlag(IEntity.DIRTY | IEntity.EMPTY);
633 
634             // Register table change for persistance syncronization. 
635             this.markTableAsChanged(entity.getEntityDescriptor());
636 
637             // Register status change on stored entity.            
638             entity.setStatusFlag(IEntity.PERSISTENT);
639             entity.clearStatusFlag(IEntity.DIRTY | IEntity.EMPTY);
640         }
641     }
642 
643     protected void executeDelete(IEntity entity) throws DataServiceException
644     {
645         // Perform security access check.
646         this.checkEntityAsDeletable(entity);
647 
648         // Access adressed table selection.
649         ISelection dbSelection = this.getTableSelection(entity.getEntityDescriptor());
650         Qualifier qualifier = entity.getOriginQualifier();
651 
652         // Find targeted entity.
653         IEntity dbEntity = null;
654         for (int j = 0; j < dbSelection.size(); j++)
655             if (qualifier.doesQualify(dbSelection.getEntity(j)))
656                 dbEntity = dbSelection.getEntity(j);
657 
658         if (dbEntity != null)
659         {
660             dbSelection.removeEntity(dbEntity);
661 
662             // Register table change for persistance syncronization. 
663             this.markTableAsChanged(entity.getEntityDescriptor());
664 
665             // Register status change on deleted entity.            
666             entity.clearStatusFlag(IEntity.DIRTY | IEntity.EMPTY | IEntity.PERSISTENT);
667         }
668     }
669 
670     protected void markTableAsChanged(IEntityDescriptor entityDescriptor)
671     {
672         ISelectionHolder holder = (ISelectionHolder) mTableSelectionMap.get(entityDescriptor);
673         if (holder != null)
674             holder.setDirty(true);
675     }
676 
677     protected ISelection getTableSelection(IEntityDescriptor entityDescriptor)
678     {
679         ISelection dbSelection = null;
680 
681         ISelectionHolder holder = (ISelectionHolder) mTableSelectionMap.get(entityDescriptor);
682         if (holder == null)
683         {
684             dbSelection = this.loadTableSelection(entityDescriptor);
685             if (dbSelection == null)
686                 dbSelection = new Selection(entityDescriptor);
687 
688             holder = this.createSelectionHolder(dbSelection);
689             mTableSelectionMap.put(entityDescriptor, holder);
690         }
691         dbSelection = holder.getSelection();
692 
693         return dbSelection;
694     }
695 
696     /*** This method can be overriden to provide a simple persistent storage 
697      * for the MemoryDataService. The method is called prior to any access
698      * of addressed table.
699      */
700     protected ISelection loadTableSelection(IEntityDescriptor entityDescriptor)
701     {
702         return null;
703     }
704 
705     /*** This method can be overriden to provide a simple persistent storage 
706      * for the MemoryDataService. The method is called to store changes when 
707      * neaded.
708      */
709     protected void storeTableSelection(ISelection tableSelection)
710     {
711     }
712 
713     /*** Can be overriden to provide a smarter ISelectionHolder class.
714      */
715     protected ISelectionHolder createSelectionHolder(ISelection tableSelection)
716     {
717         return new DefaultSelectionHolder(tableSelection);
718     }
719 
720     // Nested classes ----------------------------------------------------------
721 
722     protected interface ISelectionHolder
723     {
724         public ISelection getSelection();
725 
726         public boolean isDirty();
727         public void setDirty(boolean dirty);
728     }
729 
730     protected class DefaultSelectionHolder implements ISelectionHolder
731     {
732         // Data members --------------------------------------------------------
733         private ISelection mSelection;
734         private boolean mIsDirty;
735 
736         // Constructors --------------------------------------------------------
737 
738         public DefaultSelectionHolder(ISelection selection)
739         {
740             mSelection = selection;
741             mIsDirty = false;
742         }
743 
744         // ISelectionHolder implementation -------------------------------------
745 
746         public ISelection getSelection()
747         {
748             return mSelection;
749         }
750 
751         public boolean isDirty()
752         {
753             return mIsDirty;
754         }
755 
756         public void setDirty(boolean dirty)
757         {
758             mIsDirty = dirty;
759         }
760     }
761 
762     protected class DataTransaction extends AbstractDataTransaction
763     {
764         // Constructors --------------------------------------------------------
765         public DataTransaction()
766         {
767             super(getTimeout());
768         }
769         
770         public void commit() throws DataServiceException
771         {
772             synchronized (MemoryDataService.this)
773             {
774                 // Get data operation enumerator.
775                 Enumeration dataOperations = this.getOperations();
776 
777                 // Execute all operations
778                 try
779                 {
780                     // Perform operations.               
781                     while (dataOperations.hasMoreElements())
782                     {
783                         DataOperation operation = (DataOperation) dataOperations.nextElement();
784                         switch (operation.getOperationType())
785                         {
786                             case DataOperation.LOAD :
787                                 executeLoad(operation.getEntity(), operation.getQualifier());
788                                 break;
789                             case DataOperation.QUERY :
790                                 executeQuery(operation.getDataQuery(), operation.getEntitySelection());
791                                 break;
792                             case DataOperation.STORE :
793                                 if (operation.getEntity().isPersistent())
794                                     executeUpdate(operation.getEntity(), operation.getQualifier());
795                                 else
796                                     executeInsert(operation.getEntity());
797                                 break;
798                             case DataOperation.DELETE :
799                                 executeDelete(operation.getEntity());
800                                 break;
801                             case DataOperation.REFRESH :
802                                 executeLoad(operation.getEntity(), operation.getQualifier());
803                                 break;
804                         }
805                     }
806 
807                     //                    // Update flags.
808                     //                    dataOperations = this.getOperations();  
809                     //                    while(dataOperations.hasMoreElements())
810                     //                    {       
811                     //                        DataOperation operation = (DataOperation)dataOperations.nextElement();
812                     //                        switch(operation.getOperationType())
813                     //                        {   
814                     //                            case DataOperation.DELETE:
815                     //                                operation.getEntity().clearStatusFlag(IEntity.DIRTY | IEntity.EMPTY | IEntity.PERSISTENT);
816                     //                            break;
817                     //                            case DataOperation.STORE:
818                     //                                operation.getEntity().setStatusFlag(IEntity.PERSISTENT);
819                     //                                operation.getEntity().clearStatusFlag(IEntity.DIRTY | IEntity.EMPTY);
820                     //                            break;  
821                     //                            case DataOperation.REFRESH: // Sets flags independant of transaction sucess!!
822                     //                            case DataOperation.LOAD:
823                     //                            case DataOperation.QUERY:
824                     //                            break;
825                     //                        }
826                     //                    }                
827                 }
828                 catch (Exception e)
829                 {
830                     throw new DataServiceException("Transaction failed: " + e.getClass().getName() + " - " + e.getMessage(), e);
831                 }
832 
833                 // Syncronize against persistant storage if opted.
834                 if (isSyncronized())
835                     syncronize();
836             }
837         }
838         
839         public void abortTransaction() throws DataServiceException
840         {
841             throw new UnsupportedOperationException();
842         }
843     }
844 }