View Javadoc

1   /*
2      Copyright 2007 Ramon Servadei
3   
4      Licensed under the Apache License, Version 2.0 (the "License");
5      you may not use this file except in compliance with the License.
6      You may obtain a copy of the License at
7   
8          http://www.apache.org/licenses/LICENSE-2.0
9   
10     Unless required by applicable law or agreed to in writing, software
11     distributed under the License is distributed on an "AS IS" BASIS,
12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13     See the License for the specific language governing permissions and
14     limitations under the License.
15   */
16  package fulmine.model.component;
17  
18  import static fulmine.util.Utils.EMPTY_STRING;
19  import static fulmine.util.Utils.logException;
20  import static fulmine.util.Utils.safeToString;
21  import static fulmine.util.Utils.string;
22  
23  import java.util.Collections;
24  import java.util.List;
25  
26  import fulmine.AbstractLifeCycle;
27  import fulmine.Addressable;
28  import fulmine.IAddressable;
29  import fulmine.IDomain;
30  import fulmine.IType;
31  import fulmine.context.IFrameworkContext;
32  import fulmine.event.IEvent;
33  import fulmine.event.IEventFrameExecution;
34  import fulmine.event.IEventSource;
35  import fulmine.event.listener.IEventListener;
36  import fulmine.event.listener.IPriorityEventListener;
37  import fulmine.event.system.ISystemEvent;
38  import fulmine.event.system.ISystemEventListener;
39  import fulmine.protocol.wire.IWireIdentity;
40  import fulmine.protocol.wire.IWireState;
41  import fulmine.protocol.wire.operation.IOperationScope;
42  import fulmine.util.array.ArrayUtils;
43  import fulmine.util.collection.ReferenceCountingList;
44  import fulmine.util.log.AsyncLog;
45  
46  /**
47   * A base component class that implements many common functions. Each component
48   * has an identity that is provided during construction. A component is not
49   * explicitly related with a {@link IFrameworkContext context}.
50   * <p>
51   * The abstract component implements the {@link IAddressable} interface. This is
52   * an implementation detail and provides the context with the unique identity of
53   * this component as an entity.
54   * <p>
55   * The {@link #addListener(IEventListener)} and
56   * {@link #removeListener(IEventListener)} methods use copy-on-write for the
57   * collection of listeners.
58   * 
59   * @author Ramon Servadei
60   */
61  public abstract class AbstractComponent extends AbstractLifeCycle implements
62      IComponent, Cloneable
63  {
64      private final static AsyncLog LOG = new AsyncLog(AbstractComponent.class);
65  
66      /** The identity for this */
67      private final IAddressable id;
68  
69      /** The event source group id */
70      private final byte eventGroupId;
71  
72      /** flag to identify if this component is a clone: 0=original, 1=clone */
73      private volatile byte isClone;
74  
75      /** Holds the frame that caused this component to change. */
76      private IEventFrameExecution drivingFrame;
77  
78      /** The frame in which the change for this component occurs. */
79      private IEventFrameExecution currentFrame;
80  
81      /**
82       * The listeners registered against this source. Uses the <a
83       * href="http://www.ibm.com/developerworks/java/library/j-jtp06197.html"
84       * >'cheap read-write lock'</a>
85       */
86      private volatile List<IEventListener> listeners;
87  
88      /**
89       * The source of events generated by this component; effectively the
90       * component itself is the source. If this is a cloned component, the source
91       * is the <i>original</i> instance.
92       */
93      private IEventSource source = this;
94  
95      /**
96       * Construct the component with an identity and type. This assigns a 0 event
97       * group id
98       * 
99       * @param identity
100      *            the identity of the component
101      * @param type
102      *            the type of the component
103      * @param domain
104      *            the domain of the component
105      */
106     protected AbstractComponent(String identity, IType type, IDomain domain)
107     {
108         // by default, just use the 0'th processor
109         this(identity, type, domain, (byte) 0);
110     }
111 
112     /**
113      * Construct the component with an identity, type and event group id.
114      * 
115      * @param identity
116      *            the identity of the component
117      * @param type
118      *            the type of the component
119      * @param domain
120      *            the domain of the component
121      * @param eventGroupId
122      *            the event group id
123      */
124     protected AbstractComponent(String identity, IType type, IDomain domain,
125         byte eventGroupId)
126     {
127         super();
128         this.id = new Addressable(identity, type, domain);
129         /*
130          * Because regular expressions are used for subscription handling, we
131          * must prevent '**' in the identity.
132          */
133         if (identity.indexOf("**") > -1)
134         {
135             throw new RuntimeException(
136                 "Containers cannot have the following characters '**', attempted identity "
137                     + identity + " for " + type + ", " + domain);
138         }
139         this.eventGroupId = eventGroupId;
140     }
141 
142     public final byte getEventSourceGroupId()
143     {
144         return this.eventGroupId;
145     }
146 
147     public final String getIdentity()
148     {
149         return this.getAddressable().getIdentity();
150     }
151 
152     public final IType getType()
153     {
154         return this.getAddressable().getType();
155     }
156 
157     public final IDomain getDomain()
158     {
159         return this.getAddressable().getDomain();
160     }
161 
162     public final String getAddress()
163     {
164         return this.getAddressable().getAddress();
165     }
166 
167     public final IEventSource getSource()
168     {
169         return this.source;
170     }
171 
172     public final IEventFrameExecution getDrivingFrame()
173     {
174         return this.drivingFrame;
175     }
176 
177     public final IEventFrameExecution getFrame()
178     {
179         return this.currentFrame;
180     }
181 
182     /**
183      * Set the event frame identifier for this cloneable component. This method
184      * is useful to set the frame for an event so that it matches up with the
185      * event frame of an event that generated this event. This allows an
186      * {@link IEventListener} to process these events as a single work unit even
187      * though they are generated in separate frames.
188      * 
189      * @param frame
190      *            the event frame to associate this {@link IEvent} with.
191      */
192     public final void setFrame(IEventFrameExecution frame)
193     {
194         this.currentFrame = frame;
195     }
196 
197     /**
198      * Set the driving frame that caused the change in this component
199      * 
200      * @param drivingFrame
201      *            the driving frame
202      */
203     protected final void setDrivingFrame(IEventFrameExecution drivingFrame)
204     {
205         this.drivingFrame = drivingFrame;
206     }
207 
208     public final void readState(IOperationScope scope, byte[] dataBuffer,
209         int start, int numberOfBytes)
210     {
211         boolean processed = false;
212         try
213         {
214             if (scope.include(this))
215             {
216                 processed =
217                     doReadState(scope, dataBuffer, start, numberOfBytes);
218                 scope.exiting(this, processed);
219             }
220         }
221         catch (Exception e)
222         {
223             scope.exception(this, e);
224         }
225     }
226 
227     public final void writeState(IOperationScope scope, IWireIdentity wireId,
228         byte[][] headerBuffer, int[] headerBufferPosition, byte[][] dataBuffer,
229         int[] dataBufferPosition, boolean completeState)
230     {
231         boolean processed = false;
232         try
233         {
234             if (scope.include(this))
235             {
236                 processed =
237                     doWriteState(scope, wireId, headerBuffer,
238                         headerBufferPosition, dataBuffer, dataBufferPosition,
239                         completeState);
240                 scope.exiting(this, processed);
241             }
242         }
243         catch (Exception e)
244         {
245             scope.exception(this, e);
246         }
247     }
248 
249     /**
250      * A synchronous notification implementation
251      */
252     public void addEvent(IEvent event)
253     {
254         final List<IEventListener> listeners = getListeners();
255         for (IEventListener eventListener : listeners)
256         {
257             eventListener.update(event);
258         }
259     }
260 
261     public final boolean addListener(IEventListener listener)
262     {
263         if (this instanceof ISystemEvent
264             && !(listener instanceof ISystemEventListener))
265         {
266             throw new IllegalArgumentException(this + " can only have "
267                 + ISystemEventListener.class.getSimpleName()
268                 + " instances added as listeners.");
269         }
270         boolean added = false;
271         List<IEventListener> copy = null;
272         // copy-on-write and ensure this is an atomic unit of work
273         synchronized (this)
274         {
275             if (this.listeners == null)
276             {
277                 copy = new ReferenceCountingList<IEventListener>(0);
278             }
279             else
280             {
281                 copy =
282                     new ReferenceCountingList<IEventListener>(this.listeners);
283             }
284             if (listener instanceof IPriorityEventListener)
285             {
286                 added = !copy.contains(listener);
287                 copy.add(0, listener);
288             }
289             else
290             {
291                 added = copy.add(listener);
292             }
293             this.listeners = copy;
294             if (added)
295             {
296                 doPostAddListener(listener);
297             }
298         }
299         if (added)
300         {
301             try
302             {
303                 listener.addedAsListenerFor(this);
304             }
305             catch (Exception e)
306             {
307                 logException(getLog(), this, e);
308             }
309             if (getLog().isDebugEnabled())
310             {
311                 getLog().debug(
312                     "Added listener " + safeToString(listener) + " to source "
313                         + createIdentityString());
314             }
315         }
316         return added;
317     }
318 
319     public final List<IEventListener> getListeners()
320     {
321         final List<IEventListener> local = this.listeners;
322         if (local == null)
323         {
324             return Collections.emptyList();
325         }
326         // this is cheap - it just wraps the collection
327         return Collections.unmodifiableList(local);
328     }
329 
330     public final boolean removeListener(IEventListener listener)
331     {
332         /*
333          * If a component is not active, don't remove the listener; there may be
334          * some events generated by this component during shutdown that should
335          * be received by the listener. In any case, when this object is
336          * released and GC'd, the collection of listeners will also be GC'd.
337          */
338         if (!isActive())
339         {
340             return false;
341         }
342         List<IEventListener> copy = null;
343         boolean removed = false;
344         // copy-on-write and ensure this is an atomic unit of work
345         synchronized (this)
346         {
347             final List<IEventListener> local = this.listeners;
348             if (local == null)
349             {
350                 return false;
351             }
352             copy = new ReferenceCountingList<IEventListener>(local);
353             removed = copy.remove(listener);
354             if (copy.size() == 0)
355             {
356                 this.listeners = null;
357             }
358             else
359             {
360                 this.listeners = copy;
361             }
362             if (removed)
363             {
364                 doPostRemoveListener(listener);
365             }
366         }
367         if (removed)
368         {
369             try
370             {
371                 listener.removedAsListenerFrom(this);
372             }
373             catch (Exception e)
374             {
375                 logException(getLog(), this, e);
376             }
377             if (getLog().isDebugEnabled())
378             {
379                 getLog().debug(
380                     "Removed listener " + safeToString(listener)
381                         + " from source " + createIdentityString());
382             }
383         }
384         return removed;
385     }
386 
387     public final List<IEventListener> removeListeners()
388     {
389         synchronized (this)
390         {
391             final List<IEventListener> local = this.listeners;
392             if (local == null)
393             {
394                 return Collections.emptyList();
395             }
396             // we need a copy because we're iterating and possibly removing
397             final List<IEventListener> copy =
398                 new ReferenceCountingList<IEventListener>(local);
399             for (IEventListener eventListener : copy)
400             {
401                 try
402                 {
403                     removeListener(eventListener);
404                 }
405                 catch (Exception e)
406                 {
407                     logException(getLog(), eventListener, e);
408                 }
409             }
410             return copy;
411         }
412     }
413 
414     /**
415      * Hook to allow sub-classes to perform any post processing after an
416      * {@link IEventListener} has been added via
417      * {@link #addListener(IEventListener)}
418      * 
419      * @param listener
420      *            the listener that has been added
421      */
422     protected void doPostAddListener(IEventListener listener)
423     {
424         // noop
425     }
426 
427     /**
428      * Hook to allow sub-classes to perform any post processing after an
429      * {@link IEventListener} has been removed via
430      * {@link #removeListener(IEventListener)}
431      * 
432      * @param listener
433      *            the listener that has been removed
434      */
435     protected void doPostRemoveListener(IEventListener listener)
436     {
437         // noop
438     }
439 
440     @Override
441     protected void doStart()
442     {
443         // noop
444     }
445 
446     @Override
447     protected final void doDestroy()
448     {
449         if (!isClone())
450         {
451             doComponentDestroy();
452         }
453     }
454 
455     /**
456      * The destroy method that is invoked on a non-cloned version of this.
457      */
458     protected void doComponentDestroy()
459     {
460         this.currentFrame = null;
461         this.drivingFrame = null;
462         // don't remove listeners otherwise we don't get any destroy events...
463         // leave identity and sysId
464     }
465 
466     /**
467      * Check if this instance is a clone.
468      * 
469      * @throws IllegalStateException
470      *             if this is a cloned instance.
471      */
472     protected final void checkClone()
473     {
474         if (isClone())
475         {
476             throw new IllegalStateException(
477                 "This is a clone and cannot be altered");
478         }
479     }
480 
481     /**
482      * Determine if this instance is the original or a clone
483      * 
484      * @return <code>true</code> if this is a clone of the original
485      */
486     public final boolean isClone()
487     {
488         return this.isClone == 1;
489     }
490 
491     /**
492      * Utility method to create the system identity string for a component.
493      * 
494      * @return the system identity string
495      */
496     protected final String createIdentityString()
497     {
498         return string(this, getAddress());
499     }
500 
501     /**
502      * Helper method to return the component's identity and whether it is a
503      * clone or not
504      * 
505      * @return a string with the component's identity and whether its a clone or
506      *         not
507      */
508     protected StringBuilder getIdentityString()
509     {
510         StringBuilder sb = new StringBuilder();
511         sb.append((isClone == 1 ? "[clone]" : EMPTY_STRING)).append(
512             getIdentity());
513         return sb;
514     }
515 
516     /**
517      * Write the component state in its wire form to the buffer. <strong>Any
518      * nested fields must be written by calling their
519      * {@link #writeState(IOperationScope, IWireIdentity, byte[][], int[], byte[][], int[], boolean)}
520      * method.</strong>
521      * 
522      * @see IWireState#writeState(IOperationScope, IWireIdentity, byte[][],
523      *      int[], byte[][], int[], boolean)
524      * @see ArrayUtils for a discussion of the byte[][] usage
525      * @return true if the write operation succeeded
526      * @throws Exception
527      */
528     protected abstract boolean doWriteState(IOperationScope scope,
529         IWireIdentity wireId, byte[][] headerBuffer,
530         int[] headerBufferPosition, byte[][] dataBuffer,
531         int[] dataBufferPosition, boolean completeState) throws Exception;
532 
533     /**
534      * Update the component state from the wire format state in the buffer.
535      * <strong>Any nested fields must be read using their
536      * {@link #readState(IOperationScope, byte[], int, int)} method.</strong>
537      * 
538      * @see IWireState#readState(IOperationScope, byte[], int, int)
539      * @return true if the read operation succeded.
540      * @throws Exception
541      */
542     protected abstract boolean doReadState(IOperationScope scope,
543         byte[] buffer, int start, int numberOfBytes) throws Exception;
544 
545     public String toDetailedString()
546     {
547         return createIdentityString();
548     }
549 
550     public String toIdentityString()
551     {
552         return createIdentityString();
553     }
554 
555     public IEvent getTriggerEvent()
556     {
557         // noop
558         return null;
559     }
560 
561     public void setTriggerEvent(IEvent triggerEvent)
562     {
563         // noop
564     }
565 
566     @Override
567     protected AsyncLog getLog()
568     {
569         return LOG;
570     }
571 
572     public final IAddressable getAddressable()
573     {
574         return id;
575     }
576 
577     @Override
578     public String toString()
579     {
580         return createIdentityString();
581     }
582 
583     @Override
584     public Object clone() throws CloneNotSupportedException
585     {
586         final AbstractComponent clone = (AbstractComponent) super.clone();
587         clone.isClone = 1;
588         clone.source = this;
589         return clone;
590     }
591 
592     @Override
593     public int hashCode()
594     {
595         return this.getAddressable().hashCode();
596     }
597 
598     @Override
599     public boolean equals(Object obj)
600     {
601         return this.getAddressable().equals(obj);
602     }
603 }