/** * 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/windowing/LocalTimeBasedSlidingHandler.java * * @author gsn_devs * @author Ali Salehi * @author bgpearn * @author Mehdi Riahi * @author Timotee Maret * @author Sofiane Sarni * */ package ch.epfl.gsn.beans.windowing; 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 java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.TreeMap; import org.slf4j.LoggerFactory; import ch.epfl.gsn.Main; import ch.epfl.gsn.beans.StreamElement; import ch.epfl.gsn.beans.StreamSource; 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 LocalTimeBasedSlidingHandler implements SlidingHandler { private static final transient Logger logger = LoggerFactory.getLogger(LocalTimeBasedSlidingHandler.class); private static int timerCount = 0; private List<StreamSource> streamSources; private AbstractWrapper wrapper; private Timer timer; private long timerTick = -1; private Map<StreamSource, Long> slidingHashMap; public LocalTimeBasedSlidingHandler(AbstractWrapper wrapper) { streamSources = Collections.synchronizedList(new ArrayList<StreamSource>()); slidingHashMap = Collections.synchronizedMap(new HashMap<StreamSource, Long>()); timer = new Timer("LocalTimeBasedSlidingHandlerTimer" + (++timerCount)); this.wrapper = wrapper; } public void addStreamSource(StreamSource streamSource) { SQLViewQueryRewriter rewriter = new LTBSQLViewQueryRewriter(); rewriter.setStreamSource(streamSource); rewriter.initialize(); if (streamSource.getWindowingType() != WindowType.TIME_BASED_SLIDE_ON_EACH_TUPLE) { long oldTimerTick = timerTick; if (streamSource.getWindowingType() == WindowType.TIME_BASED) { slidingHashMap.put(streamSource, streamSource.getParsedSlideValue() - streamSource.getParsedStorageSize()); if (timerTick == -1) { timerTick = GCD(streamSource.getParsedStorageSize(), streamSource.getParsedSlideValue()); } else { timerTick = GCD(timerTick, GCD(streamSource.getParsedStorageSize(), streamSource.getParsedSlideValue())); } } else { slidingHashMap.put(streamSource, 0L); if (timerTick == -1) { timerTick = streamSource.getParsedSlideValue(); } else { timerTick = GCD(timerTick, streamSource.getParsedSlideValue()); } } if (oldTimerTick != timerTick) { timer.cancel(); timer = new Timer(); logger.debug("About to schedule new timer task at period " + timerTick + "ms in the " + wrapper.getDBAliasInStr() + " wrapper"); timer.schedule(new LTBTimerTask(), 500, timerTick); } } else { streamSources.add(streamSource); } } public long GCD(long a, long b) { return WindowingUtil.GCD(a, b); } private class LTBTimerTask extends TimerTask { @Override public void run() { synchronized (slidingHashMap) { for (StreamSource streamSource : slidingHashMap.keySet()) { long slideVar = slidingHashMap.get(streamSource) + timerTick; if (slideVar >= streamSource.getParsedSlideValue()) { slideVar = 0; streamSource.getQueryRewriter().dataAvailable(System.currentTimeMillis()); } slidingHashMap.put(streamSource, slideVar); } } } } public boolean dataAvailable(StreamElement streamElement) { boolean toReturn = false; synchronized (streamSources) { for (StreamSource streamSource : streamSources) { if (streamSource.getWindowingType() == WindowType.TIME_BASED_SLIDE_ON_EACH_TUPLE) { toReturn = streamSource.getQueryRewriter().dataAvailable(streamElement.getTimeStamp()) || toReturn; } } } return toReturn; } public String getCuttingCondition() { long timed1 = -1; long timed2 = -1; long maxTupleCount = 0; long maxSlideForTupleBased = 0; long maxWindowSize = 0; synchronized (streamSources) { for (StreamSource streamSource : streamSources) { maxWindowSize = Math.max(maxWindowSize, streamSource.getParsedStorageSize()); } } synchronized (slidingHashMap) { for (StreamSource streamSource : slidingHashMap.keySet()) { if (streamSource.getWindowingType() == WindowType.TIME_BASED) { maxWindowSize = Math.max(maxWindowSize, streamSource.getParsedStorageSize() + streamSource.getParsedSlideValue()); } else { maxSlideForTupleBased = Math.max(maxSlideForTupleBased, streamSource.getParsedSlideValue()); maxTupleCount = Math.max(maxTupleCount, streamSource.getParsedStorageSize()); } } } if (maxWindowSize > 0) { timed1 = System.currentTimeMillis() - maxWindowSize; } if (maxTupleCount > 0) { StringBuilder query = new StringBuilder(); if (Main.getWindowStorage().isH2() || Main.getWindowStorage().isMysqlDB()) { query.append(" select timed from ").append(wrapper.getDBAliasInStr()).append(" where timed <= "); query.append(System.currentTimeMillis() - maxSlideForTupleBased).append(" order by timed desc limit 1 offset ").append( maxTupleCount - 1); } else if (Main.getWindowStorage().isSqlServer()) { query.append(" select min(timed) from (select top ").append(maxTupleCount).append(" * ").append(" from ").append( wrapper.getDBAliasInStr()).append(" where timed <= ").append(System.currentTimeMillis() - maxSlideForTupleBased).append(" order by timed desc) as X "); } logger.debug("Query for getting oldest timestamp : " + query); Connection conn = null; try { ResultSet resultSet = Main.getWindowStorage().executeQueryWithResultSet(query,conn=Main.getWindowStorage().getConnection()); if (resultSet.next()) { timed2 = resultSet.getLong(1); } else { return "timed < -1"; } } catch (SQLException e) { logger.error(e.getMessage(), e); } finally { Main.getWindowStorage().close(conn); } } if (timed1 >= 0 && timed2 >= 0) { return "timed < " + Math.min(timed1, timed2); } return "timed < " + ((timed1 == -1) ? timed2 : timed1); } public void removeStreamSource(StreamSource streamSource) { streamSources.remove(streamSource); slidingHashMap.remove(streamSource); streamSource.getQueryRewriter().dispose(); updateTimerTick(); } private void updateTimerTick() { long oldTimerTick = timerTick; // recalculating timer tick timerTick = -1; synchronized (slidingHashMap) { for (StreamSource streamSource : slidingHashMap.keySet()) { if (streamSource.getWindowingType() == WindowType.TIME_BASED) { slidingHashMap.put(streamSource, streamSource.getParsedSlideValue() - streamSource.getParsedStorageSize()); if (timerTick == -1) { timerTick = GCD(streamSource.getParsedStorageSize(), streamSource.getParsedSlideValue()); } else { timerTick = GCD(timerTick, GCD(streamSource.getParsedStorageSize(), streamSource.getParsedSlideValue())); } } else { slidingHashMap.put(streamSource, 0L); if (timerTick == -1) { timerTick = streamSource.getParsedSlideValue(); } else { timerTick = GCD(timerTick, streamSource.getParsedSlideValue()); } } } } if (oldTimerTick != timerTick && timerTick > 0) { timer.cancel(); timer = new Timer(); logger.debug("About to schedule new timer task at period " + timerTick + "ms in the " + wrapper.getDBAliasInStr() + " wrapper"); timer.schedule(new LTBTimerTask(), 500, timerTick); } } public void dispose() { synchronized (streamSources) { for (StreamSource streamSource : streamSources) { streamSource.getQueryRewriter().dispose(); } streamSources.clear(); } synchronized (slidingHashMap) { for (StreamSource streamSource : slidingHashMap.keySet()) { streamSource.getQueryRewriter().dispose(); } slidingHashMap.clear(); } } public boolean isInterestedIn(StreamSource streamSource) { return WindowType.isTimeBased(streamSource.getWindowingType()); } private class LTBSQLViewQueryRewriter extends SQLViewQueryRewriter { @Override public CharSequence createViewSQL() { if (cachedSqlQuery != null) { return cachedSqlQuery; } if (streamSource.getWrapper() == null) { throw new GSNRuntimeException("Wrapper object is null, most probably a bug, please report it !"); } if (streamSource.validate() == false) { throw new GSNRuntimeException("Validation of this object the stream source failed, please check the logs."); } CharSequence wrapperAlias = streamSource.getWrapper().getDBAliasInStr(); long windowSize = streamSource.getParsedStorageSize(); if (streamSource.getSamplingRate() == 0 || windowSize == 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); String sqlQuery = streamSource.getSqlQuery(); StringBuilder toReturn = new StringBuilder(); int fromIndex = sqlQuery.indexOf(" from "); if(Main.getWindowStorage().isH2() && fromIndex > -1){ toReturn.append(sqlQuery.substring(0, fromIndex + 6)).append(" (select * from ").append(sqlQuery.substring(fromIndex + 6)); }else{ toReturn.append(sqlQuery); } if (sqlQuery.toLowerCase().indexOf(" where ") < 0) { toReturn.append(" where "); } else { toReturn.append(" and "); } if (streamSource.getSamplingRate() != 1) { if (Main.getWindowStorage().isH2()) { toReturn.append("( timed - (timed / 100) * 100 < ").append(streamSource.getSamplingRate() * 100).append(") and "); } else { toReturn.append("( mod( timed , 100)< ").append(streamSource.getSamplingRate() * 100).append(") and "); } } WindowType windowingType = streamSource.getWindowingType(); if (windowingType == WindowType.TIME_BASED_SLIDE_ON_EACH_TUPLE) { 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 )"); } long timeDifferenceInMillis = storageManager.getTimeDifferenceInMillis(); // System.out.println(timeDifferenceInMillis); toReturn.append(" - ").append(windowSize).append(" - ").append(timeDifferenceInMillis).append(" )"); if (Main.getWindowStorage().isH2() || Main.getWindowStorage().isMysqlDB()) { toReturn.append(") order by timed desc "); } } else { if (windowingType == WindowType.TIME_BASED) { toReturn.append("timed in (select timed from ").append(wrapperAlias).append(" where timed <= (select timed from ").append(SQLViewQueryRewriter.VIEW_HELPER_TABLE).append(" where U_ID='").append(streamSource.getUIDStr()).append( "') and timed >= (select timed from ").append(SQLViewQueryRewriter.VIEW_HELPER_TABLE).append( " where U_ID='").append(streamSource.getUIDStr()).append("') - ").append(windowSize).append(" ) "); if (Main.getWindowStorage().isH2() || Main.getWindowStorage().isMysqlDB()) { toReturn.append(" order by timed desc "); } } else {// WindowType.TUPLE_BASED_WIN_TIME_BASED_SLIDE if (Main.getWindowStorage().isMysqlDB()) { toReturn.append("timed <= (select timed from ").append(SQLViewQueryRewriter.VIEW_HELPER_TABLE).append( " where U_ID='").append(streamSource.getUIDStr()).append("') and timed >= (select timed from "); toReturn.append(wrapperAlias).append(" where timed <= (select timed from "); toReturn.append(SQLViewQueryRewriter.VIEW_HELPER_TABLE).append(" where U_ID='").append(streamSource.getUIDStr()); toReturn.append("') ").append(" order by timed desc limit 1 offset ").append(windowSize - 1).append(" )"); toReturn.append(" order by timed desc "); } else if (Main.getWindowStorage().isH2()) { toReturn.append("timed <= (select timed from ").append(SQLViewQueryRewriter.VIEW_HELPER_TABLE).append( " where U_ID='").append(streamSource.getUIDStr()).append("') and timed >= (select distinct(timed) from "); toReturn.append(wrapperAlias).append(" where timed in (select timed from ").append(wrapperAlias).append( " where timed <= (select timed from "); toReturn.append(SQLViewQueryRewriter.VIEW_HELPER_TABLE).append(" where U_ID='").append(streamSource.getUIDStr()); toReturn.append("') ").append(" order by timed desc limit 1 offset ").append(windowSize - 1).append(" ))"); toReturn.append(" order by timed desc "); } else if (Main.getWindowStorage().isSqlServer()) { toReturn.append("timed in (select TOP ").append(windowSize).append(" timed from ").append(wrapperAlias).append( " where timed <= (select timed from ").append(SQLViewQueryRewriter.VIEW_HELPER_TABLE).append(" where U_ID='").append(streamSource.getUIDStr()).append("') order by timed desc ) "); } } } if(Main.getWindowStorage().isH2() && fromIndex > -1){ toReturn.append(")"); } toReturn = new StringBuilder(SQLUtils.newRewrite(toReturn, rewritingMapping)); logger.debug(new StringBuilder().append("The original Query : ").append(sqlQuery).toString()); logger.debug(new StringBuilder().append("The merged query : ").append(toReturn.toString()).append(" of the StreamSource ").append(streamSource.getAlias()).append(" of the InputStream: ").append( streamSource.getInputStream().getInputStreamName()).append("").toString()); return cachedSqlQuery = toReturn; } } }