/**
* Squidy Interaction Library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* Squidy Interaction Library is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Squidy Interaction Library. If not, see
* <http://www.gnu.org/licenses/>.
*
* 2009 Human-Computer Interaction Group, University of Konstanz.
* <http://hci.uni-konstanz.de>
*
* Please contact info@squidy-lib.de or visit our website
* <http://www.squidy-lib.de> for further information.
*/
package org.squidy.manager.model;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import javax.xml.bind.Unmarshaller;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.squidy.SquidyException;
import org.squidy.manager.ProcessException;
import org.squidy.manager.data.AbstractData;
import org.squidy.manager.data.IData;
import org.squidy.manager.data.IDataContainer;
import org.squidy.manager.data.Processor;
import org.squidy.manager.data.impl.DefaultDataContainer;
/**
* <code>AbstractValve</code>.
*
* <pre>
* Date: Feb 22, 2009
* Time: 11:54:49 PM
* </pre>
*
* @author Roman Rädle<br />
* <a
* href="mailto:Roman.Raedle@uni-konstanz.de">Roman.Raedle@uni-konstanz
* .de</a><br />
* Human-Computer Interaction Group<br />
* University of Konstanz
*
* @version $Id: AbstractNode.java 772 2011-09-16 15:39:44Z raedle $
* @since 1.0.0
*/
public class AbstractNode extends Node {
// Logger to log info, error, debug,... messages.
private static final Log LOG = LogFactory.getLog(AbstractNode.class);
// ################################################################################
// BEGIN OF ADJUSTABLES
// ################################################################################
// @XmlAttribute(name = "active")
// @Property(
// name = "Active",
// group = "Activation",
// description =
// "Whether the processable is active or not. If it's not active the data points will be send through the processable without any process execution."
// )
// @CheckBox
// protected boolean active = true;
//
// /**
// * @return the active
// */
// public final boolean isActive() {
// return active;
// }
//
// /**
// * @param active
// * the active to set
// */
// public final void setActive(boolean active) {
// this.active = active;
// }
// ################################################################################
// END OF ADJUSTABLES
// ################################################################################
private final Map<Class<? extends IData>, Method> PROCESS_CACHE = new HashMap<Class<? extends IData>, Method>();
/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
// Allows processable to wait for data and notify if data has been sent.
private Object lock = new Object();
// The process thread.
private Thread processingThread;
// private final ConcurrentLinkedQueue<IDataContainer> dataQueue = new ConcurrentLinkedQueue<IDataContainer>();
private final Queue<IDataContainer> dataQueue = new LinkedList<IDataContainer>();
public final Queue<IDataContainer> getDataQueue() {
return dataQueue;
}
/*
* (non-Javadoc)
*
* @see org.squidy.manager.model.Processable#start()
*/
public final void start() throws ProcessException {
// If it is already processing just return.
if (isProcessing()) {
return;
}
super.start();
try {
onStart();
} catch (Throwable e) {
if (LOG.isErrorEnabled()) {
LOG.error(e.getMessage(), e);
}
publishFailure(e);
processing = false;
return;
}
resolveFailure();
processingThread = new Thread(new Runnable() {
/*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
public void run() {
while (processing) {
try {
synchronized (lock) {
while (processing && dataQueue.isEmpty()) {
lock.wait();
}
if (processing) {
IDataContainer dataContainer;
// while (!dataQueue.isEmpty()) {
dataContainer = dataQueue.poll();
if (dataContainer == null) {
throw new SquidyException("Empty data container has been queued");
}
dataContainer = preProcess(dataContainer);
if (dataContainer != null) {
dataContainer = processInternal(dataContainer);
}
if (dataContainer != null) {
dataContainer = postProcess(dataContainer);
}
if (dataContainer != null) {
publish(dataContainer);
}
// synchronized (lock) {
// lock.notify();
// }
}
}
} catch (InterruptedException e) {
if (processing) {
throw new ProcessException(e.getMessage(), e);
}
}
}
}
}, getId() + "_" + nextThreadNum());
processingThread.start();
}
/*
* (non-Javadoc)
*
* @see org.squidy.manager.model.Processable#stop()
*/
public final void stop() throws ProcessException {
// If it is not processing just return.
if (!processing) {
return;
}
super.stop();
try {
onStop();
} catch (Exception e) {
publishFailure(e);
// throw new ProcessException(e.getMessage(), e);
} finally {
synchronized (lock) {
// Empty the data queue.
dataQueue.clear();
lock.notify();
}
if (processingThread != null)
try {
processingThread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/* (non-Javadoc)
* @see org.squidy.manager.model.Processable#delete()
*/
public void delete() throws ProcessException {
// This hack avoids concurrent modification exceptions while deleting
// pipes.
Collection<Pipe> pipesToDelete = new ArrayList<Pipe>();
for (Pipe pipe : getPipes()) {
pipesToDelete.add(pipe);
}
// TODO [RR]: Hack for -> Fix me - if uncomment this creates 2 pipe
// objects when loading from file
for (Pipe pipe : getIncomingPipes()) {
pipesToDelete.add(pipe);
}
for (Pipe pipe : pipesToDelete) {
pipe.delete();
}
super.delete();
}
/**
*
*/
public void onStart() {
// empty
}
/**
*
*/
public void onStop() {
// empty
}
/* (non-Javadoc)
* @see org.squidy.manager.IProcessable#process(org.squidy.manager.data.IDataContainer)
*/
public final IDataContainer process(IDataContainer dataContainer) {
if (!checkProcessingPrivileges(dataContainer)) {
return null;
}
if (!processing) {
publish(dataContainer);
return null;
}
// Add data container to processing queue.
// dataQueue.add(dataContainer);
synchronized (lock) {
// // Locking of the data queue is not necessary because the queue is already thread-safe.
// Add data container to processing queue.
dataQueue.add(dataContainer);
// Notify the processing thread.
lock.notify();
// try {
// lock.wait();
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
}
return null;
}
/**
* @param dataContainer
* @return
*/
public boolean checkProcessingPrivileges(IDataContainer dataContainer) {
return true;
}
/**
* @param dataContainer
* @return
*/
private IDataContainer processInternal(IDataContainer dataContainer)
throws ProcessException {
IData[] datas = dataContainer.getData();
List<IData> processedData = new ArrayList<IData>(datas.length);
for (IData data : datas) {
// Do no process killed data objects.
if (((AbstractData) data).isKilled())
continue;
Method method = getProcessMethod(data.getClass());
// assert method != null : "Could not process data type " +
// data.getClass().getName() + " for class " + me.getName();
if (method == null) {
// TODO [RR]: Comment me if context filtering is not possible!!!
processedData.add(data);
continue;
}
// Process data object.
try {
Object o = method.invoke(this, data);
if (o != null) {
// TODO [RR]: Bubble this exception for usage at frontend.
try {
processedData.add((IData) o);
} catch (ProcessException e) {
if (LOG.isErrorEnabled()) {
LOG.error(e.getMessage(), e);
}
}
}
} catch (IllegalArgumentException e) {
if (LOG.isErrorEnabled()) {
LOG.error(e.getMessage(), e);
}
} catch (IllegalAccessException e) {
if (LOG.isErrorEnabled()) {
LOG.error(e.getMessage(), e);
}
} catch (InvocationTargetException e) {
if (LOG.isErrorEnabled()) {
LOG.error(e.getMessage(), e);
}
}
}
if (processedData.size() <= 0) {
return null;
}
// Set new data to keep data container's meta information.
dataContainer.setData(processedData.toArray(new IData[processedData.size()]));
return dataContainer;
}
/**
* @param dataContainer
* @return
*/
public IDataContainer preProcess(final IDataContainer dataContainer) {
return dataContainer;
}
/**
* @param dataContainer
* @return
*/
public IDataContainer postProcess(final IDataContainer dataContainer) {
return dataContainer;
}
/**
* Publishes a data container to all outgoing connected processables.
*
* @param dataContainer
* The data container that gets published.
*/
public final void publish(final IDataContainer dataContainer) {
Collection<Pipe> pipes = getOutgoingPipes();
int size = pipes.size();
for (Pipe pipe : pipes) {
IDataContainer container = dataContainer;
if (size > 1) {
container = dataContainer.getClone();
}
container = pipe.process(container);
if (container == null) {
continue;
}
// attachVisitors(container);
// notifyVisitors(container);
Processable processable = pipe.getTarget();
// A processable is inside of a pipeline.
if (processable instanceof Pipeline && processable.equals(pipe.getSource().getParent())) {
((Pipeline) processable).publishToOutside(container);
}
else {
processable.process(container);
}
}
}
/**
* Publishes data objects to all outgoing connected processables.
*
* @param data
* The data objects that get published.
* @see ReflectionProcessable#publish(IDataContainer)
*/
public final void publish(final IData... data) {
if (data == null || data.length < 1) {
throw new ProcessException(
"Publishing an empty collection of data.");
}
publish(new DefaultDataContainer(data));
}
/**
* Publishes data objects to all outgoing connected processables.
*
* @param data
* The data objects that get published.
* @see ReflectionProcessable#publish(IData...)
*/
public final void publish(Collection<? extends IData> data) {
publish(new DefaultDataContainer(data.toArray(new IData[data.size()])));
}
/**
* @param type
* @return
*/
private Method getProcessMethod(Class<? extends IData> type) {
// Return cached method if available.
if (PROCESS_CACHE.containsKey(type)) {
return PROCESS_CACHE.get(type);
}
Class<?> me = getClass();
Method method = null;
while (method == null && !AbstractNode.class.equals(me)) {
Class<?> dataType = type;
do {
try {
method = me.getDeclaredMethod("process", dataType);
} catch (SecurityException e) {
// do nothing
} catch (NoSuchMethodException e) {
// do nothing
}
if (method != null) {
// Add method to cache to avoid further re-runs for this
// data type.
PROCESS_CACHE.put(type, method);
return method;
}
dataType = dataType.getSuperclass();
// Hierarchical upper data class (interface).
if (dataType == null) {
break;
}
// Set base type if abstract data class has been reached.
if (dataType.isAssignableFrom(AbstractData.class)) {
dataType = IData.class;
}
} while (method == null);
me = me.getSuperclass();
}
// Add empty method to cache to avoid further re-runs for this data
// type.
PROCESS_CACHE.put(type, null);
return null;
}
}