/**
* 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 Mehdi Riahi
* @author gsn_devs
* @author Sofiane Sarni
* @author Ali Salehi
* @author Mehdi Riahi
* @author Timotee Maret
* @author Julien Eberle
*/
package org.openiot.gsn;
import org.openiot.gsn.beans.AddressBean;
import org.openiot.gsn.beans.DataField;
import org.openiot.gsn.beans.InputStream;
import org.openiot.gsn.beans.Modifications;
import org.openiot.gsn.beans.StreamSource;
import org.openiot.gsn.beans.VSensorConfig;
import org.openiot.gsn.wrappers.AbstractWrapper;
import org.openiot.gsn.wrappers.WrappersUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import org.jibx.runtime.BindingDirectory;
import org.jibx.runtime.IBindingFactory;
import org.jibx.runtime.IUnmarshallingContext;
import org.jibx.runtime.JiBXException;
public class VSensorLoader extends Thread {
public static final String VSENSOR_POOL = "VSENSOR-POOL";
public static final String STREAM_SOURCE = "STREAM-SOURCE";
public static final String INPUT_STREAM = "INPUT-STREAM";
private static transient Logger logger = LoggerFactory.getLogger ( VSensorLoader.class );
/**
* Mapping between the AddressBean and DataSources
*/
private final List < AbstractWrapper > activeWrappers = new ArrayList< AbstractWrapper >( );
//private StorageManager sm = StorageManager.getInstance ( );
private String pluginsDir;
private boolean isActive = true;
private static int VSENSOR_LOADER_THREAD_COUNTER = 0;
private static VSensorLoader singleton = null;
private ArrayList<VSensorStateChangeListener> changeListeners = new ArrayList<VSensorStateChangeListener>();
public void addVSensorStateChangeListener(VSensorStateChangeListener listener) {
if (!changeListeners.contains(listener))
changeListeners.add(listener);
}
public void removeVSensorStateChangeListener(VSensorStateChangeListener listener) {
changeListeners.remove(listener);
}
public boolean fireVSensorLoading(VSensorConfig config) {
for (VSensorStateChangeListener listener : changeListeners)
if (!listener.vsLoading(config))
return false;
return true;
}
public boolean fireVSensorUnLoading(VSensorConfig config) {
for (VSensorStateChangeListener listener : changeListeners)
if (!listener.vsUnLoading(config)) {
logger.error("Unloading failed !",new RuntimeException("Unloading : "+config.getName()+" is failed."));
return false;
}
return true;
}
public VSensorLoader() {
}
public VSensorLoader ( String pluginsPath ) {
this.pluginsDir = pluginsPath;
}
public static VSensorLoader getInstance(String path) {
if (singleton == null)
singleton = new VSensorLoader(path);
return singleton;
}
public void startLoading() {
Thread thread = new Thread ( this );
thread.setName ( "VSensorLoader-Thread" + VSENSOR_LOADER_THREAD_COUNTER++ );
thread.start ( );
}
public void run ( ) {
if ( Main.getStorage((VSensorConfig)null) == null || Main.getWindowStorage() == null ) { // Checks only if the default storage and the window storage are defined.
logger.error( "The Storage Manager shouldn't be null, possible a BUG." );
return;
}
while ( isActive ) {
try {
loadPlugin ( );
} catch ( Exception e ) {
logger.error ( e.getMessage ( ) , e );
}
}
}
public void loadVirtualSensor(String vsConfigurationFileContent, String fileName) throws Exception {
logger.info("Creating VS: "+fileName);
String filePath = getVSConfigurationFilePath(fileName);
File file = new File(filePath);
if (!file.exists()) {
try {
logger.info("Creating VS at: "+filePath);
buildVSensorConfig(vsConfigurationFileContent);
// Create the VS configuration file
Writer fw = new BufferedWriter(new FileWriter(filePath, true));
fw.write(vsConfigurationFileContent);
fw.flush();
fw.close();
// Try to load it
//if ( ! loadPlugin(fileName)) {
// throw new Exception("Failed to load the Virtual Sensor: " + fileName + " because there is syntax error in the configuration file. Please check the configuration file and try again.");
//}
}
catch (Exception e) {
logger.warn(e.getMessage(), e);
if (file.exists()) file.delete();
throw e;
}
} else {
logger.warn("The configuration file:" + filePath + " already exist.");
throw new Exception("The configuration file:" + filePath + " already exist.");
}
}
private VSensorConfig buildVSensorConfig(String fileContent) throws VirtualSensorInitializationFailedException{
java.io.InputStream is = new ByteArrayInputStream(fileContent.getBytes());
IUnmarshallingContext uctx;
try {
IBindingFactory bfact = BindingDirectory.getFactory( VSensorConfig.class );
uctx = bfact.createUnmarshallingContext( );
} catch ( JiBXException e1 ) {
logger.error( e1.getMessage( ) , e1 );
throw new VirtualSensorInitializationFailedException("Unable to create config parser: "+e1.getMessage());
}
VSensorConfig configuration;
try {
configuration = ( VSensorConfig ) uctx.unmarshalDocument( is , null );
} catch (JiBXException e) {
logger.error(e.getMessage());
e.printStackTrace();
throw new VirtualSensorInitializationFailedException("VS Malformed Configuration: "+e.getMessage());
}
//configuration.setFileName( file );
if ( !configuration.validate( ) )
throw new VirtualSensorInitializationFailedException("VS Configuration Invalid");
return configuration;
}
public static String getVSConfigurationFilePath(String fileName) {
return Main.DEFAULT_VIRTUAL_SENSOR_DIRECTORY + File.separator + fileName + ".xml";
}
public synchronized void loadPlugin() throws SQLException, JiBXException {
Modifications modifications = getUpdateStatus(pluginsDir);
ArrayList<VSensorConfig> removeIt = modifications.getRemove();
ArrayList<VSensorConfig> addIt = modifications.getAdd();
for (VSensorConfig configFile : removeIt) {
removeVirtualSensor(configFile);
}
for (VSensorConfig vs : addIt) {
loadPlugin(vs);
}
try {
Thread.sleep ( 3000 );
} catch ( InterruptedException e ) {
logger.error ( e.getMessage ( ) , e );
}
}
public synchronized boolean loadPlugin(String fileFilterName) throws SQLException, JiBXException {
Modifications modifications = getUpdateStatus(pluginsDir, fileFilterName);
ArrayList<VSensorConfig> addIt = modifications.getAdd();
boolean found = false;
for (VSensorConfig config : addIt){
if (config.getName().equals(fileFilterName)) {
found = true;
break;
}
}
if (!found)
return false;
else
return loadPlugin(addIt.get(0));
}
private synchronized boolean loadPlugin(VSensorConfig vs) throws SQLException, JiBXException {
if (!isVirtualSensorValid(vs))
return false;
VirtualSensor pool = new VirtualSensor(vs);
try {
if (createInputStreams(pool) == false) {
logger.error("loading the >" + vs.getName() + "< virtual sensor is stoped due to error(s) in preparing the input streams.");
return false;
}
} catch (InstantiationException e2) {
logger.error(e2.getMessage(), e2);
} catch (IllegalAccessException e2) {
logger.error(e2.getMessage(), e2);
}
try {
if (!Main.getStorage(vs).tableExists(vs.getName(), vs.getOutputStructure()))
Main.getStorage(vs).executeCreateTable(vs.getName(), vs.getOutputStructure(), pool.getConfig().getIsTimeStampUnique());
else
logger.info("Reusing the existing " + vs.getName() + " table.");
} catch (SQLException e) {
if (e.getMessage().toLowerCase().contains("table already exists")) {
logger.error(e.getMessage());
if (logger.isInfoEnabled()) logger.info(e.getMessage(), e);
logger.error(new StringBuilder().append("Loading the virtual sensor specified in the file : ").append(vs.getFileName()).append(" failed").toString());
logger.error(new StringBuilder().append("The table : ").append(vs.getName()).append(" is exists in the database specified in :").append(
Main.getContainerConfig().getContainerFileName()).append(".").toString());
logger.error("Solutions : ");
logger.error(new StringBuilder().append("1. Change the virtual sensor name, in the : ").append(vs.getFileName()).toString());
logger.error(new StringBuilder().append("2. Change the URL of the database in ").append(Main.getContainerConfig().getContainerFileName()).append(
" and choose another database.").toString());
logger.error(new StringBuilder().append("3. Rename/Move the table with the name : ").append(Main.getContainerConfig().getContainerFileName()).append(" in the database.")
.toString());
logger.error(new StringBuilder().append("4. Change the overwrite-tables=\"true\" (be careful, this will overwrite all the data previously saved in ").append(
vs.getName()).append(" table )").toString());
} else {
logger.error(e.getMessage(), e);
}
return false;
}
logger.warn(new StringBuilder("adding : ").append(vs.getName()).append(" virtual sensor[").append(vs.getFileName()).append("]").toString());
// Check if sensor needs to be announced to LSM
/*
if (vs.getPublishToLSM()==true) {
// Try to announce sensor to LSM
boolean success = LSMRepository.getInstance().announceSensor(vs);
if (!success) {
logger.warn("Failed to register sensor to LSM: " + Utils.identify(vs));
return false;
}
}*/
if (Mappings.addVSensorInstance(pool)) {
try {
fireVSensorLoading(pool.getConfig());
pool.start();
} catch (VirtualSensorInitializationFailedException e1) {
logger.error("Creating the virtual sensor >" + vs.getName() + "< failed.", e1);
removeVirtualSensor(vs);
return false;
}
} else {
//TODO: release all vs resources
}
return true;
}
private void removeVirtualSensor(VSensorConfig configFile) {
logger.warn ( new StringBuilder ( ).append ( "removing : " ).append ( configFile.getName ( ) ).toString ( ) );
VirtualSensor sensorInstance = Mappings.getVSensorInstanceByFileName ( configFile.getFileName ( ) );
Mappings.removeFilename ( configFile.getFileName ( ) );
removeAllVSResources ( sensorInstance );
}
public boolean isVirtualSensorValid(VSensorConfig configuration) {
for ( InputStream is : configuration.getInputStreams ( ) ) {
if ( !is.validate ( ) ) {
logger.error ( new StringBuilder ( ).append ( "Adding the virtual sensor specified in " ).append ( configuration.getFileName ( ) ).append ( " failed because of one or more problems in configuration file." )
.toString ( ) );
logger.error ( new StringBuilder ( ).append ( "Please check the file and try again" ).toString ( ) );
return false;
}
}
String vsName = configuration.getName ( );
if ( Mappings.getVSensorConfig ( vsName ) != null ) {
logger.error ( new StringBuilder ( ).append ( "Adding the virtual sensor specified in " ).append ( configuration.getFileName ( ) ).append ( " failed because the virtual sensor name used by " )
.append ( configuration.getFileName ( ) ).append ( " is already used by : " ).append ( Mappings.getVSensorConfig ( vsName ).getFileName ( ) ).toString ( ) );
logger.error ( "Note that the virtual sensor name is case insensitive and all the spaces in it's name will be removed automatically." );
return false;
}
if ( !isValidJavaIdentifier( vsName ) ) {
logger.error ( new StringBuilder ( ).append ( "Adding the virtual sensor specified in " ).append ( configuration.getFileName ( ) ).append (
" failed because the virtual sensor name is not following the requirements : " ).toString ( ) );
logger.error ( "The virtual sensor name is case insensitive and all the spaces in it's name will be removed automatically." );
logger.error ( "That the name of the virutal sensor should starting by alphabetical character and they can contain numerical characters afterwards." );
return false;
}
return true;
}
static protected boolean isValidJavaIdentifier(final String name) {
boolean valid = false;
while (true) {
if (false == Character.isJavaIdentifierStart(name.charAt(0)))
break;
valid = true;
final int count = name.length();
for (int i = 1; i < count; i++) {
if (false == Character.isJavaIdentifierPart(name.charAt(i))) {
valid = false;
break;
}
}
break;
}
return valid;
}
public void removeAllVSResources ( VirtualSensor pool ) {
VSensorConfig config = pool.getConfig ( );
pool.closePool ( );
final String vsensorName = config.getName ( );
if ( logger.isInfoEnabled ( ) ) logger.info ( new StringBuilder ( ).append ( "Releasing previously used resources used by [" ).append ( vsensorName ).append ( "]." ).toString ( ) );
for ( InputStream inputStream : config.getInputStreams ( ) ) {
for ( StreamSource streamSource : inputStream.getSources ( ) )
releaseStreamSource(streamSource);
inputStream.release();
}
try {
// delete table so that future creation with different type does not crash
Main.getStorage(vsensorName).executeDropTable(vsensorName);
} catch (SQLException ex) {
logger.warn("Problem deleting sensor tables.", ex);
}
// sm.renameTable(vsensorName,vsensorName+"Before"+System.currentTimeMillis());
logger.debug("Total change Listeners:"+changeListeners.size());
fireVSensorUnLoading(pool.getConfig());
// this.sm.dropTable ( config.getName ( ) );
}
public void releaseStreamSource(StreamSource streamSource) {
final AbstractWrapper wrapper = streamSource.getWrapper ( );
streamSource.getInputStream().getRenamingMapping().remove(streamSource.getAlias());
try {
wrapper.removeListener(streamSource);
} catch (SQLException e) {
logger.error(e.getMessage(),e);
logger.error("Release the resources failed !");
}
if ( !wrapper.isActive()) {//This stream source is the only listener
logger.debug("The wrapper:"+wrapper.getName()+" is removed.");
activeWrappers.remove ( wrapper );
}else {
logger.debug("The wrapper:"+wrapper.getName()+" is not released as it is still used by other virtual sensors.");
}
}
public static Modifications getUpdateStatus(String virtualSensorsPath) {
return getUpdateStatus(virtualSensorsPath, null);
}
public static Modifications getUpdateStatus(String virtualSensorsPath, String filterFileName) {
ArrayList<String> remove = new ArrayList<String>();
ArrayList<String> add = new ArrayList<String>();
String[] previous = Mappings.getAllKnownFileName();
FileFilter filter = new FileFilter ( ) {
public boolean accept ( File file ) {
if ( !file.isDirectory ( ) && file.getName ( ).endsWith ( ".xml" ) && !file.getName ( ).startsWith ( "." ) ) return true;
return false;
}
};
File files[] = new File(virtualSensorsPath).listFiles(filter);
Arrays.sort(files, new Comparator<File>(){
@Override
public int compare(File a, File b) {
return a.getName().compareTo(b.getName());
}});
// --- preparing the remove list
// Removing those in the previous which are not existing the new files
// or modified.
main:
for (String pre : previous) {
for (File curr : files)
if (pre.equals(curr.getAbsolutePath()) && (Mappings.getLastModifiedTime(pre) == curr.lastModified()))
continue main;
remove.add(pre);
}
// ---adding the new files to the Add List a new file should added if
//
// 1. it's just deployed.
// 2. it's modification time changed.
main:
for (File cur : files) {
for (String pre : previous)
if (cur.getAbsolutePath().equals(pre) && (cur.lastModified() == Mappings.getLastModifiedTime(pre)))
continue main;
add.add(cur.getAbsolutePath());
}
Modifications result = new Modifications(add, remove);
return result;
}
/**
* The properties file contains information on wrappers for stream sources.
* FIXME : The body of CreateInputStreams is incomplete b/c in the case of an
* error it should remove the resources.
* @throws IllegalAccessException
* @throws InstantiationException
*/
public boolean createInputStreams ( VirtualSensor pool ) throws InstantiationException, IllegalAccessException {
if ( logger.isDebugEnabled ( ) ) logger.debug ( new StringBuilder ( ).append ( "Preparing input streams for: " ).append ( pool.getConfig().getName ( ) ).toString ( ) );
if ( pool.getConfig().getInputStreams ( ).size ( ) == 0 ) logger.warn ( new StringBuilder ( "There is no input streams defined for *" ).append ( pool.getConfig().getName ( ) ).append ( "*" ).toString ( ) );
for ( Iterator < InputStream > inputStreamIterator = pool.getConfig().getInputStreams ( ).iterator ( ) ; inputStreamIterator.hasNext ( ) ; ) {
InputStream inputStream = inputStreamIterator.next ( );
for ( StreamSource dataSouce : inputStream.getSources ( )) {
if ( prepareStreamSource ( pool.getConfig(),inputStream , dataSouce ) == false ) return false;
// TODO if one stream source fails all the resources used by other successfuly initialized stream sources
// for this input stream should be released.
}
inputStream.setPool (pool );
}
return true;
}
/**
* Tries to find a wrapper first from the active wrappers or instantiates a new one and puts it in the cache.
* @param addressBean
* @return
* @throws InstantiationException
* @throws IllegalAccessException
* FIXME: COPIED_FOR_SAFE_STOAGE
*/
public AbstractWrapper findWrapper(AddressBean addressBean) throws InstantiationException, IllegalAccessException {
if ( Main.getWrapperClass ( addressBean.getWrapper ( ) ) == null ) {
logger.error ( "The wrapper >" + addressBean.getWrapper ( ) + "< is not defined in the >" + WrappersUtil.DEFAULT_WRAPPER_PROPERTIES_FILE + "< file." );
return null;
}
AbstractWrapper wrapper = ( AbstractWrapper ) Main.getWrapperClass ( addressBean.getWrapper ( ) ).newInstance ( );
wrapper.setActiveAddressBean ( addressBean );
boolean initializationResult = wrapper.initialize ( );
if ( initializationResult == false )
return null;
try {
logger.debug("Wrapper name: "+wrapper.getWrapperName()+ " -- view name "+ wrapper.getDBAliasInStr());
if (!Main.getWindowStorage().tableExists(wrapper.getDBAliasInStr(),wrapper.getOutputFormat()))
Main.getWindowStorage().executeCreateTable ( wrapper.getDBAliasInStr ( ) , wrapper.getOutputFormat ( ),wrapper.isTimeStampUnique() );
} catch ( SQLException e ) {
logger.error ( e.getMessage ( ) , e );
return null;
}
// wrapper.start ( ); //moved to the VSensorPool
activeWrappers.add ( wrapper );
return wrapper;
}
public boolean prepareStreamSource ( VSensorConfig vsensorConfig,InputStream inputStream , StreamSource streamSource ) throws InstantiationException, IllegalAccessException {
streamSource.setInputStream(inputStream);
AbstractWrapper wrapper = null;
for ( AddressBean addressBean : streamSource.getAddressing ( ) ) {
addressBean.setInputStreamName(inputStream.getInputStreamName());
addressBean.setVirtualSensorName(vsensorConfig.getName());
wrapper = findWrapper(addressBean);
try {
if (wrapper!=null && prepareStreamSource( streamSource,wrapper.getOutputFormat(),wrapper))
break;
else
//TODO: remove wrapper from activeWrappers and release its resources
wrapper=null;
} catch (SQLException e) {
logger.error(e.getMessage(),e);
logger.error("Preparation of the stream source failed : "+streamSource.getAlias()+ " from the input stream : "+inputStream.getInputStreamName());
}
}
return (wrapper!=null);
}
public boolean prepareStreamSource ( StreamSource streamSource ,DataField[] outputformat, AbstractWrapper wrapper ) throws InstantiationException, IllegalAccessException, SQLException {
if (outputformat==null) {
logger.error("Preparing the stream source failed because the wrapper : "+wrapper.getWrapperName()+" returns null for the >getOutputStructure< method!");
return false;
}
streamSource.setWrapper ( wrapper );
streamSource.getInputStream().addToRenamingMapping(streamSource.getAlias(), streamSource.getUIDStr());
return true;
}
public void stopLoading ( ) {
this.isActive = false;
this.interrupt ( );
for ( String configFile : Mappings.getAllKnownFileName ( ) ) {
VirtualSensor sensorInstance = Mappings.getVSensorInstanceByFileName ( configFile );
removeAllVSResources ( sensorInstance );
logger.warn ( "Removing the resources associated with : " + sensorInstance.getConfig ( ).getFileName ( ) + " [done]." );
}
try {
Main.getWindowStorage().shutdown( );
Iterator<VSensorConfig> iter = Mappings.getAllVSensorConfigs();
while (iter.hasNext()) {
Main.getStorage(iter.next()).shutdown();
}
} catch ( SQLException e ) {
logger.error(e.getMessage(),e);
}finally {
System.exit(0);
}
}
}