001: /**
002: * Copyright 2006 Webmedia Group Ltd.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: **/package org.araneaframework.core;
016:
017: import java.util.Collections;
018: import java.util.HashMap;
019: import java.util.Iterator;
020: import java.util.Map;
021: import org.apache.commons.collections.map.LinkedMap;
022: import org.apache.commons.logging.Log;
023: import org.apache.commons.logging.LogFactory;
024: import org.araneaframework.Component;
025: import org.araneaframework.Composite;
026: import org.araneaframework.Environment;
027: import org.araneaframework.Message;
028: import org.araneaframework.Relocatable;
029: import org.araneaframework.Scope;
030: import org.araneaframework.core.util.ExceptionUtil;
031:
032: /**
033: * The base class for all Aranea components. Base entities do not make a Composite pattern
034: * and only provide some very basic services (mainly syncronization and messaging service)
035: *
036: * @author "Toomas Römer" <toomas@webmedia.ee>
037: * @author Jevgeni Kabanov (ekabanov <i>at</i> araneaframework <i>dot</i> org)
038: */
039: public abstract class BaseComponent implements Component {
040: //*******************************************************************
041: // FIELDS
042: //*******************************************************************
043: private static final Log log = LogFactory
044: .getLog(BaseComponent.class);
045:
046: private Environment environment;
047: private Scope scope;
048: private Map children;
049: private Map disabledChildren;
050:
051: private static final byte UNBORN = 1;
052: private static final byte ALIVE = 2;
053: private static final byte DEAD = 3;
054:
055: private byte state = UNBORN;
056:
057: private transient int callCount = 0;
058: private transient int reentrantCallCount = 0;
059: private transient ThreadLocal reentrantTLS;
060:
061: //*******************************************************************
062: // PUBLIC METHODS
063: //*******************************************************************
064: public Component.Interface _getComponent() {
065: return new ComponentImpl();
066: }
067:
068: //*******************************************************************
069: // PROTECTED METHODS
070: //*******************************************************************
071: /**
072: * Init callback. Gets called when the component is initilized.
073: */
074: protected void init() throws Exception {
075: }
076:
077: /**
078: * Destroy callback. Gets called when the component is destroyed.
079: */
080: protected void destroy() throws Exception {
081: }
082:
083: protected void disable() throws Exception {
084: }
085:
086: protected void enable() throws Exception {
087: }
088:
089: protected void propagate(Message message) throws Exception {
090: }
091:
092: protected void handleException(Exception e) throws Exception {
093: throw e;
094: }
095:
096: /**
097: * Returns the Environment of this BaseComponent.
098: */
099: public Environment getEnvironment() {
100: return environment;
101: }
102:
103: public Scope getScope() {
104: return scope;
105: }
106:
107: /**
108: * Returns true, if the BaseComponent has been initialized.
109: */
110: public boolean isInitialized() {
111: return state != UNBORN;
112: }
113:
114: public boolean isAlive() {
115: return state == ALIVE;
116: }
117:
118: public boolean isDead() {
119: return state == DEAD;
120: }
121:
122: /**
123: * Sets the environment of this BaseComponent to environment.
124: */
125: protected synchronized void _setEnvironment(Environment env) {
126: this .environment = env;
127: }
128:
129: /**
130: * Sets the environment of this BaseComponent to environment.
131: *
132: * @since 1.1
133: */
134: protected void _setScope(Scope scope) {
135: this .scope = scope;
136: }
137:
138: /**
139: * Waits until no call is made to the component. Used to synchronize calls
140: * to the component.
141: */
142: protected synchronized void _waitNoCall()
143: throws InterruptedException {
144: long waitStart = System.currentTimeMillis();
145:
146: while (callCount != reentrantCallCount) {
147: this .wait(1000);
148:
149: if (callCount != reentrantCallCount) {
150: log
151: .warn("Deadlock or starvation suspected, call count '"
152: + callCount
153: + "', reentrant call count '"
154: + reentrantCallCount + "'");
155:
156: if (waitStart < System.currentTimeMillis() - 10000) {
157: log
158: .error(
159: "Deadlock or starvation not solved in 10s, call count '"
160: + callCount
161: + "', reentrant call count '"
162: + reentrantCallCount + "'",
163: new AraneaRuntimeException(
164: "Deadlock or starvation suspected!"));
165: return;
166: }
167: }
168: }
169: }
170:
171: /**
172: * Checks if a call is valid (component is in the required state), increments the call count.
173: */
174: protected synchronized void _startCall()
175: throws IllegalStateException {
176: _checkCall();
177: incCallCount();
178: }
179:
180: /**
181: * @since 1.1
182: */
183: protected synchronized void _strictStartCall()
184: throws IllegalStateException {
185: _strictCheckCall();
186: incCallCount();
187: }
188:
189: /**
190: * Decrements the call count. Wakes up all threads that are waiting on
191: * this object's monitor
192: */
193: protected synchronized void _endCall() {
194: decCallCount();
195: this .notifyAll();
196: }
197:
198: /**
199: * Used for starting a call that is a re-entrant call. Meaning a call of this component
200: * is doing the calling
201: */
202: protected synchronized void _startWaitingCall() {
203: if (getReentrantCount() >= 1)
204: reentrantCallCount++;
205: }
206:
207: /**
208: * Decrements internal callcount counter.
209: */
210: protected synchronized void _endWaitingCall() {
211: if (getReentrantCount() >= 1)
212: reentrantCallCount--;
213: }
214:
215: /**
216: * Checks if this component was initialized. If not, throws IllegalStateException.
217: * This is relatively loose check, allowing leftover calls to dead components.
218: * @throws IllegalStateException when component has never been initialized
219: */
220: protected void _checkCall() throws IllegalStateException {
221: if (!isInitialized()) {
222: throw new IllegalStateException("Component '"
223: + getClass().getName() + "' was never initialized!");
224: }
225: }
226:
227: /**
228: * Checks if this component is currently alive.
229: * This is strict check that disallows leftover calls to dead components.
230: * @throws IllegalStateException when component is unborn or dead
231: * @since 1.1
232: */
233: protected void _strictCheckCall() throws IllegalStateException {
234: if (!isAlive()) {
235: throw new IllegalStateException("Component '"
236: + getClass().getName() + "' is not alive!");
237: }
238: }
239:
240: /**
241: * Returns the children of this component.
242: */
243: protected Map _getChildren() {
244: synchronized (this ) {
245: if (children == null)
246: children = Collections
247: .synchronizedMap(new LinkedMap(1));
248: }
249: return children;
250: }
251:
252: /**
253: * Returns the children of this component.
254: */
255: protected Map _getDisabledChildren() {
256: synchronized (this ) {
257: if (disabledChildren == null)
258: disabledChildren = Collections
259: .synchronizedMap(new LinkedMap(1));
260: }
261: return disabledChildren;
262: }
263:
264: /**
265: * Adds a child component to this component with the key and initilizes it with the
266: * specified Environment env.
267: */
268: protected void _addComponent(Object key, Component component,
269: Environment env) {
270: _addComponent(key, component,
271: new StandardScope(key, getScope()), env);
272: }
273:
274: /**
275: * Adds a child component to this component with the key and initilizes it with the
276: * specified Environment env.
277: *
278: * @since 1.1
279: */
280: protected void _addComponent(Object key, Component component,
281: Scope scope, Environment env) {
282: Assert.notNull(this , key, "Cannot add a component of class '"
283: + (component == null ? null : component.getClass())
284: + "' under a null key!");
285: //Only Strings are supported by this implementation
286: Assert.isInstanceOfParam(String.class, key, "key");
287:
288: _checkCall();
289:
290: // cannot add a child with key that clashes with a disabled child's key
291: if (_getChildren().containsKey(key)) {
292: if (_getDisabledChildren().containsKey(key))
293: _enableComponent(key);
294: _removeComponent(key);
295: }
296:
297: // Sequence is very important as by the time of init
298: // component should be in place since during init the
299: // component might be overridden.
300: _getChildren().put(key, component);
301: component._getComponent().init(scope, env);
302: }
303:
304: /**
305: * Removes the childcomponent with the specified key from the children and calls destroy on it.
306: */
307: protected void _removeComponent(Object key) {
308: Assert.notNullParam(this , key, "key");
309: //Only Strings are supported by this implementation
310: Assert.isInstanceOfParam(String.class, key, "key");
311:
312: _checkCall();
313:
314: Component comp = (Component) _getChildren().get(key);
315:
316: if (comp == null) {
317: return;
318: }
319:
320: //Sequence is very important, and guarantees that there won't
321: //be a second destroy call if the first one doesn't succeed.
322: _getChildren().remove(key);
323: comp._getComponent().destroy();
324: }
325:
326: /**
327: * Disables the child component with the specified key. A disabled child is a child that is removed
328: * from the standard set of children
329: */
330: protected void _disableComponent(Object key) {
331: Assert.notNullParam(this , key, "key");
332: //Only Strings are supported by this implementation
333: Assert.isInstanceOfParam(String.class, key, "key");
334:
335: _checkCall();
336:
337: boolean enabled = _getChildren().containsKey(key);
338: boolean disabled = _getDisabledChildren().containsKey(key);
339:
340: if (!enabled && !disabled) {
341: throw new NoSuchComponentException(key);
342: }
343:
344: if (enabled) {
345: ((Component) _getChildren().get(key))._getComponent()
346: .disable();
347: _getDisabledChildren().put(key, _getChildren().remove(key));
348: }
349: }
350:
351: /**
352: * Enables a disabled child component with the specified key.
353: * @param key
354: */
355: protected void _enableComponent(Object key) {
356: Assert.notNullParam(this , key, "key");
357: //Only Strings are supported by this implementation
358: Assert.isInstanceOfParam(String.class, key, "key");
359:
360: _checkCall();
361:
362: boolean enabled = _getChildren().containsKey(key);
363: boolean disabled = _getDisabledChildren().containsKey(key);
364:
365: if (!disabled && !enabled) {
366: throw new NoSuchComponentException(key);
367: }
368:
369: if (disabled) {
370: _getChildren().put(key, _getDisabledChildren().remove(key));
371: ((Component) _getChildren().get(key))._getComponent()
372: .enable();
373: }
374: }
375:
376: /**
377: * Relocates parent's child with keyFrom to this BaseComponent with a new key keyTo. The child
378: * will get the Environment specified by newEnv.
379: * @param parent is the current parent of the child to be relocated.
380: * @param newEnv the new Environment of the child.
381: * @param keyFrom is the key of the child to be relocated.
382: * @param keyTo is the the key, with which the child will be added to this StandardService.
383: */
384: protected void _relocateComponent(Composite parent,
385: Environment newEnv, Object keyFrom, Object keyTo) {
386: Assert.notNull(this , parent,
387: "Cannot relocate a component from under key '"
388: + keyFrom + "' to key '" + keyTo
389: + "' from a null parent!");
390: Assert.notNull(this , parent._getComposite().getChildren().get(
391: keyFrom),
392: "The component to be relocated must be a child under key '"
393: + keyFrom + "'!");
394: Assert.isTrue(this , parent._getComposite().getChildren().get(
395: keyFrom) instanceof Relocatable,
396: "The component of class '"
397: + parent._getComposite().getChildren().get(
398: keyFrom).getClass()
399: + "' to be relocated from under key '"
400: + keyFrom + "' to key '" + keyTo
401: + "' must implement Relocatable!");
402: //Only Strings are supported by this implementation
403: Assert.isInstanceOfParam(String.class, keyFrom, "keyFrom");
404: Assert.isInstanceOfParam(String.class, keyTo, "keyTo");
405:
406: Relocatable comp = (Relocatable) parent._getComposite().detach(
407: keyFrom);
408: comp._getRelocatable().overrideEnvironment(newEnv);
409:
410: _getChildren().put(keyTo, comp);
411: }
412:
413: protected void _propagate(Message message) {
414: Assert.notNullParam(this , message, "message");
415:
416: if (children == null || isDead())
417: return;
418:
419: Iterator ite = (new LinkedMap(_getChildren())).keySet()
420: .iterator();
421: while (ite.hasNext()) {
422: Object key = ite.next();
423:
424: Component component = (Component) _getChildren().get(key);
425:
426: // message has not destroyed previously existing child
427: if (component != null) {
428: message.send(key, component);
429: }
430: }
431: }
432:
433: //*******************************************************************
434: // PRIVATE METHODS
435: //*******************************************************************
436:
437: private void incCallCount() {
438: if (reentrantTLS == null)
439: reentrantTLS = new ThreadLocal();
440:
441: if (reentrantTLS.get() == null)
442: reentrantTLS.set(new Counter(0));
443:
444: callCount++;
445: ((Counter) reentrantTLS.get()).counter++;
446:
447: if (getReentrantCount() > 1)
448: reentrantCallCount++;
449: }
450:
451: private void decCallCount() {
452: if (getReentrantCount() > 1)
453: reentrantCallCount--;
454:
455: callCount--;
456: ((Counter) reentrantTLS.get()).counter--;
457: }
458:
459: private int getReentrantCount() {
460: return (reentrantTLS == null || reentrantTLS.get() == null) ? 0
461: : ((Counter) reentrantTLS.get()).counter;
462: }
463:
464: //*******************************************************************
465: // PROTECTED CLASSES
466: //*******************************************************************
467: //component implementation
468: protected class ComponentImpl implements Component.Interface {
469:
470: public synchronized void init(Scope scope, Environment env) {
471: Assert.notNull(this , env, "Environment cannot be null!");
472: Assert.isTrue(this , !isInitialized(),
473: "Cannot initialize the component more than once!");
474:
475: BaseComponent.this ._setScope(scope);
476: BaseComponent.this ._setEnvironment(env);
477: state = ALIVE;
478: try {
479: BaseComponent.this .init();
480: } catch (Exception e) {
481: ExceptionUtil.uncheckException(e);
482: }
483: }
484:
485: public void destroy() {
486: _startWaitingCall();
487: _strictCheckCall();
488:
489: try {
490: /* XXX synch logic a bit weird.
491: * Second call to destroy should fail, not wait. */
492: _waitNoCall();
493: synchronized (this ) {
494: if (children != null)
495: for (Iterator i = new HashMap(_getChildren())
496: .keySet().iterator(); i.hasNext();) {
497: _removeComponent(i.next());
498: }
499:
500: if (disabledChildren != null)
501: for (Iterator i = _getDisabledChildren()
502: .keySet().iterator(); i.hasNext();) {
503: Component comp = (Component) _getDisabledChildren()
504: .get(i.next());
505: if (comp != null) {
506: comp._getComponent().destroy();
507: }
508: }
509:
510: BaseComponent.this .destroy();
511: }
512: state = DEAD;
513: } catch (Exception e) {
514: ExceptionUtil.uncheckException(e);
515: } finally {
516: _endWaitingCall();
517: }
518: }
519:
520: public void propagate(Message message) {
521: _startCall();
522: try {
523: BaseComponent.this .propagate(message);
524: } catch (Exception e) {
525: ExceptionUtil.uncheckException(e);
526: } finally {
527: _endCall();
528: }
529: }
530:
531: public void enable() {
532: _startCall();
533: try {
534: BaseComponent.this .enable();
535: } catch (Exception e) {
536: ExceptionUtil.uncheckException(e);
537: } finally {
538: _endCall();
539: }
540: }
541:
542: public void disable() {
543: _startCall();
544: try {
545: BaseComponent.this .disable();
546: } catch (Exception e) {
547: ExceptionUtil.uncheckException(e);
548: } finally {
549: _endCall();
550: }
551: }
552: }
553:
554: //*******************************************************************
555: // PRIVATE CLASSES
556: //*******************************************************************
557: private static class Counter {
558: /**
559: * The value of the counter
560: */
561: public int counter;
562:
563: /**
564: * Initializes the counter with a value.
565: * @param counter value of the counter
566: */
567: public Counter(int counter) {
568: this.counter = counter;
569: }
570: }
571: }
|