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  import org.caleigo.toolkit.util.*;
28  
29  /*** A ProxyEntity is a basic implementation of the IProxyEntity iterface.
30   * The class acts as a proxy to another IEntity object that may set by simple
31   * access a methods. All events are forwarded transparently.
32   *
33   * Note that controlled proxies are transient and will not be serialized.
34   *
35   * @author  Dennis Zikovic
36   * @version 1.00
37   *
38   *//* 
39   *
40   * WHEN        WHO               WHY & WHAT
41   * -----------------------------------------------------------------------------
42   * 2001-11-21  Dennis Zikovic    Creation
43   */
44  public class ProxyEntity implements IProxyEntity, IProxyController
45  {
46      // Data members ------------------------------------------------------------
47      private IEntity mRemoteEntity;
48      private IEntityDescriptor mEntityDescriptor;
49      
50      private RelayListener mRelayListener;
51      private transient IEntityListener mEntityListener;
52      private transient IEntityChangeListener mEntityChangeListener;
53      private transient IProxyListener mProxyListener;
54      
55      private transient Map mControlledProxyMap;
56      private transient boolean mHasDirtyControlledProxies = false;
57      private transient ControlledProxyListener mControlledProxyListener;
58          
59      // Constructors ------------------------------------------------------------
60      
61      /*** Creates new ProxyEntity
62       */
63      public ProxyEntity(IEntityDescriptor descriptor) 
64      {
65          mEntityDescriptor = descriptor;
66      }
67      
68      /*** Creates new ProxyEntity
69       */
70      public ProxyEntity(IEntity entity) 
71      {
72          // Check entity, the proxy must have a descriptor.
73          if(entity==null)
74              throw new InvalidEntityException("ProxyEntity can not be instatiated with a null entity.");
75          
76          // Store state data.
77          mEntityDescriptor = entity.getEntityDescriptor();
78          this.setRemoteEntity(entity);
79      }
80      
81      // Superclass overrides ----------------------------------------------------
82      
83      /*** Overriden to display the entity description type, identifying data and
84       * state of the status flags (D)IRTY, (E)MPTY and (P)ERSISTANT.
85       */
86      public String toString()
87      {
88          if(mRemoteEntity!=null)
89              return "Proxy to "+mRemoteEntity.toString();
90          else
91              return "Proxy to "+mEntityDescriptor.getCodeName()+"(EMPTY)";
92      }
93      
94      // IProxyEntity implementation ---------------------------------------------
95      
96      /*** Boolean access method that returns true if the proxy has a remote
97       * entity if false then getRemoteEntity() will return null.
98       */
99      public boolean hasRemoteEntity()
100     {
101         return this.getRemoteEntity()!=null;
102     }
103     
104     /*** Access method that returns the remote entity of the proxy.
105      * May return null if the proxy does not currently have a remote.
106      */
107     public IEntity getRemoteEntity()
108     {
109         return mRemoteEntity;
110     }
111     
112     /*** Boolean access method that returns true if the proxy has a source
113      * entity if false then getSourceEntity() will return null.
114      */
115     public boolean hasSourceEntity()
116     {
117         return this.getSourceEntity()!=null;
118     }
119     
120     /*** Access method that returns the source entity of the proxy.
121      * May return null if the proxy does not currently have a source entity.
122      */
123     public IEntity getSourceEntity()
124     {
125         if(mRemoteEntity==null)
126             return null;
127         if(mRemoteEntity instanceof IProxyEntity)
128             return ((IProxyEntity)mRemoteEntity).getSourceEntity();
129         else
130             return this.getRemoteEntity();
131     }
132     
133     /*** Optional mutation method that throws an UnsupportedOperationException 
134      * if the implementing class does not support the method.
135      */
136     public void setRemoteEntity(IEntity entity)
137     {
138         // Ignore entity if equal to the current remote entity.
139         if(mRemoteEntity==entity)
140             return; 
141         
142         // Verify the new entities type.
143         if(entity!=null && entity.getEntityDescriptor()!=mEntityDescriptor)
144             throw new InvalidEntityException("ProxyEntity with type \""+mEntityDescriptor.getCodeName()+"\" was asigned entity of type \""+entity.getEntityDescriptor().getCodeName()+"\".");
145         
146         // Remove all listeners in existing remote object if any.
147         if(mRemoteEntity!=null)
148         {
149             if(mEntityListener!=null)
150                 mRemoteEntity.removeEntityListener(this.getRelayListener());
151             if(mEntityChangeListener!=null)
152                 mRemoteEntity.removeEntityChangeListener(this.getRelayListener());
153             if(mRemoteEntity instanceof IProxyEntity)
154                 ((IProxyEntity)mRemoteEntity).removeProxyListener(this.getRelayListener());
155         }
156         
157         // Update the stored remote entity.
158         mRemoteEntity = entity;
159         
160         // Register listeners on the new remote object.
161         if(mRemoteEntity!=null)
162         {
163             if(mEntityListener!=null)
164                 mRemoteEntity.addEntityListener(this.getRelayListener());
165             if(mEntityChangeListener!=null)
166                 mRemoteEntity.addEntityChangeListener(this.getRelayListener());
167             if(mRemoteEntity instanceof IProxyEntity)
168                  ((IProxyEntity)mRemoteEntity).addProxyListener(this.getRelayListener());
169         }
170         
171         // Load controlled proxies.
172         this.loadControlledProxies(false);
173         
174         // Fire relevant proxy event.
175         this.fireProxyEvent(ProxyEvent.CHANGED);
176     }
177     
178     /*** Adds an IProxyListener to receive notifications of changes of 
179      * the remote object.
180      */
181     public void addProxyListener(IProxyListener listener)
182     {
183 //        if(mProxyListener==null && mRemoteEntity!=null && mRemoteEntity instanceof IProxyEntity)
184 //             ((IProxyEntity)mRemoteEntity).addProxyListener(this.getRelayListener());
185         mProxyListener = (IProxyListener)CELEventMulticaster.add(mProxyListener, listener);
186     }    
187     
188     /*** Removes the specified IProxyListener from the remote object.
189      */
190     public void removeProxyListener(IProxyListener listener)
191     {
192         mProxyListener = (IProxyListener)CELEventMulticaster.remove(mProxyListener, listener);
193 //        if(mProxyListener==null && mRemoteEntity!=null && mRemoteEntity instanceof IProxyEntity)
194 //             ((IProxyEntity)mRemoteEntity).removeProxyListener(this.getRelayListener());
195     }    
196 
197     // IEntity implementation --------------------------------------------------
198     
199     /*** This method will store any unsaved changes in the entity to it's 
200      * realated persistent storage. This will reset the DIRTY flag and set 
201      * the PERSISTENT flag.
202      */ 
203     public void store()
204     {
205         if(mRemoteEntity==null)
206             return;
207         if(this.hasDirtyControlledProxies())
208         {
209             this.storeControlledProxies();
210         }
211         else
212             mRemoteEntity.store();
213     }
214     
215     /*** This method will delete the entity from it's related persistent storage.
216      * This will set the DIRTY flag and reset the PERSISTENT flag. Note that 
217      * that the actual java object will not be deleted or changed in any other 
218      * way besides its status changes makeing possible to recreate the object
219      * by simply calling the store() method.
220      */
221     public void delete()
222     {
223         if(mRemoteEntity!=null)
224         {
225             mRemoteEntity.delete();
226             this.loadControlledProxies(true);
227         }
228     }
229     
230     /*** This method will refresh the entity with current data from the related 
231      * persistent storage. All contained data with any existing changes will
232      * be replaced from the storage. This will reset the DIRTY flag and can
233      * possibly reset the PERSISTENT flag if the entity can no longer be found 
234      * in the related database/storage. 
235      */
236     public void refresh()
237     {
238         if(mRemoteEntity!=null)
239         {
240             mRemoteEntity.refresh();
241             this.loadControlledProxies(true);
242         }
243     }
244     
245     /*** Copies and replaces the, in the entity, contained data by reading each 
246      * individual data field as a property from the provided property source.
247      * Note that since IEntity extends the IDataProvider interface it is
248      * thereby possibli to copy other entities with this method. Even if the
249      * etities are not of the same type all fields with the same name will be 
250      * copied into the entity. Identical data will not be copied and if any 
251      * changes were made the DIRTY flag will be set.
252      */
253     public void copyData(IDataProvider provider)
254     {
255         if(mRemoteEntity!=null)
256         {
257             mRemoteEntity.copyData(provider);
258             this.loadControlledProxies(false);
259         }
260     }
261         
262     /*** Returns true if the data in all the entities IDENTIY fields are 
263      * considered equal according to their DataType class.
264      */
265     public boolean equals(Object entity)
266     {
267         if(mRemoteEntity!=null)
268             return mRemoteEntity.equals(entity);
269         else
270             return false;
271     }
272     
273     /*** Compares all data values between the objects if they are of the 
274      * same type. True is returned only if all contained data exactly matches 
275      * the compareded entity's data according to their DataType class.
276      */
277     public boolean equalsExactly(Object entity)
278     {
279         if(mRemoteEntity!=null)
280             return mRemoteEntity.equalsExactly(entity);
281         else
282             return false;
283     }
284     
285     /*** Returns true if the addressed entity field is contains a NULL value.
286      */
287     public boolean isDataNull(IFieldDescriptor fieldDescriptor)
288     {
289         if(mRemoteEntity!=null)
290             return mRemoteEntity.isDataNull(fieldDescriptor);
291         else
292             return fieldDescriptor.getDefaultValue()==null;
293     }
294     
295     /*** Returns the data value of the addressed data field. Can return NULL if
296      * the field excepts and contains a NULL value.
297      * @exception org.caleigo.core.exception.InvalidFieldException
298      */
299     public Object getData(IFieldDescriptor fieldDescriptor)
300     {
301         if(mRemoteEntity!=null)
302             return mRemoteEntity.getData(fieldDescriptor);
303         else
304             return fieldDescriptor.getDefaultValue();
305     }
306 
307     /*** Sets the value of the addressed data field. Sets the DIRTY flag and
308      * clears the EMPTY flag but only if the new value differs from the old.
309      * If the value is actually changed then one or more EntityChangeExceptions
310      * will be fired.
311      * @exception org.caleigo.core.exception.InvalidFieldException
312      * @exception org.caleigo.core.exception.ReadOnlyViolationException
313      */
314     public void setData(IFieldDescriptor fieldDescriptor, Object data)
315     {
316         if(mRemoteEntity!=null)
317             mRemoteEntity.setData(fieldDescriptor, data);
318     }
319     
320     /*** Clear resets all data in the entity to their defalt values and sets 
321      * the flags to reflect an empty unchanged data entity.
322      */
323     public void clear()
324     {
325         if(mRemoteEntity!=null)
326             mRemoteEntity.clear();
327     }
328     
329     /*** Help method that validates the data contained in the called data
330      * object and returns a ValidationResult object. Call isValid on the 
331      * returned object to verify data validity. Should never return null.
332      */
333     public ValidationResult validateData()
334     {
335         // Validate the remote entity.
336         ValidationResult result = mRemoteEntity.validateData();
337         if(!result.isValid())
338             return result;
339             
340         // Validate controlled proxies if any.
341         //
342         // Note that the slaves validation methods is not used since the slaves
343         // fields that are part of the relation to their master should not be
344         // included in the validation if the master is non-persistent.
345         Iterator slaves = this.getControlledProxies();
346         while(result.isValid() && slaves.hasNext())
347         {
348             IControlledProxy slave = (IControlledProxy)slaves.next();
349             if((slave instanceof IEntity) && ((IEntity)slave).isDirty())
350             {
351                 for(int j=0; result.isValid() && j<slave.getEntityDescriptor().getFieldCount(); j++)
352                     if(mRemoteEntity.isPersistent() || !slave.getRelationPath().getLastRelation().isRelationField(slave.getEntityDescriptor().getFieldDescriptor(j)))
353                         result = slave.getEntityDescriptor().getFieldDescriptor(j).validateData(((IEntity)slave).getData(slave.getEntityDescriptor().getFieldDescriptor(j)), (IEntity)slave);
354             }
355             else if(slave instanceof ISelection)
356             {
357                 // Scan dirty entities for invalid data.
358                 for(int k=0; k<((ISelection)slave).size() && result.isValid(); k++)
359                     if(((ISelection)slave).getEntity(k).isDirty())
360                     {
361                         for(int j=0; result.isValid() && j<slave.getEntityDescriptor().getFieldCount(); j++)
362                             if(mRemoteEntity.isPersistent() || !slave.getRelationPath().getLastRelation().isRelationField(slave.getEntityDescriptor().getFieldDescriptor(j)))
363                                 result = slave.getEntityDescriptor().getFieldDescriptor(j).validateData(((ISelection)slave).getEntity(k).getData(slave.getEntityDescriptor().getFieldDescriptor(j)), ((ISelection)slave).getEntity(k));
364                     }
365             }
366         }
367         
368         return result;
369     }
370 
371     /*** Return the entity objects IEntityDescriptor that defines it's type and 
372      * structure. Enables extended means of reflection for the entity. 
373      */
374     public IEntityDescriptor getEntityDescriptor()
375     {
376         return mEntityDescriptor;
377     }
378     
379     /*** Returns true if the addressed entity field has been changed since 
380      * creation or the last syncronization with the persistent storage.
381      */
382     public boolean isFieldDirty(IFieldDescriptor fieldDescriptor)
383     {
384         if(mRemoteEntity!=null)
385             return mRemoteEntity.isFieldDirty(fieldDescriptor);
386         else
387             return false;
388     }
389     
390     /*** Returns true if any entity field in the entity has been changed since 
391      * creation or the last syncronization with the persistent storage.
392      */
393     public boolean isDirty()
394     {
395         // Check controlled proxy chain objects.
396         if(mRemoteEntity!=null)
397             return mRemoteEntity.isDirty() || this.hasDirtyControlledProxies();
398         else
399             return false;
400     }
401     
402     /*** Returns true for newly creted object that that has had no 
403      * changes from the default data set at the moment of creation.
404      */
405     public boolean isEmpty()
406     {
407         if(mRemoteEntity!=null)
408             return mRemoteEntity.isEmpty();
409         else
410             return true;
411     }
412     
413     /*** Returns true if the the entity reflects data that exists in a related 
414      * persistent storage. This means that newly created and deleted entities 
415      * will have this flag set to false. 
416      */
417     public boolean isPersistent()
418     {
419         if(mRemoteEntity!=null)
420             return mRemoteEntity.isPersistent();
421         else
422             return false;
423     }
424     
425     /*** Returns the data source that the entity object belongs to. 
426      * Newly created will normally return the default data source defined
427      * by the IDataSourceDescriptor that the entity is linked to trough it's
428      * IEntityDescriptor. Entities loaded from a persistent storage will return 
429      * the data source that identifies that storage/database.  
430      */
431     public IDataSource getDataSource()
432     {
433         if(mRemoteEntity!=null)
434             return mRemoteEntity.getDataSource();
435         else if(mEntityDescriptor.getDataSourceDescriptor()!=null)
436             return mEntityDescriptor.getDataSourceDescriptor().getDefaultDataSource();
437         else
438             return null;
439     }
440     
441     /*** Returns a identity qualifier that uniquely qualifies the entity in a 
442      * persistent storage. If the entity is PERSISTENt and any of the data in 
443      * the identity fields have changed since the storage syncronization the
444      * returned Qualifier will identify the stored persistent version of the
445      * entity and NOT& the updated local one.
446      */
447     public Qualifier getOriginQualifier()
448     {
449         if(mRemoteEntity!=null)
450             return mRemoteEntity.getOriginQualifier();
451         else
452             return null;
453     }
454     
455     /*** Should not normally be used by standard API users. Would have been
456      * protected if the Java language spec allowed it.
457      */
458     public void setStatusFlag(int flags)
459     {
460         if(mRemoteEntity!=null)
461             mRemoteEntity.setStatusFlag(flags);
462     }
463     
464     /*** Should not normally be used by standard API users. Would have been
465      * protected if the Java language spec allowed it.
466      */
467     public void clearStatusFlag(int flags)
468     {
469         if(mRemoteEntity!=null)
470             mRemoteEntity.clearStatusFlag(flags);
471     }
472     
473     /*** Adds IEntityListener to receive notifications of performed 
474      * data operations on the entity object.
475      */
476     public void addEntityListener(IEntityListener listener)
477     {
478         if(mEntityListener==null && mRemoteEntity!=null)
479              mRemoteEntity.addEntityListener(this.getRelayListener());
480         mEntityListener = (IEntityListener)CELEventMulticaster.add(mEntityListener, listener);
481     }
482     
483     /*** Removes the specified IEntityListener from the entity object.
484      */
485     public void removeEntityListener(IEntityListener listener)
486     {
487         mEntityListener = (IEntityListener)CELEventMulticaster.remove(mEntityListener, listener);
488         if(mEntityChangeListener==null && mRemoteEntity!=null)
489              mRemoteEntity.removeEntityListener(this.getRelayListener());
490     }
491 
492     /*** Adds IEntityChangeListener to receive notifications of changes in the  
493      * entity's status and data content. Note that changes can in specific 
494      * situations like during end-user editation be very frequent. 
495      */
496     public void addEntityChangeListener(IEntityChangeListener listener)
497     {
498         if(mEntityChangeListener==null && mRemoteEntity!=null)
499              mRemoteEntity.addEntityChangeListener(this.getRelayListener());
500         mEntityChangeListener = (IEntityChangeListener)CELEventMulticaster.add(mEntityChangeListener, listener);
501     }
502     
503     /*** Removes the specified IEntityListener from the entity object.
504      */
505     public void removeEntityChangeListener(IEntityChangeListener listener)    
506     {
507         mEntityChangeListener = (IEntityChangeListener)CELEventMulticaster.remove(mEntityChangeListener, listener);
508         if(mEntityChangeListener==null && mRemoteEntity!=null)
509              mRemoteEntity.removeEntityChangeListener(this.getRelayListener());
510     }
511 
512     // Comparable implementation -----------------------------------------------
513     
514     /*** Compares all identity data values between the objects if they are of the 
515      * same type that is are defined by the same entity descriptor. <BR><BR>
516      *
517      * If the compared object is not an IEntity object a ClassCastException
518      * will be thrown. If the object is an IEntity but of another type, that is 
519      * described by another entity descriptor, then the entities will first be 
520      * ordered according to type using the order MASTER_ENTITY, SLAVE_ENTITY, 
521      * LINK_ENTITY, STATIC_ENTITY, CUSTOM_ENTITY and secondly according to the
522      * entity descriptors code name. <BR><BR>
523      *
524      * If the proxy does not have a remote it is compared with the provided 
525      * entity using the default data values according to its type. 
526      */
527     public int compareTo(Object entity)
528     {
529         // Throw exception if the provided object is null or not an IEntity.
530         if(entity==null || !(entity instanceof IEntityDescriptor))
531             throw new ClassCastException();
532         else if(mRemoteEntity!=null)
533             return mRemoteEntity.compareTo(entity);
534         else
535         {
536             // Checks if the entities are of the same type if not use special order.
537             if(this.getEntityDescriptor()!=((IEntity)entity).getEntityDescriptor())
538             {
539                 if(this.getEntityDescriptor().getEntityType()!=((IEntity)entity).getEntityDescriptor().getEntityType())
540                     return this.getEntityDescriptor().getEntityType()-((IEntity)entity).getEntityDescriptor().getEntityType();
541                 else
542                     return this.getEntityDescriptor().getCodeName().compareTo(((IEntity)entity).getEntityDescriptor().getCodeName());
543             }
544 
545             // Else compare all individal data values based on their DataType
546             // and using the default values defined by their field descriptor.
547             int compValue = 0;
548             IFieldDescriptor field;
549             for(int j=0; compValue==0 && j<this.getEntityDescriptor().getFieldCount(); j++)
550             {
551                 field = this.getEntityDescriptor().getFieldDescriptor(j);
552                 if(field.isIdentityField())
553                     compValue = field.getDataType().compare(this.getData(field), ((IEntity)entity).getData(field));
554             }
555             return compValue;
556         }
557     }
558     
559     // IProxyController implementation -----------------------------------------
560     
561     /*** Returns an IControlledProxy object controlled by the called controller.
562      * Implementing classes may cache the controlled proxies and return the 
563      * same physical proxy object as long as the relationPath matches.
564      * The controller may refuse to controll a proxy and should then cast 
565      * an IllegalArgumentException. If the used relation path does not link
566      * up the controller and the slave an InvalidRelationException is thrown.
567      */
568     public IControlledProxy getControlledProxy(IEntityDescriptor entityDescriptor, IEntityRelationPath relationPath)
569     {
570         IControlledProxy proxy = null;
571         
572         // Create control map if not already done.
573         if(mControlledProxyMap==null)
574             mControlledProxyMap = new HashMap();
575         
576         // Check if proxy is already created and still controlled.
577         SlaveProxyItem item = (SlaveProxyItem)mControlledProxyMap.get(relationPath);
578         if(item!=null)
579             proxy = item.getControlledProxy();
580         
581         // Create new proxy if not already controlled.
582         if(proxy==null)
583         {
584             // Validate relation path as connecting to own entity descriptor.
585             if(relationPath==null || !relationPath.isRelatedByPath(this.getEntityDescriptor(), entityDescriptor))
586                 throw new InvalidRelationException("Invalid relation path "+this.getEntityDescriptor()+" and "+entityDescriptor+" are not related by the path "+relationPath);
587             
588             // Create controlled proxy.
589             if(relationPath.getForwardCardinality()==IEntityRelationPath.ONE && relationPath.getRelation(0).canBeFullReference(this.getEntityDescriptor()))
590             {
591                 proxy = new ControlledProxyEntity(this, entityDescriptor, relationPath);
592                 ((IEntity)proxy).addEntityChangeListener(this.getControlledProxyListener());
593             }
594             else
595             {
596                 proxy = new ControlledProxySelection(this, entityDescriptor, relationPath);
597                 ((ISelection)proxy).addEntityChangeListener(this.getControlledProxyListener());
598                 ((ISelection)proxy).addSelectionListener(this.getControlledProxyListener());
599             }
600             
601             // Store proxy as controlled proxy.
602             mControlledProxyMap.put(relationPath, new SlaveProxyItem(proxy));
603             
604             Log.print(this, "Creating new controlled proxy: "+relationPath);
605         }
606         else
607             item.addProxyUsers();
608         
609         return proxy;
610     }
611         
612     /*** Used to inform the controller that a controlled proxy instance will be
613      * discarded and gives the controller a chance to deallocate resources 
614      * connected to it. 
615      */
616     public void releaseControlledProxy(IControlledProxy proxy)
617     {
618         if(mControlledProxyMap==null)
619             return;
620         
621         SlaveProxyItem item = ((SlaveProxyItem)mControlledProxyMap.get(proxy.getRelationPath()));
622         if(item!=null)
623         {
624             // Decrease the number of active proxy users.
625             item.decProxyUsers();
626             
627             // Remove control item if no users remain.
628             if(!item.hasProxyUsers())
629             {
630                 mControlledProxyMap.remove(proxy.getRelationPath());
631                 if(proxy instanceof IEntity)
632                     ((IEntity)proxy).removeEntityChangeListener(this.getControlledProxyListener());
633                 else if(proxy instanceof ISelection)
634                 {
635                     ((ISelection)proxy).removeEntityChangeListener(this.getControlledProxyListener());
636                     ((ISelection)proxy).removeSelectionListener(this.getControlledProxyListener());
637                 }
638                 this.updateDirtyState();
639                     
640                 Log.print(this, "Removing controlled proxy: "+proxy.getRelationPath());
641             }
642         }
643     }
644         
645     /*** Returns an Iterator with all proxies currently controlled by the
646      * called controller.
647      */ 
648     public java.util.Iterator getControlledProxies()
649     {
650         if(mControlledProxyMap==null)
651             return Iterators.EMPTY_ITERATOR;
652         else
653             return new Iterators.UnmodifiableIterator(mControlledProxyMap.values().iterator())
654                     {
655                         public Object next() 
656                         {
657                             return ((SlaveProxyItem)mIterator.next()).getControlledProxy();
658                         }
659                     };
660     }
661         
662     /*** Prepares an transaction to load the proxy-chain structure that is
663      * controlled by the called IProxyController.
664      */
665     public void prepareControlledLoad(IDataTransaction transaction)
666     {
667         Iterator it = this.getControlledProxies();
668         while(it.hasNext())
669             ((IControlledProxy)it.next()).prepareLoad(transaction);
670     }
671     
672     /*** Prepares an transaction to store any changes made to the proxy-chain 
673      * structure that is controlled by the called IProxyController.
674      */
675     public void prepareControlledStore(IDataTransaction transaction)
676     {
677         Iterator it = this.getControlledProxies();
678         while(it.hasNext())
679             ((IControlledProxy)it.next()).prepareStore(transaction);
680     }
681     
682     /*** The finalizeTransaction method is called after a transaction have been
683      * fully completed. This method will always be called once after any call
684      * to prepareLoad or prepareStore.
685      */
686     public void finalizeTransaction(boolean successful)
687     {
688         Iterator it = this.getControlledProxies();
689         while(it.hasNext())
690             ((IControlledProxy)it.next()).finalizeTransaction(successful);
691     }
692     
693     /*** Help method that initializes the data in an entity object based on its
694      * relation to the controller.
695      */
696     public void initializeEntity(IEntity entity, IEntityRelationPath relationPath)
697     {
698         if(mControlledProxyMap==null)
699             return;
700         
701         // !!!
702     }
703     
704     // IDataProvider implementation --------------------------------------------
705     public Object getData(String codeName) 
706     {
707         if(mRemoteEntity!=null)
708             return mRemoteEntity.getData(codeName);
709         else
710             return null;
711     }
712     
713     // IDataConsumer implementation -------------------------------------------
714     public void setData(String codeName, Object dataValue) 
715     {
716         if(mRemoteEntity!=null)
717             mRemoteEntity.setData(codeName, dataValue);
718     }
719     
720     public void setData(IDataProvider dataProvider) 
721     {
722         if(mRemoteEntity!=null)
723             mRemoteEntity.setData(dataProvider);
724     }
725  
726     // Help method -------------------------------------------------------------
727     
728     /*** This method is called each time the remote entity object are replaced.
729      * The method does by default load all controlled proxies.
730      */
731     public void doOnRemoteChange()
732     {
733         // Update all controlled proxies
734         this.loadControlledProxies(false);
735     }
736     
737     protected boolean hasDirtyControlledProxies()
738     {
739         Iterator it = this.getControlledProxies();
740         while(it.hasNext())
741             if(((IControlledProxy)it.next()).isDirty())
742                 return true;
743         return false;
744     }
745 
746     protected void loadControlledProxies(boolean includeLocalRemote) throws DataServiceException
747     {
748         IDataTransaction transaction = this.getDataSource().getDataService().newTransaction();
749         if(includeLocalRemote)
750             transaction.addRefresh(this.getSourceEntity());
751         else if(this.getSourceEntity()==null)
752             return;
753         
754         // Prepare transaction with controlled proxies actions.
755         try
756         {
757             this.prepareControlledLoad(transaction);
758             AsyncTransactionHandler transactionHandler = AsyncTransactionHandler.create();
759             transactionHandler.setCallback(this, "loadControlledProxiesCallback");
760             if(!transaction.isEmpty())
761                 transactionHandler.commit(transaction);
762             else
763             {
764                 this.finalizeTransaction(true);
765                 this.updateDirtyState();
766             }
767             
768 //            if(!transaction.isEmpty())       
769 //            	transaction.commit();
770 //            this.finalizeTransaction(true);
771         }
772         catch(DataServiceException e)
773         {
774             this.finalizeTransaction(false);
775             Log.printError(this, "Store transaction failed in proxy controller "+this);
776             throw e;
777         }
778         
779 //        this.updateDirtyState();
780     }
781     
782     public void loadControlledProxiesCallback(int status)
783     {
784         if (status == AsyncTransactionHandler.COMMIT_SUCCEEDED)
785             this.finalizeTransaction(true);
786         else
787             this.finalizeTransaction(false);
788             
789         this.updateDirtyState();
790     }
791     
792     protected void storeControlledProxies() throws DataServiceException
793     {
794         if(this.getSourceEntity()==null)
795             return;
796         
797         IDataTransaction transaction = this.getDataSource().getDataService().newTransaction();
798         if(this.getSourceEntity().isDirty() || !this.getSourceEntity().isPersistent())
799             transaction.addStore(this.getSourceEntity());
800         
801         // Prepare transaction with controlled proxies actions.
802         try
803         {
804             this.prepareControlledStore(transaction);
805             transaction.commit();
806             this.finalizeTransaction(true);
807         }
808         catch(DataServiceException e)
809         {
810             this.finalizeTransaction(false);
811             Log.printError(this, "Store transaction failed in proxy controller "+this);
812             throw e;
813         }
814         
815         this.updateDirtyState();
816     }
817     
818     /*** Fires an EntityEvent with the provided operation type to all registered
819      * IEntityListener objects.
820      */
821     protected void fireOpPerformedEvent(int opType)
822     {
823         if(mEntityListener == null)
824             return;
825         else if(opType == EntityEvent.STORED)
826             mEntityListener.storePerformed(new EntityEvent(this, opType));
827         else if(opType == EntityEvent.DELETED)
828             mEntityListener.deletePerformed(new EntityEvent(this, opType));
829         else if(opType == EntityEvent.REFRESHED)
830             mEntityListener.refreshPerformed(new EntityEvent(this, opType));
831     }
832     
833     /*** Fires an EntityChangeEvent specifying a data field change to all 
834      * registered IEntityChangeListener objects.
835      */
836     protected void fireDataChangedEvent(IFieldDescriptor fieldDescriptor, Object oldValue, Object newValue)
837     {
838         if(mEntityChangeListener!=null)
839             mEntityChangeListener.dataChanged(new EntityChangeEvent(this, fieldDescriptor, oldValue, newValue));
840     }
841     
842     /*** Fires an EntityChangeEvent specifying a status change to all registered
843      * IEntityChangeListener objects.
844      */
845     protected void fireStatusChangedEvent(int statusType, boolean newStatus)
846     {
847         if(mEntityChangeListener!=null)
848             mEntityChangeListener.statusChanged(new EntityChangeEvent(this, statusType, newStatus));
849     }
850     
851     /*** Fires a ProxyEvent with the provided operation type to all registered
852      * IProxyListener objects.
853      */
854     protected void fireProxyEvent(int eventType)
855     {
856         if(mProxyListener==null)
857             return;
858         else if(eventType == ProxyEvent.CHANGED)
859             mProxyListener.remoteChanged(new ProxyEvent(this, eventType));
860         else if(eventType == ProxyEvent.EXPANDED)
861             mProxyListener.remoteExpanded(new ProxyEvent(this, eventType));
862     }
863     
864     /*** Help method that returns the selections relay listener.
865      * Can be overriden to customize the listener. Note that the method is
866      * contracted to allways return the same listener instance for a given 
867      * ProxyEntity instance.
868      */
869     protected RelayListener getRelayListener()
870     {
871         if(mRelayListener==null)
872             mRelayListener = new RelayListener();
873         return mRelayListener;
874     }
875     
876     /*** Help method that returns the selections slave change listener.
877      * Can be overriden to customize the listener. Note that the method is
878      * contracted to allways return the same listener instance for a given 
879      * ProxyEntity instance.
880      */
881     protected ControlledProxyListener getControlledProxyListener()
882     {
883         if(mControlledProxyListener==null)
884             mControlledProxyListener = new ControlledProxyListener();
885         return mControlledProxyListener;
886     }
887     
888     protected void updateDirtyState()
889     {
890         boolean hasDirtySlaves = this.hasDirtyControlledProxies();
891         boolean hasDirtyRemote = mRemoteEntity!=null ? mRemoteEntity.isDirty() : false;
892         if(mHasDirtyControlledProxies!=hasDirtySlaves && !hasDirtyRemote)
893             this.fireStatusChangedEvent(IEntity.DIRTY, hasDirtySlaves || hasDirtyRemote);
894         mHasDirtyControlledProxies = hasDirtySlaves;
895     }
896 
897     // Nested classes ----------------------------------------------------------
898     
899     private class SlaveProxyItem
900     {
901         // Data members --------------------------------------------------------
902         private IControlledProxy mControlledProxy;
903         private int mProxyUserCount;
904         
905         // Constructors --------------------------------------------------------
906         
907         public SlaveProxyItem(IControlledProxy proxy)
908         {
909             mControlledProxy = proxy;
910             mProxyUserCount = 1;
911         } 
912         
913         // Access methods ------------------------------------------------------
914         
915         public IControlledProxy getControlledProxy()
916         {
917             return mControlledProxy;
918         }
919         
920         public void addProxyUsers()
921         {
922             mProxyUserCount++;
923         }
924         
925         public void decProxyUsers()
926         {
927             mProxyUserCount--;
928         }
929         
930         public boolean hasProxyUsers()
931         {
932             return mProxyUserCount>0;
933         }
934     }
935 
936     private class RelayListener implements IEntityListener, IEntityChangeListener, IProxyListener, java.io.Serializable
937     {
938         // IEntityListener implemntation ---------------------------------------
939         public void storePerformed(EntityEvent event)
940         {
941             if(mEntityListener!=null)
942                 mEntityListener.storePerformed(event);
943         }
944         
945         public void deletePerformed(EntityEvent event)
946         {
947             if(mEntityListener!=null)
948                 mEntityListener.deletePerformed(event);
949         }
950         
951         public void refreshPerformed(EntityEvent event)
952         {
953             if(mEntityListener!=null)
954                 mEntityListener.refreshPerformed(event);
955         }
956         
957         // IEntityChangeListener implemntation ---------------------------------
958         public void dataChanged(EntityChangeEvent event)
959         {
960             if(mEntityChangeListener!=null)
961                 mEntityChangeListener.dataChanged(event);
962         }
963         
964         public void statusChanged(EntityChangeEvent event)
965         {
966             if(mEntityChangeListener!=null)
967                 mEntityChangeListener.statusChanged(event);
968         }        
969         
970         // IProxyListener implementation ---------------------------------------
971         public void remoteChanged(ProxyEvent event)
972         {
973             doOnRemoteChange();
974             
975             if(mProxyListener!=null)
976                 mProxyListener.remoteChanged(event);
977         }
978 
979         public void remoteExpanded(ProxyEvent event)
980         {
981             if(mProxyListener!=null)
982                 mProxyListener.remoteExpanded(event);
983         }
984     }
985     
986     protected class ControlledProxyListener implements IEntityChangeListener, ISelectionListener
987     {
988         // IEntityChangeListener implementation --------------------------------
989         public void dataChanged(EntityChangeEvent event)
990         {
991         }
992 
993         public void statusChanged(EntityChangeEvent event)
994         {
995             updateDirtyState();
996         }
997         
998         // ISelectionListener implementation -----------------------------------
999         public void contentsChanged(SelectionEvent event)
1000         {
1001         }
1002         
1003         public void entityAdded(SelectionEvent event)
1004         {
1005             updateDirtyState();
1006         }
1007         
1008         public void entityRemoved(SelectionEvent event)
1009         {
1010             updateDirtyState();
1011         }        
1012     }
1013 }