/**
* Global Sensor Networks (GSN) Source Code
* Copyright (c) 2006-2016, Ecole Polytechnique Federale de Lausanne (EPFL)
*
* This file is part of GSN.
*
* GSN is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GSN 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GSN. If not, see <http://www.gnu.org/licenses/>.
*
* File: src/ch/epfl/gsn/beans/StreamSource.java
*
* @author Mehdi Riahi
* @author gsn_devs
* @author Ali Salehi
* @author Timotee Maret
* @author Sofiane Sarni
*
*/
package ch.epfl.gsn.beans;
import java.io.Serializable;
import java.sql.SQLException;
import java.util.Date;
import java.util.TreeMap;
import org.slf4j.LoggerFactory;
import ch.epfl.gsn.Main;
import ch.epfl.gsn.beans.AddressBean;
import ch.epfl.gsn.beans.InputStream;
import ch.epfl.gsn.beans.StreamSource;
import ch.epfl.gsn.beans.windowing.QueryRewriter;
import ch.epfl.gsn.beans.windowing.WindowType;
import ch.epfl.gsn.storage.SQLUtils;
import ch.epfl.gsn.utils.CaseInsensitiveComparator;
import ch.epfl.gsn.utils.GSNRuntimeException;
import ch.epfl.gsn.wrappers.AbstractWrapper;
import org.slf4j.Logger;
public class StreamSource implements Serializable{
private static final long serialVersionUID = 5222853537667420098L;
public static final String DEFAULT_QUERY = "select * from wrapper";
private static final transient Logger logger = LoggerFactory.getLogger( StreamSource.class );
private String alias;
private float samplingRate ;
private String rawHistorySize = null;
private String rawSlideValue = null;
private int disconnectedBufferSize;
private String sqlQuery;
protected int uid ;
protected StringBuilder uidS ;
/**
* Checks the timing to see whether the time is ok for starting.
*/
private static final String [ ] dateFormats = new String [ ] { "yyyy/MM/dd 'at' HH:mm:ss z" , "h:mm:ss a" , "h:mm a" };
private transient Date startDate;
private transient Date endDate;
public static final AddressBean[] EMPTY_ADDRESS_BEAN = new AddressBean[] {};
private AddressBean addressing [] = EMPTY_ADDRESS_BEAN;
private transient AbstractWrapper wrapper;
private InputStream inputStream ;
private AddressBean activeAddressBean; // To be used by the gui
private transient QueryRewriter queryRewriter;
public StreamSource() {
}
public String getRawHistorySize() {
return rawHistorySize;
}
public StreamSource setRawHistorySize(String rawHistorySize) {
this.rawHistorySize = rawHistorySize;
isValidated = false;
return this;
}
public StreamSource setRawSlideValue(String rawSlideValue){
this.rawSlideValue = rawSlideValue;
isValidated = false;
return this;
}
public StreamSource setAddressing(AddressBean[] addressing) {
this.addressing = addressing;
return this;
}
public StreamSource setAlias(String alias) {
this.alias = alias;
return this;
}
public StreamSource setSqlQuery(String sqlQuery) {
this.sqlQuery = sqlQuery;
return this;
}
public AddressBean [] getAddressing ( ) {
if (addressing==null)
addressing=EMPTY_ADDRESS_BEAN;
return addressing;
}
/**
* @return Returns the alias.
*/
public CharSequence getAlias ( ) {
return alias.toLowerCase( );
}
public InputStream getInputStream() {
return inputStream;
}
/**
* @return Returns the bufferSize.
*/
public int getDisconnectedBufferSize ( ) {
return disconnectedBufferSize;
}
public void setDisconnectedBufferSize(int disconnectedBufferSize){
this.disconnectedBufferSize = disconnectedBufferSize;
}
public float getSamplingRate ( ) {
return samplingRate;
}
public void setSamplingRate (float newRate ) {
if (cachedSqlQuery!=null)
throw new GSNRuntimeException("Sampling rate can't be changed anymore !");
if (newRate>=0 && newRate<=1)
this.samplingRate=newRate;
else
throw new GSNRuntimeException("Invalid sampling rate is provided. Sampling rate is between 0 and 1.");
}
/**
* @return Returns the storageSize.
*/
public String getStorageSize ( ) {
return this.rawHistorySize;
}
/**
* @return the slide value
*/
public String getSlideValue () {
return rawSlideValue != null ? rawSlideValue : String.valueOf(DEFAULT_SLIDE_VALUE);
}
/**
* @return Returns the sqlQuery.
*/
public String getSqlQuery ( ) {
if (sqlQuery==null || sqlQuery.trim( ).length( ) == 0 ) sqlQuery = DEFAULT_QUERY;
return sqlQuery;
}
public void setWrapper ( AbstractWrapper wrapper ) throws SQLException {
if (validate()==false)
throw new GSNRuntimeException("Can't set the wrapper when the stream source is invalid.");
this.wrapper = wrapper;
this.activeAddressBean = wrapper.getActiveAddressBean();
wrapper.addListener(this);
}
/**
* @return Returns the activeSourceProducer.
*/
public AbstractWrapper getWrapper ( ) {
if (wrapper==null)
throw new GSNRuntimeException("The wrapper for stream source is not set !.");
return wrapper;
}
private transient boolean isStorageCountBased = false;
private WindowType windowingType = DEFAULT_WINDOW_TYPE;
public static final long STORAGE_SIZE_NOT_SET = -1;
public static final long DEFAULT_SLIDE_VALUE = 1;
public static final WindowType DEFAULT_WINDOW_TYPE = WindowType.TUPLE_BASED_SLIDE_ON_EACH_TUPLE;
private transient long parsedStorageSize = STORAGE_SIZE_NOT_SET;
private transient long parsedSlideValue = DEFAULT_SLIDE_VALUE;
private transient boolean isValidated = false;
private transient boolean validationResult = false;
/**;
* Note that the validate method doesn't case if the wrapper variable or input stream variable are set or not.
*
* @return
*/
public boolean validate ( ) {
if (isValidated==true)
return validationResult;
windowingType = DEFAULT_WINDOW_TYPE;
isValidated=true;
if (samplingRate <=0 )
logger.warn("The sampling rate is set to zero (or negative) which means no results. StreamSource = " + getAlias( ));
else if(samplingRate > 1){
samplingRate = 1;
logger.warn("The provided sampling rate is greater than 1, resetting it to 1. StreamSource = " + getAlias( ));
}
if (getAddressing().length==0) {
logger.warn("Validation failed because there is no addressing predicates provided for the stream source (the addressing part of the stream source is empty) stream source alias = "+getAlias());
return validationResult=false;
}
if ( this.rawHistorySize != null ) {
this.rawHistorySize = this.rawHistorySize.replace( " " , "" ).trim( ).toLowerCase( );
if ( this.rawHistorySize.equalsIgnoreCase( "" ) ) return validationResult = true;
final int second = 1000;
final int minute = second * 60;
final int hour = minute * 60;
final int mIndex = this.rawHistorySize.indexOf( "m" );
final int hIndex = this.rawHistorySize.indexOf( "h" );
final int sIndex = this.rawHistorySize.indexOf( "s" );
if ( mIndex < 0 && hIndex < 0 && sIndex < 0 ) {
try {
this.parsedStorageSize = Long.parseLong(this.rawHistorySize );
this.isStorageCountBased = true;
windowingType = WindowType.TUPLE_BASED;
} catch ( final NumberFormatException e ) {
logger.error( "The storage size, " + this.rawHistorySize + ", specified for the Stream Source : " + this.getAlias( ) + " is not valid.", e );
return (validationResult= false);
}
} else
try {
final StringBuilder shs = new StringBuilder( this.rawHistorySize );
if ( mIndex >= 0 && mIndex == shs.length() - 1) this.parsedStorageSize = Long.parseLong(shs.deleteCharAt( mIndex ).toString( ) ) * minute;
else if ( hIndex >= 0 && hIndex == shs.length() - 1) this.parsedStorageSize = Long.parseLong( shs.deleteCharAt( hIndex ).toString( ) ) * hour;
else if ( sIndex >= 0 && sIndex == shs.length() - 1) this.parsedStorageSize = Long.parseLong( shs.deleteCharAt( sIndex ).toString( ) ) * second;
else Long.parseLong("");
this.isStorageCountBased = false;
windowingType = WindowType.TIME_BASED;
} catch ( NumberFormatException e ) {
logger.error("The storage size, "+rawHistorySize+", specified for the Stream Source : "+this.getAlias()+" is not valid: "+ e.getMessage());
return (validationResult=false);
}
}
//Parsing slide value
if(this.rawSlideValue == null){
//If slide value was not specified by the user, consider it as 1 tuple
windowingType = (windowingType == WindowType.TUPLE_BASED) ? WindowType.TUPLE_BASED_SLIDE_ON_EACH_TUPLE : WindowType.TIME_BASED_SLIDE_ON_EACH_TUPLE;
return validationResult = true;
} else {
this.rawSlideValue = this.rawSlideValue.replace( " " , "" ).trim( ).toLowerCase( );
//If slide value was not specified by the user, consider it as 1 tuple
if ( this.rawSlideValue.equalsIgnoreCase( "" ) ){
windowingType = (windowingType == WindowType.TUPLE_BASED) ? WindowType.TUPLE_BASED_SLIDE_ON_EACH_TUPLE : WindowType.TIME_BASED_SLIDE_ON_EACH_TUPLE;
return validationResult = true;
}
final int second = 1000;
final int minute = second * 60;
final int hour = minute * 60;
final int mIndex = this.rawSlideValue.indexOf( "m" );
final int hIndex = this.rawSlideValue.indexOf( "h" );
final int sIndex = this.rawSlideValue.indexOf( "s" );
if ( mIndex < 0 && hIndex < 0 && sIndex < 0 ) {
try {
this.parsedSlideValue = Long.parseLong( this.rawSlideValue );
if(parsedSlideValue == 1){//We consider this as a special case
windowingType = (windowingType == WindowType.TIME_BASED) ? WindowType.TIME_BASED_SLIDE_ON_EACH_TUPLE : WindowType.TUPLE_BASED_SLIDE_ON_EACH_TUPLE;
}
else if(windowingType == WindowType.TIME_BASED)
windowingType = WindowType.TIME_BASED_WIN_TUPLE_BASED_SLIDE;
} catch ( final NumberFormatException e ) {
logger.error("The slide value, " + rawSlideValue + ", specified for the Stream Source : " + getAlias() + " is not valid.", e );
return (validationResult= false);
}
} else
try {
final StringBuilder shs = new StringBuilder( this.rawSlideValue );
if ( mIndex >= 0 && mIndex == shs.length() - 1) this.parsedSlideValue = Long.parseLong( shs.deleteCharAt( mIndex ).toString( ) ) * minute;
else if ( hIndex >= 0 && hIndex == shs.length() - 1) this.parsedSlideValue = Long.parseLong( shs.deleteCharAt( hIndex ).toString( ) ) * hour;
else if ( sIndex >= 0 && sIndex == shs.length() - 1) this.parsedSlideValue = Long.parseLong( shs.deleteCharAt( sIndex ).toString( ) ) * second;
else Long.parseLong("");
if(windowingType == WindowType.TUPLE_BASED)
windowingType = WindowType.TUPLE_BASED_WIN_TIME_BASED_SLIDE;
} catch ( NumberFormatException e ) {
logger.error("The slide value, "+rawSlideValue+", specified for the Stream Source : "+getAlias()+" is not valid: "+e.getMessage());
return (validationResult=false);
}
}
return validationResult=true;
}
public boolean isStorageCountBased ( ) {
validate();
return this.isStorageCountBased;
}
public long getParsedStorageSize ( ) {
validate();
return this.parsedStorageSize;
}
public WindowType getWindowingType(){
validate();
return windowingType;
}
public long getParsedSlideValue(){
validate();
return parsedSlideValue;
}
public boolean windowSlided() throws SQLException{
logger.debug("Data availble in the stream *" + getAlias( ) + "*");
return inputStream.executeQuery( getUIDStr() );
}
public void setQueryRewriter(QueryRewriter rewriter){
this.queryRewriter = rewriter;
}
public QueryRewriter getQueryRewriter(){
return queryRewriter;
}
public StringBuilder rewrite(String query){
if(queryRewriter != null){
return queryRewriter.rewrite(query);
}else{
//TODO ??
return null;
}
}
public StringBuilder getUIDStr() {
if (validate()==false)
return null;
if (uidS==null) {
uid = Main.getWindowStorage().tableNameGenerator( );
uidS = Main.getWindowStorage().tableNameGeneratorInString( uid );
}
return uidS;
}
public int hashCode() {
return getUIDStr().hashCode();
}
// public Boolean dataAvailable ( ) throws SQLException {
// if ( logger.isDebugEnabled( ) ) logger.debug( new StringBuilder( ).append( "Data avialble in the stream *" ).append( getAlias( ) ).append( "*" ).toString( ) );
// return inputStream.dataAvailable( getUIDStr() );
// }
private StringBuilder cachedSqlQuery = null;
/**
* This method gets a stream source specification and generates the appropriate where clauses representing the stream source.
* The method takes into account the start time, end time, sampling rate, storage size (Both timed and count).
* This method combines the conditions generated by the stream source specification with the
* actually conditions listed in the stream source query. Afterwades, the method does the
* renaming of the whole query. The result will be cased in this object for later reuse.
*
* @return
*/
public StringBuilder toSql() {
if (cachedSqlQuery!=null)
return cachedSqlQuery;
if (getWrapper()==null)
throw new GSNRuntimeException("Wrapper object is null, most probably a bug, please report it !");
if (validate()==false)
throw new GSNRuntimeException("Validation of this object the stream source failed, please check the logs.");
CharSequence wrapperAlias = getWrapper().getDBAliasInStr();
if (samplingRate==0 || (isStorageCountBased && getParsedStorageSize()==0))
return cachedSqlQuery = new StringBuilder("select * from ").append(wrapperAlias).append(" where 1=0");
TreeMap < CharSequence , CharSequence > rewritingMapping = new TreeMap < CharSequence , CharSequence >(new CaseInsensitiveComparator() );
rewritingMapping.put("wrapper", wrapperAlias);
StringBuilder toReturn = new StringBuilder(getSqlQuery());
if (getSqlQuery().toLowerCase().indexOf(" where ")<0)
toReturn.append(" where " );
else
toReturn.append(" and " );
// Applying the ** START AND END TIME ** for all types of windows based windows
// toReturn.append(" wrapper.timed >=").append(getStartDate().getTime()).append(" and timed <=").append(getEndDate().getTime()).append(" and ");
if (isStorageCountBased()) {
if (Main.getWindowStorage().isH2()||Main.getWindowStorage().isMysqlDB())
toReturn.append("timed >= (select distinct(timed) from ").append(wrapperAlias).append(" order by timed desc limit 1 offset " ).append(getParsedStorageSize()-1).append( " )" );
else if (Main.getWindowStorage().isSqlServer())
toReturn.append("timed >= (select min(timed) from (select TOP ").append(getParsedStorageSize()).append(" timed from (select distinct(timed) from ").append(wrapperAlias).append(") as x order by timed desc ) as y )" );
}else { //time based
toReturn.append("(wrapper.timed >");
if ( Main.getWindowStorage().isH2( ) )
toReturn.append( " (NOW_MILLIS()");
else if ( Main.getWindowStorage().isMysqlDB( ) )
toReturn.append(" (UNIX_TIMESTAMP()*1000");
else if (Main.getWindowStorage().isPostgres())
toReturn.append(" (extract(epoch FROM now())*1000");
else if (Main.getWindowStorage().isSqlServer()) {
// NOTE1 : The value retuend is in seconds (hence 1000)
// NOTE2 : There is no time in the date for the epoch, maybe doesn't match with the current system time, needs checking.
toReturn.append(" (convert(bigint,datediff(second,'1/1/1970',current_timestamp))*1000 )");
}
toReturn.append(" - ").append(getParsedStorageSize()).append(" ) ) ");
}
if ( samplingRate !=1 )
toReturn.append( " and ( mod( timed , 100)< " ).append( samplingRate*100 ).append( ")" );
toReturn = new StringBuilder(SQLUtils.newRewrite(toReturn, rewritingMapping));
// toReturn.append(" order by timed desc ");
logger.debug("The original query : " + getSqlQuery());
logger.debug("The merged query : " + toReturn + " of the StreamSource " + getAlias( ) + " of the InputStream: " + inputStream.getInputStreamName());
return cachedSqlQuery=toReturn;
}
public StreamSource setInputStream(InputStream is) throws GSNRuntimeException{
if (alias==null)
throw new NullPointerException("Alias can't be null!");
if (this.inputStream!=null && is!=this.inputStream)
throw new GSNRuntimeException("Can't reset the input stream variable !.");
this.inputStream=is;
if (validate()==false)
throw new GSNRuntimeException("You can't set the input stream on an invalid stream source. ");
return this;
}
/**
* The wrapper in <code>StreamSource</code> is transient and is not serialized, so
* we use this method to specify the active address bean which is not accessible via
* the wrapper instance.
* @return the active address bean
*/
public AddressBean getActiveAddressBean() {
return activeAddressBean;
}
public String toString() {
StringBuilder toReturn = new StringBuilder();
toReturn.append(" Stream Source object: ");
toReturn.append(" Alias: ").append(alias);
toReturn.append(" uidS: ").append(uidS);
toReturn.append(" Active source: ").append(activeAddressBean);
return toReturn.toString();
}
}