/** * 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/VSensorLoader.java * * @author Mehdi Riahi * @author gsn_devs * @author Sofiane Sarni * @author Ali Salehi * @author Mehdi Riahi * @author Timotee Maret * @author Julien Eberle * */ package ch.epfl.gsn; import java.io.*; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import org.slf4j.LoggerFactory; import ch.epfl.gsn.Main; import ch.epfl.gsn.Mappings; import ch.epfl.gsn.VSensorLoader; import ch.epfl.gsn.VSensorStateChangeListener; import ch.epfl.gsn.VirtualSensor; import ch.epfl.gsn.VirtualSensorInitializationFailedException; import ch.epfl.gsn.beans.AddressBean; import ch.epfl.gsn.beans.DataField; import ch.epfl.gsn.beans.InputStream; import ch.epfl.gsn.beans.Modifications; import ch.epfl.gsn.beans.StreamSource; import ch.epfl.gsn.beans.VSensorConfig; import ch.epfl.gsn.wrappers.AbstractWrapper; import ch.epfl.gsn.wrappers.WrappersUtil; import org.slf4j.Logger; 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 String pluginsDir; private boolean isActive = true; private ArrayList<VSensorStateChangeListener> changeListeners = new ArrayList<VSensorStateChangeListener>(); private static int VSENSOR_LOADER_THREAD_COUNTER = 0; private static VSensorLoader singleton = null; private static transient Logger logger = LoggerFactory.getLogger ( VSensorLoader.class ); 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 ); } try { Thread.sleep ( 3000 ); } catch ( InterruptedException e ) { logger.error ( e.getMessage ( ) , e ); } } } public synchronized void loadVirtualSensor(String vsConfigurationFileContent, String fileName) throws Exception { String filePath = getVSConfigurationFilePath(fileName); File file = new File(filePath); if (!file.exists()) { try { // 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."); } } public static String getVSConfigurationFilePath(String fileName) { return Main.virtualSensorDirectory + File.separator + fileName + ".xml"; } public synchronized void loadPlugin() throws SQLException { Modifications modifications = getUpdateStatus(pluginsDir); ArrayList<VSensorConfig> removeIt = modifications.getRemove(); ArrayList<VSensorConfig> addIt = modifications.getAdd(); for (VSensorConfig configFile : removeIt) { removeVirtualSensor(configFile); } for (VSensorConfig vs : addIt) { try{ loadPlugin(vs); }catch(Exception e){ logger.error("Unable to load VSensor " + vs.getName() + ", retrying later... : "+ e.getMessage()); } } } public synchronized boolean loadPlugin(String fileFilterName) throws SQLException { 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 { 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 (Exception e) { removeAllVSResources(pool); if (e.getMessage().toLowerCase().contains("table already exists")) { logger.error("Loading the virtual sensor from " + vs.getFileName() + " failed, because the table " + vs.getName() + " already exists in the database specified in " + Main.getContainerConfig().getContainerFileName() + "."); logger.info("Solutions : "); logger.info("1. Change the virtual sensor name, in the : " + vs.getFileName()); logger.info("2. Change the URL of the database in " + Main.getContainerConfig().getContainerFileName() + " and choose another database."); logger.info("3. Rename/Move the table with the name : " + Main.getContainerConfig().getContainerFileName() + " in the database."); logger.info("4. Change the overwrite-tables=\"true\" (be careful, this will overwrite all the data previously saved in " + vs.getName() + " table )"); } else { logger.error(e.getMessage(), e); } return false; } logger.info("adding : " + vs.getName() + " virtual sensor[" + vs.getFileName() + "]"); 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 { removeAllVSResources(pool); } return true; } private void removeVirtualSensor(VSensorConfig configFile) { logger.info ("removing : " + configFile.getName ( )); 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("Adding the virtual sensor specified in " + configuration.getFileName ( ) + " failed because of one or more problems in configuration file." ); logger.info("Please check the file and try again"); return false; } } String vsName = configuration.getName ( ); if ( Mappings.getVSensorConfig ( vsName ) != null ) { logger.error("Adding the virtual sensor specified in " + configuration.getFileName ( ) + " failed because the virtual sensor name used by " + configuration.getFileName ( ) + " is already used by : " + Mappings.getVSensorConfig ( vsName ).getFileName ( )); logger.info( "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("Adding the virtual sensor specified in "+configuration.getFileName()+" failed because the virtual sensor name is not following the requirements : "); logger.info ( "The virtual sensor name is case insensitive and all the spaces in it's name will be removed automatically." ); logger.info ( "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 ( ); logger.info ("Releasing previously used resources used by [" + vsensorName + "]."); for ( InputStream inputStream : config.getInputStreams ( ) ) { for ( StreamSource streamSource : inputStream.getSources ( ) ) releaseStreamSource(streamSource); inputStream.release(); } logger.debug("Total change Listeners:"+changeListeners.size()); fireVSensorUnLoading(pool.getConfig()); } 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("Release the resources failed !" + e.getMessage()); } } 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 { 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 ( ) ); ArrayList<StreamSource> sources = new ArrayList<StreamSource>(); ArrayList<InputStream> streams = new ArrayList<InputStream>(); 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 )){ for (StreamSource ss : sources){ releaseStreamSource(ss); } for (InputStream is : streams){ is.release(); } return false; } sources.add(dataSouce); } streams.add(inputStream); inputStream.setPool (pool ); } return true; } /** * Instantiate the wrapper from its addressBean. * if it doesn't return null, the calling class is responsible for releasing the resources of the wrapper. * @param addressBean * @return * @throws InstantiationException * @throws IllegalAccessException * FIXME: COPIED_FOR_SAFE_STOAGE */ public AbstractWrapper createWrapper(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_wrapper ( ); 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 ( Exception e ) { try{ wrapper.releaseResources(); //releasing resources }catch (SQLException sql){ logger.error ( sql.getMessage ( ) , sql ); } logger.error ( e.getMessage ( ) , e ); return null; } 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 = createWrapper(addressBean); try { if (wrapper!=null && prepareStreamSource( streamSource,wrapper.getOutputFormat(),wrapper)) break; else if (wrapper!=null){ wrapper.releaseResources(); } wrapper=null; } catch (Exception e) { if (wrapper!=null){ try{ wrapper.releaseResources(); }catch(SQLException sql){ logger.error(sql.getMessage(),sql); } } logger.error("Preparation of the stream source failed : "+streamSource.getAlias()+ " from the input stream : "+inputStream.getInputStreamName()+". " + e.getMessage(),e); } } 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.info( "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); } } }