/**
* Copyright (c) 2011-2014, OpenIoT
*
* This file is part of OpenIoT.
*
* OpenIoT 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, version 3 of the License.
*
* OpenIoT 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 OpenIoT. If not, see <http://www.gnu.org/licenses/>.
*
* Contact: OpenIoT mailto: info@openiot.eu
* @author gsn_devs
* @author Ali Salehi
* @author Mehdi Riahi
* @author Timotee Maret
*/
package org.openiot.gsn.wrappers;
import org.openiot.gsn.Main;
import org.openiot.gsn.beans.AddressBean;
import org.openiot.gsn.beans.DataField;
import org.openiot.gsn.beans.StreamElement;
import org.openiot.gsn.beans.StreamSource;
import org.openiot.gsn.beans.windowing.LocalTimeBasedSlidingHandler;
import org.openiot.gsn.beans.windowing.RemoteTimeBasedSlidingHandler;
import org.openiot.gsn.beans.windowing.SlidingHandler;
import org.openiot.gsn.beans.windowing.TupleBasedSlidingHandler;
import org.openiot.gsn.beans.windowing.WindowType;
import org.openiot.gsn.utils.GSNRuntimeException;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import javax.naming.OperationNotSupportedException;
import org.apache.log4j.Logger;
public abstract class AbstractWrapper extends Thread {
private final static transient Logger logger = Logger
.getLogger(AbstractWrapper.class);
protected final List<StreamSource> listeners = Collections
.synchronizedList(new ArrayList<StreamSource>());
private AddressBean activeAddressBean;
private boolean isActive = true;
private SlidingHandler tupleBasedSlidingHandler;
private SlidingHandler timeBasedSlidingHandler;
private HashMap<Class, SlidingHandler> slidingHandlers = new HashMap<Class, SlidingHandler>();
private boolean usingRemoteTimestamp = false;
private Long lastInOrderTimestamp;
public static final int GARBAGE_COLLECT_AFTER_SPECIFIED_NO_OF_ELEMENTS = 2;
/**
* Returns the view name created for this listener. Note that, GSN creates
* one view per listener.
*
* @throws SQLException
*/
public void addListener(StreamSource ss) throws SQLException {
if (WindowType.isTimeBased(ss.getWindowingType())) {
if (timeBasedSlidingHandler == null) {
timeBasedSlidingHandler = isUsingRemoteTimestamp() == false ? new LocalTimeBasedSlidingHandler(
this)
: new RemoteTimeBasedSlidingHandler(this);
addSlidingHandler(timeBasedSlidingHandler);
}
} else {
if (tupleBasedSlidingHandler == null)
tupleBasedSlidingHandler = new TupleBasedSlidingHandler(this);
addSlidingHandler(tupleBasedSlidingHandler);
}
for (SlidingHandler slidingHandler : slidingHandlers.values()) {
if (slidingHandler.isInterestedIn(ss))
slidingHandler.addStreamSource(ss);
}
listeners.add(ss);
if (logger.isDebugEnabled())
logger.debug("Adding listeners: " + ss.toString());
}
public void addSlidingHandler(SlidingHandler slidingHandler) {
slidingHandlers.put(slidingHandler.getClass(), slidingHandler);
}
/**
* Removes the listener with it's associated view.
*
* @throws SQLException
*/
public void removeListener(StreamSource ss) throws SQLException {
listeners.remove(ss);
// getStorageManager( ).executeDropView( ss.getUIDStr() );
for (SlidingHandler slidingHandler : slidingHandlers.values()) {
if (slidingHandler.isInterestedIn(ss))
slidingHandler.removeStreamSource(ss);
}
if (listeners.size() == 0) {
releaseResources();
}
}
/**
* @return the listeners
*/
public List<StreamSource> getListeners() {
return listeners;
}
//protected StorageManager getStorageManager() {
// return StorageManager.getInstance();
//
//}
/**
* This method is called whenever the wrapper wants to send a data item back
* to the source where the data is coming from. For example, If the data is
* coming from a wireless sensor network (WSN), This method sends a data
* item to the sink node of the virtual sensor. So this method is the
* communication between the System and actual source of data. The data sent
* back to the WSN could be a command message or a configuration message.
*
* @param dataItem
* : The data which is going to be send to the source of the data
* for this wrapper.
* @return True if the send operation is successful.
* @throws OperationNotSupportedException
* If the wrapper doesn't support sending the data back to the
* source. Note that by default this method throws this
* exception unless the wrapper overrides it.
*/
public boolean sendToWrapper(String action, String[] paramNames,
Object[] paramValues) throws OperationNotSupportedException {
throw new OperationNotSupportedException(
"This wrapper doesn't support sending data back to the source.");
}
public final AddressBean getActiveAddressBean() {
if (this.activeAddressBean == null) {
throw new RuntimeException(
"There is no active address bean associated with the wrapper.");
}
return activeAddressBean;
}
/**
* Only sets if there is no other activeAddressBean configured.
*
* @param newVal
* the activeAddressBean to set
*/
public void setActiveAddressBean(AddressBean newVal) {
if (this.activeAddressBean != null) {
throw new RuntimeException(
"There is already an active address bean associated with the wrapper.");
}
this.activeAddressBean = newVal;
}
private long noOfCallsToPostSE = 0;
private final transient int aliasCode = Main.getWindowStorage().tableNameGenerator();
private final CharSequence aliasCodeS = Main.getWindowStorage().tableNameGeneratorInString(aliasCode);
public int getDBAlias() {
return aliasCode;
}
public CharSequence getDBAliasInStr() {
return aliasCodeS;
}
public abstract DataField[] getOutputFormat();
public boolean isActive() {
return isActive;
}
protected void postStreamElement(Serializable... values) {
StreamElement se = new StreamElement(getOutputFormat(), values, System
.currentTimeMillis());
postStreamElement(se);
}
protected void postStreamElement(long timestamp, Serializable[] values) {
StreamElement se = new StreamElement(getOutputFormat(), values,
timestamp);
postStreamElement(se);
}
/**
* This method gets the generated stream element and notifies the input
* streams if needed. The return value specifies if the newly provided
* stream element generated at least one input stream notification or not.
*
* @param streamElement
* @return If the method returns false, it means the insertion doesn't
* effected any input stream.
*/
protected Boolean postStreamElement(StreamElement streamElement) {
if (streamElement == null) {
logger.info("postStreamElement is called with null ! Wrapper "
+ getWrapperName() + " might has a problem !");
return false;
}
try {
if (!isActive() || listeners.size() == 0)
return false;
if (!insertIntoWrapperTable(streamElement))
return false;
boolean toReturn = false;
if (logger.isDebugEnabled())
logger.debug("Size of the listeners to be evaluated - "
+ listeners.size());
for (SlidingHandler slidingHandler : slidingHandlers.values()) {
toReturn = slidingHandler.dataAvailable(streamElement)
|| toReturn;
}
if (++noOfCallsToPostSE
% GARBAGE_COLLECT_AFTER_SPECIFIED_NO_OF_ELEMENTS == 0) {
int removedRaws = removeUselessValues();
}
return toReturn;
} catch (Exception e) {
logger.error(e.getMessage(), e);
logger
.error("Produced data item from the wrapper couldn't be propagated inside the system.");
return false;
}
}
/**
* Updates the table representing the data items produced by the stream
* element. Returns false if the update fails or doesn't change the state of
* the table.
*
* @param se
* Stream element to be inserted to the table if needed.
* @return true if the stream element is successfully inserted into the
* table.
* @throws SQLException
*/
public boolean insertIntoWrapperTable(StreamElement se) throws SQLException {
if (listeners.size() == 0)
return false;
Connection conn = null;
try {
if (isOutOfOrder(se)) {
logger.debug("Out of order data item detected, it is not propagated into the system : [" + se.toString() + "]");
return false;
}
conn = Main.getWindowStorage().getConnection();
Main.getWindowStorage().executeInsert(aliasCodeS, getOutputFormat(), se, conn);
lastInOrderTimestamp = se.getTimeStamp();
return true;
} finally {
Main.getWindowStorage().close(conn);
}
}
public boolean isOutOfOrder(StreamElement se) throws SQLException {
if (listeners.size() == 0)
return false;
Connection conn = null;
try {
// Checks if the stream element is out of order
if (lastInOrderTimestamp == null) {
conn = Main.getWindowStorage().getConnection();
StringBuilder query = new StringBuilder();
query.append("select max(timed) from ").append(aliasCodeS);
ResultSet rs = Main.getWindowStorage().executeQueryWithResultSet(query,
conn);
if (rs.next()) {
lastInOrderTimestamp = rs.getLong(1);
} else {
lastInOrderTimestamp = Long.MIN_VALUE; // Table is empty
}
}
return (se.getTimeStamp() <= lastInOrderTimestamp);
} finally {
Main.getWindowStorage().close(conn);
}
}
/**
* This method is called whenever the wrapper wants to send a data item back
* to the source where the data is coming from. For example, If the data is
* coming from a wireless sensor network (WSN), This method sends a data
* item to the sink node of the virtual sensor. So this method is the
* communication between the System and actual source of data. The data sent
* back to the WSN could be a command message or a configuration message.
*
* @param dataItem
* : The data which is going to be send to the source of the data
* for this wrapper.
* @return True if the send operation is successful.
* @throws OperationNotSupportedException
* If the wrapper doesn't support sending the data back to the
* source. Note that by default this method throws this
* exception unless the wrapper overrides it.
*/
public boolean sendToWrapper(Object dataItem)
throws OperationNotSupportedException {
if (isActive == false)
throw new GSNRuntimeException(
"Sending to an inactive/disabled wrapper is not allowed !");
throw new OperationNotSupportedException(
"This wrapper doesn't support sending data back to the source.");
}
/**
* Removes all the listeners, drops the views representing them, drops the
* sensor table, stops the TableSizeEnforce thread.
*
*/
public StringBuilder getUselessWindow() {
long minTimed = -1;
synchronized (slidingHandlers) {
for (SlidingHandler slidingHandler : slidingHandlers.values()) {
long timed = slidingHandler.getOldestTimestamp();
logger.debug("***** Oldest timestamp : " + timed);
if (timed == -1) {
minTimed = -1;
break;
} else {
minTimed = (minTimed != -1) ? Math.min(minTimed, timed)
: timed;
}
}
}
logger.debug("Oldest timestamp : " + minTimed);
if (minTimed == -1)
return null;
StringBuilder sb = new StringBuilder("delete from ").append(
getDBAliasInStr()).append(" where ");
sb.append(" timed < ").append(minTimed);
return sb;
}
public int removeUselessValues() throws SQLException {
StringBuilder query = getUselessWindow();
if (query == null)
return 0;
if (logger.isDebugEnabled())
logger.debug(new StringBuilder().append(
"RESULTING QUERY FOR Table Size Enforce ").append(query)
.toString());
int deletedRows = Main.getWindowStorage().executeUpdate(query);
if (logger.isDebugEnabled())
logger.debug(new StringBuilder().append(deletedRows).append(
" old rows dropped from ").append(getDBAliasInStr())
.toString());
return deletedRows;
}
public void releaseResources() throws SQLException {
isActive = false;
dispose();
if (logger.isInfoEnabled())
logger.info("dispose called");
listeners.clear();
for (SlidingHandler slidingHandler : slidingHandlers.values()) {
slidingHandler.dispose();
}
Main.getWindowStorage().executeDropTable(aliasCodeS);
}
public static final String TIME_FIELD = "timed";
/**
* The addressing is provided in the ("ADDRESS",Collection<KeyValue>). If
* the DataSource can't initialize itself because of either internal error
* or inaccessibility of the host specified in the address the method
* returns false. The dbAliasName of the DataSource is also specified with
* the "DBALIAS" in the context. The "STORAGEMAN" points to the
* StorageManager which should be used for querying.
*
* @return True if the initialization do successfully otherwise false;
*/
public abstract boolean initialize();
public abstract void dispose();
public abstract String getWrapperName();
/**
* Indicates whether we use GSN's time (local time) or the time already
* exists in the data (remote time) for the timestamp of generated stream
* elements.
*
* @return <code>false</code> if we use local time <br>
* <code>true</code> if we use remote time
*/
protected boolean isUsingRemoteTimestamp() {
return usingRemoteTimestamp;
}
/**
*
* @param usingRemoteTimestamp
*/
protected void setUsingRemoteTimestamp(boolean usingRemoteTimestamp) {
this.usingRemoteTimestamp = usingRemoteTimestamp;
}
/**
* Returns true if the wrapper can produce multiple different data items
* [stream elements] with the same timestamp. If this is true, then all the
* stream elements with the same timestamp will be accepted. If this method
* returns false (default value), duplicates override each other and the
* latest received duplicate is the one which is going to be persisted.
*/
public boolean isTimeStampUnique() {
return true;
}
public boolean manualDataInsertion(StreamElement se) {
throw new RuntimeException(
"Manual data insertion is not supported by this wrapper");
}
}