/** * 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 */ package org.openiot.gsn.vsensor; import org.openiot.gsn.ContainerImpl; import org.openiot.gsn.beans.DataField; import org.openiot.gsn.beans.DataTypes; import org.openiot.gsn.beans.StreamElement; import org.openiot.gsn.beans.VSensorConfig; import java.io.Serializable; import java.sql.SQLException; import org.apache.log4j.Logger; public abstract class AbstractVirtualSensor { private static final transient Logger logger = Logger.getLogger( AbstractVirtualSensor.class ); private VSensorConfig virtualSensorConfiguration; private long lastVisitiedTime = 0; /** * Called once while initializing an instance of the virtual sensor * * @return True if the initialization is done successfully. */ public abstract boolean initialize ( ); private void validateStreamElement ( StreamElement streamElement ,boolean adjust) { if ( !compatibleStructure( streamElement, getVirtualSensorConfiguration( ).getOutputStructure( ),adjust ) ) { StringBuilder exceptionMessage = new StringBuilder( ).append( "The streamElement produced by :" ).append( getVirtualSensorConfiguration( ).getName( ) ).append( " Virtual Sensor is not compatible with the defined streamElement.\n" ); exceptionMessage.append( "The expected stream element structure (specified in " ).append( getVirtualSensorConfiguration( ).getFileName( ) ).append( " is [" ); for ( DataField df : getVirtualSensorConfiguration( ).getOutputStructure( ) ) exceptionMessage.append( df.getName( ) ).append( " (" ).append( DataTypes.TYPE_NAMES[ df.getDataTypeID( ) ] ).append( ") , " ); exceptionMessage.append( "] but the actual stream element received from the " + getVirtualSensorConfiguration( ).getName( ) ).append( " has the [" ); for ( int i = 0 ; i < streamElement.getFieldNames( ).length ; i++ ) exceptionMessage.append( streamElement.getFieldNames( )[ i ] ).append( "(" ).append( DataTypes.TYPE_NAMES[ streamElement.getFieldTypes( )[ i ] ] ).append( ")," ); exceptionMessage.append(" ] thus the stream element dropped !!!" ); throw new RuntimeException( exceptionMessage.toString( ) ); } } /** * if Adjust is true then system checks the output structure of the virtual sensor and * only publishes the fields defined in the output structure of the virtual sensor and * ignores the rest. IF the adjust is set to false, the system will enforce strict * compatibility of the output and the produced value. * * @param streamElement * @param adjust Default is false. */ protected synchronized void dataProduced ( StreamElement streamElement,boolean adjust ) { try { validateStreamElement( streamElement,adjust ); } catch ( Exception e ) { logger.error( e.getMessage( ) , e ); return; } if ( !streamElement.isTimestampSet( ) ) streamElement.setTimeStamp( System.currentTimeMillis( ) ); final int outputStreamRate = getVirtualSensorConfiguration( ).getOutputStreamRate( ); final long currentTime = System.currentTimeMillis( ); if ( ( currentTime - lastVisitiedTime ) < outputStreamRate ) { if ( logger.isInfoEnabled( ) ) logger.info( "Called by *discarded* b/c of the rate limit reached." ); return; } lastVisitiedTime = currentTime; try { ContainerImpl.getInstance().publishData( this ,streamElement); } catch (SQLException e) { if (e.getMessage().toLowerCase().contains("duplicate entry")) logger.info(e.getMessage(),e); else logger.error(e.getMessage(),e); } } /** * Calls the dataProduced with adjust = false. * @param streamElement */ protected synchronized void dataProduced ( StreamElement streamElement ) { dataProduced(streamElement,true); } /** * First checks compatibility of the data type of each output data item in the stream element with the * defined output in the VSD file. (this check is done regardless of the value for adjust flag). * <p> * If the adjust flag is set to true, the method checks the newly generated stream element * and returns true if and only if the number of data items is equal to the number of output * data structure defined for this virtual sensor. * If the adjust=true, then this test is not performed. * * @param se * @param outputStructure * @param adjust default is false. * @return */ private static boolean compatibleStructure ( StreamElement se , DataField [] outputStructure ,boolean adjust ) { if (!adjust && outputStructure.length != se.getFieldNames().length ) { logger.warn( "Validation problem, the number of field doesn't match the number of output data strcture of the virtual sensor" ); return false; } int i =-1; for (DataField field: outputStructure) { Serializable value = se.getData(field.getName()); i++; if (value==null) continue; if ( ( ( field.getDataTypeID() == DataTypes.BIGINT || field.getDataTypeID() == DataTypes.DOUBLE || field.getDataTypeID() == DataTypes.INTEGER|| field.getDataTypeID() == DataTypes.SMALLINT|| field.getDataTypeID() == DataTypes.TINYINT ) &&!(value instanceof Number)) || ( (field.getDataTypeID() == DataTypes.VARCHAR || field.getDataTypeID() == DataTypes.CHAR) && !(value instanceof String)) || ( (field.getDataTypeID() == DataTypes.BINARY) && !(value instanceof byte[])) ){ logger.warn( "Validation problem for output field >" + field.getName( ) + ", The field type declared as >" + field.getType()+"< while in VSD it is defined as >"+DataTypes.TYPE_NAMES[outputStructure[ i ].getDataTypeID( )]); return false; } } return true; } /** * Called when the container want to stop the pool and remove it's resources. * The container will call this method once on each install of the virtual * sensor in the pool. The progrmmer should release all the resouce used by * this virtual sensor instance in this method specially those resouces * aquired during the <code>initialize</code> call. <p/> Called once while * finalizing an instance of the virtual sensor */ public abstract void dispose ( ); public boolean dataFromWeb ( String action,String[] paramNames, Serializable[] paramValues ) { return false; } /** * @return the virtualSensorConfiguration */ public VSensorConfig getVirtualSensorConfiguration ( ) { if ( virtualSensorConfiguration == null ) { throw new RuntimeException( "The VirtualSensorParameter is not set !!!" ); } return virtualSensorConfiguration; } /** * @param virtualSensorConfiguration the virtualSensorConfiguration to set */ public void setVirtualSensorConfiguration ( VSensorConfig virtualSensorConfiguration ) { this.virtualSensorConfiguration = virtualSensorConfiguration; } /** * This method is going to be called by the container when one of the input * streams has a data to be delivered to this virtual sensor. After receiving * the data, the virutal sensor can do the processing on it and this * processing could possibly result in producing a new stream element in this * virtual sensor in which case the virutal sensor will notify the container * by simply adding itself to the list of the virtual sensors which have * produced data. (calling <code>container.publishData(this)</code>. For * more information please check the <code>AbstractVirtalSensor</code> * @param inputStreamName is the name of the input stream as specified in the * configuration file of the virtual sensor. @param inputDataFromInputStream * is actually the real data which is produced by the input stream and should * be delivered to the virtual sensor for possible processing. */ public abstract void dataAvailable ( String inputStreamName , StreamElement streamElement ); }