/**
* Copyright 2006 Webmedia Group Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
package org.araneaframework.core;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.collections.map.LinkedMap;
import org.apache.log4j.Logger;
import org.araneaframework.Component;
import org.araneaframework.Composite;
import org.araneaframework.Environment;
import org.araneaframework.Message;
import org.araneaframework.Relocatable;
/**
* The base class for all Aranea components. Base entities do not make a Composite pattern
* and only provide some very basic services.
*
* @author "Toomas Römer" <toomas@webmedia.ee>
*/
public abstract class BaseComponent implements Component {
//*******************************************************************
// FIELDS
//*******************************************************************
private static final Logger log = Logger.getLogger(BaseComponent.class);
private Environment environment;
private Map children = Collections.synchronizedMap(new LinkedMap());
private Map disabledChildren = Collections.synchronizedMap(new LinkedMap());
private boolean wasInited = false;
private transient int callCount = 0;
private transient int reentrantCallCount = 0;
private transient ThreadLocal reentrantTLS;
private Collection destroyers = new ArrayList();
//*******************************************************************
// PUBLIC METHODS
//*******************************************************************
public Component.Interface _getComponent() {
return new ComponentImpl();
}
//*******************************************************************
// PROTECTED METHODS
//*******************************************************************
/**
* Init callback. Gets called when the component is initilized.
*/
protected void init() throws Exception {
}
/**
* Destroy callback. Gets called when the component is destroyed.
*/
protected void destroy() throws Exception {
}
protected void disable() throws Exception {
}
protected void enable() throws Exception {
}
protected void propagate(Message message) throws Exception {
}
protected void handleException(Exception e) throws Exception {
throw e;
}
/**
* Returns the Environment of this BaseComponent.
*/
protected Environment getEnvironment() {
return environment;
}
/**
* Returns true, if the BaseComponent has been initialized.
*/
protected boolean isInitialized() {
return wasInited;
}
public void addDestroyer(Standard.Destroyer destroyer) {
destroyers.add(destroyer);
}
/**
* Sets the environment of this BaseComponent to environment.
*/
protected void _setEnvironment(Environment env) {
this.environment = env;
}
/**
* Waits until no call is made to the component. Used to synchronize calls
* to the component.
*/
protected synchronized void _waitNoCall() throws InterruptedException {
long waitStart = System.currentTimeMillis();
while (callCount != reentrantCallCount) {
this.wait(1000);
if (callCount != reentrantCallCount) {
log.warn("Deadlock or starvation suspected, call count=" + callCount + ", reentrant call coint=" + reentrantCallCount);
if (waitStart < System.currentTimeMillis() - 10000) {
//XXX can't throw this in production...
throw new AraneaRuntimeException("Deadlock or starvation suspected!");
//return;
}
}
}
}
/**
* Checks if a call is valid (component is in the required state), increments the call count.
*/
protected synchronized void _startCall() throws IllegalStateException {
_checkCall();
incCallCount();
}
/**
* Decrements the call count. Wakes up all threads that are waiting on
* this object's monitor
*/
protected synchronized void _endCall() {
decCallCount();
this.notifyAll();
}
/**
* Used for starting a call that is a re-entrant call. Meaning a call of this component
* is doing the calling
*/
protected synchronized void _startWaitingCall() {
if (getReentrantCount() >= 1)
reentrantCallCount++;
}
/**
* Decrements internal callcount counter.
*/
protected synchronized void _endWaitingCall() {
if (getReentrantCount() >= 1)
reentrantCallCount--;
}
/**
* Checks if this component is initialized. If not, throws IllegalStateException.
*/
protected void _checkCall() throws IllegalStateException {
if (!wasInited) {
throw new IllegalStateException("Component has not been initialized!");
}
}
/**
* Returns the children of this component.
*/
protected Map _getChildren() {
return children;
}
/**
* Adds a child component to this component with the key and initilizes it with the
* specified Environment env.
*/
protected void _addComponent(Object key, Component component, Environment env) throws Exception {
_checkCall();
// cannot add a child with key that clashes with a disabled child's key
if (children.containsKey(key)) {
if (disabledChildren.containsKey(key))
_enableComponent(key);
_removeComponent(key);
}
// Sequence is very important as by the time of init
// component should be in place since during init the
// component might be overridden.
children.put(key, component);
component._getComponent().init(env);
}
/**
* Removes the childcomponent with the specified key from the children and calls destroy on it.
*/
protected void _removeComponent(Object key) throws Exception {
_checkCall();
Component comp = (Component)children.get(key);
if (comp == null) {
return;
}
//Sequence is very important, and guarantees that there won't
//be a second destroy call if the first one doesn't succeed.
children.remove(key);
comp._getComponent().destroy();
}
/**
* Disables the child component with the specified key. A disabled child is a child that is removed
* from the standard set of children
*/
protected void _disableComponent(Object key) throws Exception {
_checkCall();
if (!children.containsKey(key)) {
throw new NoSuchComponentException(key);
}
((Component) children.get(key))._getComponent().disable();
disabledChildren.put(key, children.remove(key));
}
/**
* Enables a disabled child component with the specified key.
* @param key
* @throws Exception
*/
protected void _enableComponent(Object key) throws Exception {
_checkCall();
if (!disabledChildren.containsKey(key)) {
throw new NoSuchComponentException(key);
}
children.put(key, disabledChildren.remove(key));
((Component) children.get(key))._getComponent().enable();
}
/**
* Relocates parent's child with keyFrom to this BaseComponent with a new key keyTo. The child
* will get the Environment specified by newEnv.
* @param parent is the current parent of the child to be relocated.
* @param newEnv the new Environment of the child.
* @param keyFrom is the key of the child to be relocated.
* @param keyTo is the the key, with which the child will be added to this StandardService.
*/
protected void _relocateComponent(Composite parent, Environment newEnv, Object keyFrom, Object keyTo) throws Exception {
if (!(parent._getComposite().getChildren().get(keyFrom) instanceof Relocatable)) {
throw new AraneaRuntimeException("Child "+keyFrom+" not an instance of Relocatable");
}
Relocatable comp = (Relocatable) parent._getComposite().detach(keyFrom);
comp._getRelocatable().overrideEnvironment(newEnv);
children.put(keyTo, comp);
}
protected void _propagate(Message message) throws Exception {
Iterator ite = (new HashMap(_getChildren())).keySet().iterator();
while(ite.hasNext()) {
Object key = ite.next();
Component component = (Component)_getChildren().get(key);
if (component == null ) {
throw new NoSuchComponentException(key);
}
message.send(key, component);
}
}
//*******************************************************************
// PRIVATE METHODS
//*******************************************************************
private void incCallCount() {
if (reentrantTLS == null)
reentrantTLS = new ThreadLocal();
if (reentrantTLS.get() == null)
reentrantTLS.set(new Counter(0));
callCount++;
((Counter) reentrantTLS.get()).counter++;
if (getReentrantCount() > 1)
reentrantCallCount++;
}
private void decCallCount() {
if (getReentrantCount() > 1)
reentrantCallCount--;
callCount--;
((Counter) reentrantTLS.get()).counter--;
}
private int getReentrantCount() {
return (reentrantTLS == null || reentrantTLS.get() == null) ? 0 : ((Counter) reentrantTLS.get()).counter;
}
//*******************************************************************
// PROTECTED CLASSES
//*******************************************************************
//component implementation
protected class ComponentImpl implements Component.Interface {
public synchronized void init(Environment env) throws Exception {
if (env == null) {
throw new AraneaRuntimeException("Environment cannot be null");
}
if (wasInited) {
throw new AraneaRuntimeException("Cannot initialize a component more than once");
}
BaseComponent.this._setEnvironment(env);
wasInited = true;
BaseComponent.this.init();
}
public void destroy() throws Exception {
_startWaitingCall();
try {
/* XXX synch logic a bit weird.
* Second call to destroy should fail, not wait. */
_waitNoCall();
synchronized (this) {
for (Iterator i = new HashMap(_getChildren()).keySet().iterator(); i.hasNext(); ) {
_removeComponent(i.next());
}
for (Iterator i = new HashMap(disabledChildren).keySet().iterator(); i.hasNext(); ) {
Component comp = (Component) disabledChildren.get(i.next());
if (comp != null) {
comp._getComponent().destroy();
}
}
for (Iterator i = new ArrayList(destroyers).iterator(); i.hasNext(); ) {
Standard.Destroyer dest = (Standard.Destroyer) i.next();
dest.destroy();
}
BaseComponent.this.destroy();
}
wasInited = false;
}
finally {
_endWaitingCall();
}
}
public void propagate(Message message) throws Exception {
_startCall();
try {
BaseComponent.this.propagate(message);
}
finally {
_endCall();
}
}
public void enable() throws Exception {
_startCall();
try {
BaseComponent.this.enable();
}
finally {
_endCall();
}
}
public void disable() throws Exception {
_startCall();
try {
BaseComponent.this.disable();
}
finally {
_endCall();
}
}
}
//*******************************************************************
// PRIVATE CLASSES
//*******************************************************************
private static class Counter {
/**
* The value of the counter
*/
public int counter;
/**
* Initializes the counter with a value.
* @param counter value of the counter
*/
public Counter(int counter) {
this.counter = counter;
}
}
}