/* * Data Hub Service (DHuS) - For Space data distribution. * Copyright (C) 2013,2014,2015 GAEL Systems * * This file is part of DHuS software sources. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package fr.gael.dhus.service; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.text.ParseException; import java.util.Date; import java.util.Iterator; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.ContextClosedEvent; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import fr.gael.dhus.DHuS; import fr.gael.dhus.database.dao.SynchronizerDao; import fr.gael.dhus.database.object.SynchronizerConf; import fr.gael.dhus.database.object.config.system.ExecutorConfiguration; import fr.gael.dhus.service.exception.InvokeSynchronizerException; import fr.gael.dhus.sync.Executor; import fr.gael.dhus.sync.MetaExecutor; import fr.gael.dhus.sync.Synchronizer; import fr.gael.dhus.sync.SynchronizerStatus; import fr.gael.dhus.system.config.ConfigurationManager; /** * Manages the {@link Executor}, {@link Synchronizer} and * {@link SynchronizerConf}. */ @Service public class SynchronizerService implements ISynchronizerService { /** Log. */ private static final Logger LOGGER = LogManager.getLogger(SynchronizerService.class); /** Manages {@link SynchronizerConf}. */ @Autowired private SynchronizerDao synchronizerDao; /** Configuration (etc/dhus.xml). */ @Autowired private ConfigurationManager cfgManager; @Autowired private SecurityService secu; /** An instance of {@link Executor}, running the synchronization. */ private final Executor executor = MetaExecutor.getInstance(); /** * Returns the status of the {@link Executor}. * @return {@link Status#RUNNING} or {@link Status#STOPPED}. */ @Override public Status getStatus () { return executor.isRunning ()? Status.RUNNING: Status.STOPPED; } /** * If the {@link Executor} is not started yet, add all the active * {@link Synchronizer} in the {@link Executor} and starts the * {@link Executor}. */ @Override public void startSynchronization () { if (!executor.isRunning ()) { for (SynchronizerConf sc: synchronizerDao.getActiveSynchronizers ()) { try { Synchronizer sync = instanciate (sc); executor.addSynchronizer (sync); } catch (InvokeSynchronizerException ex) { LOGGER.error ("Failed to invoke a Synchronizer", ex); } } executor.start (true); // FIXME: true or false? } } /** * Stops the {@link Executor}. */ @Override public void stopSynchronization () { executor.stop (); executor.removeAllSynchronizers (); } /** * Returns a {@link SynchronizerConf} by its identifier. * @param id {@link SynchronizerConf} identifier. * @return a {@link SynchronizerConf} or {@code null} if not found. */ @Override public SynchronizerConf getSynchronizerConfById (long id) { return synchronizerDao.read (id); } /** * Returns a List of {@link SynchronizerConf}. * @return a List of {@link SynchronizerConf}. */ @Override public Iterator<SynchronizerConf> getSynchronizerConfs () { return synchronizerDao.getAllSynchronizerConfs (null); } @Override public Iterator<SynchronizerConf> getSynchronizerConfs (String type) { return synchronizerDao.getAllSynchronizerConfs (type); } /** * Returns how many {@link SynchronizerConf} exist in the database. * @return count. */ @Override public int count () { return synchronizerDao.count (); } /** * Creates a new {@link Synchronizer} from the given type and cron expression. * The newly created {@link Synchronizer} is flagged as not active and is not * run by the {@link Executor}. * * @see SynchronizerConf#setCronExpression(String) * * @param label see {@link SynchronizerConf#getLabel()}, can be null. * @param type see {@link SynchronizerConf#getType()}. * @param cron_exp the pace of the synchronization. * * @return the newly created {@link SynchronizerConf}. * * @throws ParseException failed to parse the given cron expression. */ @Override public SynchronizerConf createSynchronizer (String label, String type, String cron_exp) throws ParseException { SynchronizerConf sc = new SynchronizerConf (); sc.setLabel (label); sc.setType (type); sc.setCronExpression (cron_exp); sc.setCreated (new Date()); sc.setModified (sc.getCreated ()); sc = synchronizerDao.create(sc); LOGGER.info("Synchronizer#" + sc.getId() + " created by user " + secu.getCurrentUser().getUsername()); return sc; } /** * Removes a {@link Synchronizer} with the given identifier. * The removed {@link Synchronizer} won't be run any longer. * @param id {@link SynchronizerConf} identifier. */ @Override public void removeSynchronizer (long id) { SynchronizerConf sc = synchronizerDao.read (id); if (sc != null) { try { if (sc.getActive ()) { executor.removeSynchronizer (sc); } } finally { synchronizerDao.delete (sc); LOGGER.info("Synchronizer#" + sc.getId() + " deleted by user " + secu.getCurrentUser().getUsername()); } } } /** * Sets a {@link Synchronizer} active and adds it in the executor. * @param id {@link SynchronizerConf} identifier. * @throws InvokeSynchronizerException Synchronizer invocation failure. */ @Transactional @Override public void activateSynchronizer (long id) throws InvokeSynchronizerException { SynchronizerConf sc = synchronizerDao.read (id); if (sc != null) { boolean wasActive = true; if (!sc.getActive ()) { sc.setActive (true); wasActive = false; } Synchronizer s = instanciate (sc); executor.addSynchronizer (s); if (!wasActive) { synchronizerDao.update (sc); LOGGER.info("Synchronizer#" + sc.getId() + " started by user " + secu.getCurrentUser().getUsername()); } } } /** * Sets a {@link Synchronizer} inactive and removes it from the executor. * @param id {@link SynchronizerConf} identifier. */ @Transactional @Override public void deactivateSynchronizer (long id) { SynchronizerConf sc = synchronizerDao.read (id); if (sc != null && sc.getActive ()) { try { // Removes the synchronizer from the Executor Synchronizer s = executor.removeSynchronizer (sc); if (s != null && s.getSynchronizerConf () != null) { sc = s.getSynchronizerConf (); } LOGGER.info("Synchronizer#" + sc.getId() + " stopped by user " + secu.getCurrentUser().getUsername()); } finally { sc.setActive (false); synchronizerDao.update (sc); } } } /** * Enables the batch mode of the {@link Executor}. * @see Executor#enableBatchMode(boolean) * @param enable {@code true} to enable the batch mode. */ @Override public void enableBatchMode (boolean enable) { executor.enableBatchMode (enable); } /** * Returns {@code true} if the batch mod is enabled. * @see Executor#isBatchModeEnabled() * @return {@code true} if the batch mod is enabled. */ @Override public boolean isBatchModeEnabled () { return executor.isBatchModeEnabled (); } /** * Saves the configuration of a {@link Synchronizer}. * First it deactivates the synchronizer with the given ID, then it saves its * configuration, then it reactivates the synchronizer if its {@code active} * field is set to {@code true}. * @param sc configuration. * @throws InvokeSynchronizerException if the given configuration does not * allow instanciation of a {@link Synchronizer}. */ @Override public void saveSynchronizerConf (SynchronizerConf sc) throws InvokeSynchronizerException { long id = sc.getId (); // the corresponding synchronizer must be removed from the Executor // before we save its configuration in the database. deactivateSynchronizer(id); sc.setModified (new Date()); this.synchronizerDao.update (sc); if (sc.getActive ()) { activateSynchronizer (id); } LOGGER.info("Synchronizer#" + sc.getId() + " modified by user " + secu.getCurrentUser().getUsername()); } /** * Saves the given synchronizer's configuration back in the databse. * This method is intended to be used by Synchronizers themselves, this * method does no tests at all to avoid deadlocks. * <p> * This method is unsafe and you should probably be using * {@link #saveSynchronizerConf(SynchronizerConf)} instead. * @param s to save. */ @Override @Transactional (propagation = Propagation.REQUIRES_NEW) public void saveSynchronizer (Synchronizer s) { this.synchronizerDao.update (s.getSynchronizerConf ()); } /** * Returns the status of the given {@link Synchronizer}. * @param sc an instance of SynchronizerConf that exists in the database. * @return status. */ @Override public SynchronizerStatus getStatus(SynchronizerConf sc) { if (!this.executor.isRunning ()) { return new SynchronizerStatus (SynchronizerStatus.Status.STOPPED, new Date(0L), "Executor is not running"); } if (!sc.getActive ()) { return SynchronizerStatus.makeStoppedStatus (sc.getModified ()); } SynchronizerStatus ss = this.executor.getSynchronizerStatus (sc); if (ss == null) { return SynchronizerStatus.makeUnknownStatus (); } return ss; } /** * Creates a new Synchronizer instance of the class returned from * {@link SynchronizerConf#getType()}. */ private static Synchronizer instanciate (SynchronizerConf sc) throws InvokeSynchronizerException { String type = sc.getType (); if (!type.contains (".")) { type = "fr.gael.dhus.sync.impl." + type; } try { Class<?> type_class = Class.forName (type); if (!Synchronizer.class.isAssignableFrom (type_class)) { throw new InvokeSynchronizerException ( "type " + type + " is not Synchronizer"); } Class<Synchronizer> sync_impl = (Class<Synchronizer>) type_class; Constructor<Synchronizer> con = sync_impl.getConstructor (SynchronizerConf.class); return con.newInstance (sc); } // Only catch block below, until the end of the method. catch (ClassNotFoundException ex) { throw new InvokeSynchronizerException ( "Cannot instanciate Synchronizer, type=" + type, ex); } catch (NoSuchMethodException ex) { throw new InvokeSynchronizerException ( type + " has no constructor with a SynchronizerConf param", ex); } catch (InstantiationException ex) { throw new InvokeSynchronizerException ( type + " is an abstract class", ex); } catch (IllegalAccessException ex) { throw new InvokeSynchronizerException ( "Constructor is not public, type=" + type, ex); } catch (IllegalArgumentException ex) { throw new InvokeSynchronizerException (ex); } catch (InvocationTargetException ex) { String cause = ex.getCause().getMessage(); if (cause == null || cause.isEmpty()) { cause = ex.getCause().toString(); } throw new InvokeSynchronizerException ( type + " has thrown an exception while being invoked, cause: " + cause, ex); } } /** * Spring ContextClosedEvent listener to terminate the {@link Executor}. * <b>YOU MUST NOT CALL THIS METHOD!</b> */ @Override public void onApplicationEvent (ContextClosedEvent event) { LOGGER.debug ("Synchronizer: event " + event + " received"); if (event == null) { return; } // Terminates the Executor LOGGER.info ("Synchronization: Executor is terminating"); executor.terminate (); executor.removeAllSynchronizers (); } /** * Starts the {@link Executor} after every webapps have been installed. * Called by main start in {@link DHuS} * <b>YOU MUST NOT CALL THIS METHOD!</b> */ public void init() { // Starts the Executor if not started yet if (!this.executor.isRunning ()) { ExecutorConfiguration cfg = this.cfgManager.getExecutorConfiguration (); if (cfg.isEnabled ()) { this.executor.enableBatchMode (cfg.isBatchModeEnabled ()); startSynchronization (); // Adds every active synchronizer and starts LOGGER.info ("Synchronization: Starting the Executor (batchmode: " + (cfg.isBatchModeEnabled () ? "on": "off") + ')'); } } } }