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;
20  
21  
22  import java.util.*;
23  
24  import org.caleigo.core.event.*;
25  import org.caleigo.core.exception.*;
26  import org.caleigo.toolkit.log.*;
27  
28  /*** <Description for ControledProxySelection>
29   *
30   * @author  Dennis Zikovic
31   * @version 1.00
32   *
33   *//* 
34   *
35   * WHEN        WHO               WHY & WHAT
36   * -----------------------------------------------------------------------------
37   * 2002-04-22  Dennis Zikovic    Creation
38   */
39  public class ControlledProxySelection extends ProxySelection implements IControlledProxy
40  {
41      // Constants ---------------------------------------------------------------
42      
43      // Data members ------------------------------------------------------------
44      private IProxyController mControlEntityProxy;
45      private IEntityRelationPath mRelationPath;
46      private ActionQueue mEditActionQueue;
47      
48      // Constructors ------------------------------------------------------------
49      
50      /*** Creates new ControledSelectionProxy. Note protected scope the class 
51       * should normally be instantiated by a call to object impementing the
52       * IProxyController interface and the getControlledProxy method.
53       */
54      protected ControlledProxySelection(IProxyController controller, IEntityDescriptor selectionDescriptor) 
55      {
56          this(controller, selectionDescriptor, null);
57      }
58      
59      /*** Creates new ControledSelectionProxy. Note protected scope the class 
60       * should normally be instantiated by a call to object impementing the
61       * IProxyController interface and the getControlledProxy method.
62       * Provided path should be directed from controller to slave.
63       */
64      protected ControlledProxySelection(IProxyController controller, IEntityDescriptor selectionDescriptor, IEntityRelationPath relationPath) 
65      {
66          super(selectionDescriptor);
67          mEditActionQueue = new ActionQueue();
68          
69          // Verify the relation path.
70          if(relationPath!=null && !relationPath.isRelatedByPath(controller.getEntityDescriptor(), selectionDescriptor))
71              throw new InvalidRelationException("The relation path is not valid for the descriptors "+controller.getEntityDescriptor()+" and "+selectionDescriptor);
72          
73          // Store controller and relation path.
74          mControlEntityProxy = controller;
75          if(relationPath!=null)
76              mRelationPath = relationPath;
77          else
78              mRelationPath = this.createRelationPath();
79          
80          // Register new control listeners.
81          ControllListener listener = new ControllListener();
82          mControlEntityProxy.addProxyListener(listener);
83          mControlEntityProxy.addEntityChangeListener(listener);
84          
85          // Register change listener for cotained entities.
86          this.addEntityChangeListener(new IEntityChangeListener()
87                  {
88                      public void dataChanged(EntityChangeEvent event)
89                      {
90                      }
91  
92                      public void statusChanged(EntityChangeEvent event)
93                      {
94                          if(event.getStatusType()==IEntity.DIRTY)
95                          {
96                              if(event.getSourceEntity().isDirty())
97                                  registerChange(event.getSourceEntity());
98                              else if(event.getSourceEntity().isPersistent())
99                                  mEditActionQueue.clear((IUnaryEntityAction)event.getSourceEntity().getEntityDescriptor().getAction(IEntityDescriptor.STORE_ACTION), event.getSourceEntity());
100                         }
101                     }
102                 });
103                 
104         // Load data.
105         this.loadRemoteSelection();
106     }
107                 
108     // Superclass overrides ----------------------------------------------------
109     
110     /*** Return true if any (one or more) of the collections's contained 
111      * entities has the DIRTY flag set to true that is have unsaved changes.
112      */
113     public boolean isDirty()
114     {
115         return super.isDirty() || !mEditActionQueue.isEmpty();
116     }
117     
118     /*** Stores all contained entities that have the DIRTY flag set to true.
119      * The entities are stored in a single transaction meaning that if one
120      * store fails then all fails.
121      */
122     public void storeAll()
123     {
124         // Clear all queued store actions.
125         ITransactionEntityAction queued = null;
126         ITransactionEntityAction store = (ITransactionEntityAction)this.getEntityDescriptor().getAction(IEntityDescriptor.STORE_ACTION);
127         Iterator it = mEditActionQueue.getActions();
128         while(it.hasNext())
129         {
130             queued = (ITransactionEntityAction)it.next();
131             if(queued.getEntityDescriptor()==store)
132                 it.remove();
133         }
134 
135         // Enqueue store request for all persistent entities.
136         for(int j=0; j<this.size(); j++)
137             if(this.getEntity(j).isPersistent())
138                 this.registerChange(this.getEntity(j));
139     }
140     
141     /*** Deletes all contained entities. The entities are deleted in a single 
142      * transaction meaning that if one delete fails then all fails.
143      * USE THIS METHOD WITH CAUTION.
144      */
145     public void deleteAll()
146     {
147         // Clear all queued store actions.
148         mEditActionQueue.clear((ITransactionEntityAction)this.getEntityDescriptor().getAction(IEntityDescriptor.STORE_ACTION));
149         
150         // Enqueue delete request for all persistent entities.
151         for(int j=0; j<this.size(); j++)
152             if(this.getEntity(j).isPersistent())
153                 this.registerRemove(this.getEntity(j));
154     }
155     
156     /*** Performs a refresh based on the current controller. All queued edit
157      * operations are cleared.
158      */
159     public void refreshAll()
160     {
161         mEditActionQueue.clear();
162         this.loadRemoteSelection();
163     }
164     
165     /*** Adds the provided IEntity object to the end of selection object. 
166      * If an entity with the same identity already exists in the selection the
167      * requeat is ignored and false is returned.
168      */
169     public boolean addEntity(IEntity entity)
170     {
171         boolean ok = super.addEntity(entity);
172         if(ok)
173             this.registerAdd(entity);       
174         return ok;
175     }
176     
177     /*** Adds the provided IEntity object to at the specified index in the 
178      * selection object. If an entity with the same identity already exists 
179      * in the selection the requeat is ignored and false is returned.
180      */
181     public boolean addEntity(int index, IEntity entity)
182     {
183         boolean ok = super.addEntity(index, entity);
184         if(ok)
185             this.registerAdd(entity);       
186         return ok;
187     }
188 
189     /*** Mutation method that removes the provided entity from the selection.
190      * Returns true if the entity was found and removed otherwise false is 
191      * returned.
192      */
193     public boolean removeEntity(IEntity entity)
194     {
195         boolean ok = super.removeEntity(entity);
196         if(ok)
197             this.registerRemove(entity);       
198         return ok;
199     }
200     
201     /*** Mutation method that removes the indexed entity from the selection.
202      * Returns the indexed entity if it was found and removed otherwise null  
203      * is returned. The entities are not effecte in any other way.
204      */
205     public IEntity removeEntity(int index)
206     {
207         IEntity entity = super.removeEntity(index);
208         if(entity!=null)
209             this.registerRemove(entity);     
210         return entity;
211     }
212 
213     // IControlledProxy implementation -----------------------------------------
214     
215     public IProxyController getController()
216     {
217         return mControlEntityProxy; 
218     }
219      
220     public IEntityRelationPath getRelationPath()
221     {
222         return mRelationPath;
223     }
224 
225     public boolean isEditable()
226     {
227         return mRelationPath.getRelationCount()==1 || mRelationPath.getRelationCount()==2 && 
228                 mRelationPath.getFirstRelation().getRelatedEntityDescriptor(mRelationPath.getFirstEntityDescriptor()).getEntityType()==IEntityDescriptor.LINK_ENTITY;            
229     }
230     
231     public void prepareLoad(IDataTransaction transaction)
232     {
233         if(mControlEntityProxy==null || mControlEntityProxy.isEmpty())
234             this.clear();
235         else
236         {
237             // Create new selection.
238             ISelection selection = new Selection(this.getEntityDescriptor());
239             
240             if(mRelationPath.getRelationCount()==1)
241             {
242                 Qualifier directQualifier = Relations.makeRelationQualifier(mControlEntityProxy, mRelationPath.getRelation(0));
243                 
244                 //Prepare loading of the new selection.
245                 transaction.addLoadSelection(this.getEntityDescriptor(), directQualifier, selection);
246             }
247             else
248             {
249                 IFieldDescriptor[] targetFieldDescriptors = new IFieldDescriptor[mRelationPath.getRelation(0).getFieldCount()];
250                 Iterator iter = mRelationPath.getRelation(0).getTargetFieldDescriptors();
251                 int c = 0;
252                 while (iter.hasNext())
253                     targetFieldDescriptors[c++] = (IFieldDescriptor) iter.next();
254                 Qualifier qualifier = Relations.makeFieldQualifier(targetFieldDescriptors, mControlEntityProxy);
255                 //Prepare loading of the new selection use of external qualifier.
256                 transaction.addLoadSelection(this.getEntityDescriptor(), qualifier, mRelationPath, selection);
257             }
258             
259             this.setRemoteSelection(selection);
260         }
261     }
262     
263     public void prepareStore(IDataTransaction transaction)
264     {
265         // Update all proxy data on all non-persistent entities.
266         if(mRelationPath.getRelationCount()==1 && this.isDirty() && !mControlEntityProxy.isPersistent())
267         {
268             for(int j=0; j<this.size(); j++)
269                 if(!this.getEntity(j).isPersistent())
270                     Relations.setReferenceData(this.getEntity(j), mControlEntityProxy, mRelationPath.getRelation(0));
271         }
272 
273         mEditActionQueue.prepareTransaction(transaction);
274     }
275     
276     /*** The finalizeTransaction method is called after a transaction have been
277      * fully completed. This method will always be called once after any call
278      * to prepareLoad or prepareStore.
279      */
280     public void finalizeTransaction(boolean successful)
281     {
282         if(successful)
283             mEditActionQueue.clear();
284         
285         // Check for dirty entities in selection. We do this since the 
286         // transaction is running in another thread and some other entities 
287         // can have been added during the transaction. Solution is questionable
288         // and som better way to solve this would desirable.       
289         if(this.isDirty())
290         {
291             for(int j=0; j<this.size(); j++)
292                 if(!this.getEntity(j).isPersistent())
293                     this.registerAdd(this.getEntity(j));
294         }
295     }
296     
297     // Help methods ------------------------------------------------------------
298     
299     protected boolean isMasterRelationLinked()
300     {
301         if(mRelationPath.getRelationCount()==2)
302             return mRelationPath.getLastRelation().getRelatedEntityDescriptor(mRelationPath.getLastEntityDescriptor()).getEntityType()==IEntityDescriptor.LINK_ENTITY;
303         else
304             return false;
305     }
306     
307     protected IEntity getLinkEntity(IEntity entity)
308     {
309         IEntity linkEntity = null;
310         if(mRelationPath.getRelationCount()==2)
311         {
312             IEntityDescriptor linkDescriptor = mRelationPath.getLastRelation().getRelatedEntityDescriptor(mRelationPath.getLastEntityDescriptor());
313         }
314         return linkEntity;
315     }
316     
317     protected IEntity makeLinkEntity(IEntity entity)
318     {
319         IEntity linkEntity = null;
320         if(mRelationPath.getRelationCount()==2)
321         {
322             IEntityDescriptor linkDescriptor = mRelationPath.getLastRelation().getRelatedEntityDescriptor(mRelationPath.getLastEntityDescriptor());
323             if(linkDescriptor.getEntityType()==IEntityDescriptor.LINK_ENTITY)
324             {
325                 linkEntity = linkDescriptor.createEntity();
326 //                mControlEntityProxy.initializeEntity(linkEntity, mRelationPath);
327                 Relations.setReferenceData(linkEntity, mControlEntityProxy, mRelationPath.getRelation(0));
328                 Relations.setReferenceData(linkEntity, entity, mRelationPath.getRelation(1));
329             }
330        }
331        return linkEntity;
332     }
333     
334     protected void registerAdd(IEntity entity)
335     {
336         if(!this.isEditable())
337             throw new UnsupportedOperationException("RegisterAdd called on non editable proxy!");
338 
339         boolean wasDirty = this.isDirty();
340         
341         // Store added entity if non persistent or dirty.
342         // Note that the added entity should be enqueded to the action queue 
343         // prior to any link entity since it may contain generated key fields.
344         if(mRelationPath.getRelationCount()==1)
345             Relations.setReferenceData(entity, mControlEntityProxy, mRelationPath.getRelation(0));
346         if(!entity.isPersistent() || entity.isDirty())
347         {
348             ITransactionEntityAction action = (ITransactionEntityAction)this.getEntityDescriptor().getAction(IEntityDescriptor.STORE_ACTION);
349             mEditActionQueue.addAction(action, entity);
350             Log.print(this, "Store action queued on "+entity);
351         }
352             
353         // Add link creation if needed.
354         IEntity linkEntity = this.makeLinkEntity(entity);
355         if(linkEntity!=null)
356         {
357             ITransactionEntityAction action = (ITransactionEntityAction)linkEntity.getEntityDescriptor().getAction(IEntityDescriptor.STORE_ACTION);
358             mEditActionQueue.addAction(action, linkEntity);
359             Log.print(this, "Store action queued on link "+linkEntity);
360         }
361         
362         // Fire StatusChangedEvent if dirty mode changed.
363         if(wasDirty!=this.isDirty())
364             this.fireStatusChangedEvent(entity, IEntity.DIRTY, this.isDirty());
365     }
366     
367     protected void registerChange(IEntity entity)
368     {
369         if(!this.isEditable())
370             throw new UnsupportedOperationException("RegisterChange called on non editable proxy!");
371                 
372         boolean wasDirty = this.isDirty();
373         
374         if(entity.isDirty() && entity.isPersistent())
375         {
376             ITransactionEntityAction action = (ITransactionEntityAction)this.getEntityDescriptor().getAction(IEntityDescriptor.STORE_ACTION);
377             mEditActionQueue.addAction(action, entity);
378             Log.print(this, "Store action queued on "+entity);
379         }
380         
381         // Fire StatusChangedEvent if dirty mode changed.
382         if(wasDirty!=this.isDirty())
383             this.fireStatusChangedEvent(entity, IEntity.DIRTY, this.isDirty());
384     }
385     
386     protected void registerRemove(IEntity entity)
387     {
388         if(!this.isEditable())
389             throw new UnsupportedOperationException("RegisterRemove called on non editable proxy!");
390                 
391         boolean wasDirty = this.isDirty();
392         
393         IEntity referencedEntity = entity;
394         if(this.isMasterRelationLinked())
395         {
396             entity = this.makeLinkEntity(entity);
397             entity.refresh();
398         }
399         if(entity.isPersistent())
400         {
401             // Delete directly linked slaves.
402             ITransactionEntityAction action = (ITransactionEntityAction)entity.getEntityDescriptor().getAction(IEntityDescriptor.DELETE_ACTION);
403             mEditActionQueue.addAction(action, entity);
404             Log.print(this, "Delete action queued on "+entity);
405         }
406         else
407         {
408             mEditActionQueue.clear(null, entity);
409             mEditActionQueue.clear(null, referencedEntity);
410         }
411         
412         // Fire StatusChangedEvent if dirty mode changed.
413         if(wasDirty!=this.isDirty())
414             this.fireStatusChangedEvent(entity, IEntity.DIRTY, this.isDirty());
415     }
416     
417     protected void loadRemoteSelection()
418     {
419         IDataTransaction transaction = this.getEntityDescriptor().getDataSourceDescriptor().getDefaultDataSource().getDataService().newTransaction();
420         this.prepareLoad(transaction);
421         if(!transaction.isEmpty());
422         	transaction.commit();
423     }
424     
425     /*** This method is called each time the view entity object are replaced.
426      * It is called prior to doOnDataChange and does does by default clears 
427      * the selection if the master is set to "empty".
428      */
429     protected void doOnControllerChange()
430     {
431         if((mControlEntityProxy instanceof IProxyEntity) && ((IProxyEntity)mControlEntityProxy).getSourceEntity()==null)
432             this.clear();
433     }
434     
435     /*** This method is called each time any data in the viewed entity data are 
436      * changed. It is called emidiately after doOnEntityChange and does nothing 
437      * by default.
438      */
439     protected void doOnControllerDataChange(IFieldDescriptor fieldDescriptor)
440     {
441     }
442     
443     /*** Protected help method responsible for  creating the relation path 
444      * between the master and slave descriptor. The method is only called 
445      * if no relation path was provided in the object construction. By default
446      * i creates a new EntityRelationPath with no provided waypoint by  it can
447      * be overrriden for more customized bahaviour.
448      */
449     protected IEntityRelationPath createRelationPath()
450     {
451         return EntityRelationPath.create(mControlEntityProxy.getEntityDescriptor(), this.getEntityDescriptor());
452     }
453 
454     // Nested classes ----------------------------------------------------------
455     
456     protected class ControllListener implements IEntityChangeListener, IProxyListener
457     {
458         // IEntityChangeListener implemntation ---------------------------------
459         public void dataChanged(EntityChangeEvent event)
460         {
461             doOnControllerDataChange(event.getFieldDescriptor());
462         }
463 
464         public void statusChanged(EntityChangeEvent event)
465         {
466         }
467         
468         // IProxyListener implementation ---------------------------------------
469         public void remoteChanged(ProxyEvent event)
470         {
471             doOnControllerChange();
472             doOnControllerDataChange(null);
473         }
474 
475         public void remoteExpanded(ProxyEvent event)
476         {
477             doOnControllerDataChange(null);
478         }
479     }
480 }