Java tutorial
/* * WebTop Services is a Web Application framework developed by Sonicle S.r.l. * Copyright (C) 2014 Sonicle S.r.l. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by * the Free Software Foundation with the addition of the following permission * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED * WORK IN WHICH THE COPYRIGHT IS OWNED BY SONICLE, SONICLE DISCLAIMS THE * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * 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 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 or write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA. * * You can contact Sonicle S.r.l. at email address sonicle@sonicle.com * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License * version 3, these Appropriate Legal Notices must retain the display of the * Sonicle logo and Sonicle copyright notice. If the display of the logo is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Copyright (C) 2014 Sonicle S.r.l.". */ package com.sonicle.webtop.core.app; import com.sonicle.commons.LangUtils; import com.sonicle.commons.db.DbUtils; import com.sonicle.commons.db.StatementUtils; import com.sonicle.commons.time.DateTimeUtils; import com.sonicle.webtop.core.CoreManager; import com.sonicle.webtop.core.CoreSettings; import com.sonicle.webtop.core.CoreUserSettings; import com.sonicle.webtop.core.admin.CoreAdminManager; import com.sonicle.webtop.core.app.sdk.interfaces.IControllerUserEvents; import com.sonicle.webtop.core.bol.OUpgradeStatement; import com.sonicle.webtop.core.dal.DAOException; import com.sonicle.webtop.core.dal.UpgradeStatementDAO; import com.sonicle.webtop.core.model.ServicePermission; import com.sonicle.webtop.core.sdk.BaseController; import com.sonicle.webtop.core.sdk.BaseJobService; import com.sonicle.webtop.core.sdk.BaseJobService.TaskDefinition; import com.sonicle.webtop.core.sdk.BaseManager; import com.sonicle.webtop.core.sdk.BasePublicService; import com.sonicle.webtop.core.sdk.BaseUserOptionsService; import com.sonicle.webtop.core.sdk.ServiceManifest; import com.sonicle.webtop.core.sdk.ServiceVersion; import com.sonicle.webtop.core.sdk.BaseService; import com.sonicle.webtop.core.sdk.UserProfile; import com.sonicle.webtop.core.sdk.UserProfileId; import com.sonicle.webtop.core.sdk.WTException; import com.sonicle.webtop.core.sdk.WTRuntimeException; import com.sonicle.webtop.core.util.LoggerUtils; import com.sonicle.webtop.core.versioning.AnnotationLine; import com.sonicle.webtop.core.versioning.BaseScriptLine; import com.sonicle.webtop.core.versioning.DataSourceAnnotationLine; import com.sonicle.webtop.core.versioning.IgnoreErrorsAnnotationLine; import com.sonicle.webtop.core.versioning.RequireAdminAnnotationLine; import com.sonicle.webtop.core.versioning.SqlLine; import com.sonicle.webtop.core.versioning.SqlUpgradeScript; import com.zaxxer.hikari.HikariConfig; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.lang.reflect.Constructor; import java.net.URL; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.TimeUnit; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.HierarchicalConfiguration; import org.apache.commons.configuration.XMLConfiguration; import org.apache.commons.lang3.StringUtils; import org.quartz.JobBuilder; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.quartz.impl.matchers.GroupMatcher; import org.slf4j.Logger; import com.sonicle.webtop.core.app.sdk.interfaces.IControllerServiceHooks; import java.util.Collection; import java.util.Map; import java.util.stream.Collectors; /** * * @author malbinola */ public class ServiceManager { private static final Logger logger = WT.getLogger(ServiceManager.class); private static boolean initialized = false; /** * Initialization method. This method should be called once. * * @param wta WebTopApp instance. * @param scheduler Scheduler instance. * @return The instance. */ public static synchronized ServiceManager initialize(WebTopApp wta, Scheduler scheduler) { if (initialized) throw new RuntimeException("Initialization already done"); ServiceManager svcm = new ServiceManager(wta, scheduler); initialized = true; logger.info("Initialized"); return svcm; } public static final String SERVICES_DESCRIPTOR_RESOURCE = "META-INF/webtop-services.xml"; private WebTopApp wta = null; private Scheduler scheduler = null; private final Object lock1 = new Object(); private final Object lock2 = new Object(); private final LinkedHashMap<String, ServiceDescriptor> descriptors = new LinkedHashMap<>(); private final HashMap<String, String> xidToServiceId = new HashMap<>(); private final HashMap<String, String> serviceIdToJsPath = new HashMap<>(); private final LinkedHashMap<String, BaseController> controllers = new LinkedHashMap<>(); private final HashMap<String, String> serviceIdToPublicName = new HashMap<>(); private final HashMap<String, String> publicNameToServiceId = new HashMap<>(); private final LinkedHashMap<String, BaseJobService> jobServices = new LinkedHashMap<>(); /** * Private constructor. * Instances of this class must be created using static initialize method. * @param wta WebTopApp instance. */ private ServiceManager(WebTopApp wta, Scheduler scheduler) { this.wta = wta; this.scheduler = scheduler; init(); } /** * Performs cleanup process. */ public void cleanup() { // Cleanup public/job services BasePublicService publicInst = null; BaseJobService jobInst = null; for (String serviceId : listPrivateServices()) { // Cleanup job service //TODO: effettuare lo shutdown dei task jobInst = jobServices.remove(serviceId); if (jobInst != null) cleanupJobService(jobInst); } jobServices.clear(); descriptors.clear(); xidToServiceId.clear(); serviceIdToJsPath.clear(); serviceIdToPublicName.clear(); publicNameToServiceId.clear(); scheduler = null; wta = null; logger.info("Cleaned up"); } private String getUpgradeTag() { UpgradeStatementDAO upgdao = UpgradeStatementDAO.getInstance(); Connection con = null; try { con = wta.getConnectionManager().getConnection(); String lastTag = upgdao.selectLastTag(con); int pendingUpgrades = upgdao.countPendingByTagType(con, lastTag, OUpgradeStatement.STATEMENT_TYPE_SQL); if (pendingUpgrades == 0) { return String.valueOf(DateTimeUtils.now(true).getMillis()); } else { return lastTag; } } catch (SQLException | DAOException ex) { logger.error("Unable to determine upgrade-tag", ex); return null; } finally { DbUtils.closeQuietly(con); } } private void init() { // Loads services' manifest files from classpath logger.debug("Starting services discovery..."); Map<String, ServiceManifest> manifests = null; try { manifests = discoverServices(); } catch (IOException ex) { throw new RuntimeException("Error during services discovery", ex); } if (!manifests.containsKey(CoreManifest.ID)) throw new RuntimeException("Missing Core manifest"); // Programmatically register Core's manifest // This call must be the first because it performs main dataSource registration ServiceDescriptor coreDesc = registerService(manifests.get(CoreManifest.ID)); boolean dbInitEnabled = isDbInitEnabled(); boolean dbAutoUpgrade = isDbAutoUpgrade(); boolean maintenanceDisabled = isMaintenanceDisabled(); boolean requireAdmin = false; if (maintenanceDisabled) { logger.warn("Special key [{}] is active. Maintenance will be forcedly disabled!", CoreSettings.MAINTENANCE_ENABLED); } // Always insert the maintenance flag. If useless // (requireAdmin=false) it will be removed at the end of the // initialization process. In case of unexpected errors, we are // sure that users cannot connect before admin intervention. boolean oldMaintenance = isInMaintenance(CoreManifest.ID); if (!oldMaintenance) setMaintenance(CoreManifest.ID, true); // Defines a proper upgrade-tag String upgradeTag = getUpgradeTag(); logger.info("Database upgrades will be appended to {}", upgradeTag); // Upgrade database (core) if (coreDesc.isUpgraded()) { requireAdmin = requireAdmin | upgradeServiceDb(coreDesc, upgradeTag, dbAutoUpgrade); } // Register discovered services for (ServiceManifest manifest : manifests.values()) { String sid = manifest.getId(); if (CoreManifest.ID.equals(sid)) continue; ServiceDescriptor desc = registerService(manifest); // Init database if (dbInitEnabled) { //TODO: implementare inizializzazione database } // Upgrade database if (desc.isUpgraded()) { requireAdmin = requireAdmin | upgradeServiceDb(desc, upgradeTag, dbAutoUpgrade); } // Inits classes createController(sid); } // Handle post db-scripts for (String serviceId : listRegisteredServices()) { ServiceDescriptor desc = getDescriptor(serviceId); requireAdmin = requireAdmin | postUpgradeServiceDb(desc, upgradeTag, dbAutoUpgrade); } boolean newMaintenance = true; if (requireAdmin) { logger.info("SysAdmin intervention is needed!"); } else { // Admin support is not needed. Restore the maintenance value // read before startup. This prevents from overriding changes made // by a previous application startup newMaintenance = oldMaintenance; if (maintenanceDisabled) { // Forcedly turn-off maintenance newMaintenance = false; } } if (!newMaintenance) setMaintenance(CoreManifest.ID, false); logger.info("Maintenance mode: {}", newMaintenance); // Instantiate job services int okJobs = 0, failJobs = 0; for (String serviceId : listPrivateServices()) { if (getDescriptor(serviceId).hasJobService()) { if (!isInMaintenance(serviceId)) { if (createJobService(serviceId)) { okJobs++; } else { failJobs++; //TODO: invalidare startup servizio, job non inizializzato? } } } } logger.debug("Instantiated {} of {} job services", okJobs, (okJobs + failJobs)); } /** * Initialize each JobService instance. * Postpone this methos call, WebTopApp instance must be set! */ public void initializeJobServices() { // Inits job services synchronized (jobServices) { for (Entry<String, BaseJobService> entry : jobServices.entrySet()) { initializeJobService(entry.getValue()); } } } /** * Returns if this manager contains a descriptor for the specified service. * @param serviceId The service ID. * @return True, if service is valid, false otherwise. */ public boolean hasService(String serviceId) { return descriptors.containsKey(serviceId); } public String getServiceJsPath(String serviceId) { return serviceIdToJsPath.get(serviceId); } public String getPublicName(String serviceId) { return serviceIdToPublicName.get(serviceId); } /** * Returns the service ID by its public-name (if available). * @param publicName The service's public-name. * @return The service ID if found, null otherwise. */ public String getServiceIdByPublicName(String publicName) { return publicNameToServiceId.get(publicName); } /** * Gets descriptor for a specified service. * @param serviceId The service ID. * @return Service descriptor object. */ ServiceDescriptor getDescriptor(String serviceId) { synchronized (lock1) { if (!descriptors.containsKey(serviceId)) return null; return descriptors.get(serviceId); } } public boolean isCoreService(String serviceId) { return serviceId.equals(CoreManifest.ID); } public boolean hasFullRights(String serviceId) { return isCoreService(serviceId); } private boolean isDbInitEnabled() { SettingsManager setMgr = wta.getSettingsManager(); return LangUtils.value(setMgr.getServiceSetting(CoreManifest.ID, CoreSettings.DB_INIT_ENABLED), true); } private boolean isDbAutoUpgrade() { SettingsManager setMgr = wta.getSettingsManager(); return LangUtils.value(setMgr.getServiceSetting(CoreManifest.ID, CoreSettings.DB_UPGRADE_AUTO), true); } private boolean isMaintenanceDisabled() { SettingsManager setMgr = wta.getSettingsManager(); return LangUtils.value(setMgr.getServiceSetting(CoreManifest.ID, CoreSettings.MAINTENANCE_ENABLED), false); } public boolean isInMaintenance(String serviceId) { SettingsManager setMgr = wta.getSettingsManager(); return LangUtils.value(setMgr.getServiceSetting(serviceId, CoreSettings.MAINTENANCE), false); } public boolean isInDevMode(String serviceId) { ServiceDescriptor descr = getDescriptor(serviceId); if (ServiceManifest.BUILD_TYPE_PROD.equals(descr.getManifest().getBuildType())) { return WebTopProps.getDevMode(); } else { return true; } } public void setMaintenance(String serviceId, boolean maintenance) { SettingsManager setMgr = wta.getSettingsManager(); setMgr.setServiceSetting(serviceId, CoreSettings.MAINTENANCE, maintenance); } /** * Gets manifest for a specified service. * @param serviceId The service ID. * @return Service manifest object. */ public ServiceManifest getManifest(String serviceId) { ServiceDescriptor descr = getDescriptor(serviceId); return (descr == null) ? null : descr.getManifest(); } public List<ServicePermission> getDeclaredPermissions(String serviceId) { ServiceManifest manifest = getManifest(serviceId); return (manifest == null) ? null : manifest.getDeclaredPermissions(); } /** * Lists IDs of registered services. * @return List of services' IDs. */ public List<String> listRegisteredServices() { synchronized (lock1) { return Arrays.asList(descriptors.keySet().toArray(new String[descriptors.size()])); } } public BaseController getController(String serviceId) { synchronized (controllers) { if (!controllers.containsKey(serviceId)) throw new WTRuntimeException("Unable to get controller for service [{0}]", serviceId); return controllers.get(serviceId); } } /** * Lists IDs of services which Controller implements specified class. * @param clazz The interface class to implement * @return List of services' IDs */ public List<String> listServicesWhichControllerImplements(Class clazz) { ArrayList<String> list = new ArrayList<>(); synchronized (lock1) { for (ServiceDescriptor descr : descriptors.values()) { if (descr.doesControllerImplements(clazz)) list.add(descr.getManifest().getId()); } } return list; } /** * Lists IDs of services that have api implementation. * @return List of services' IDs. */ public List<String> listServicesWithRestApiEndpoints() { ArrayList<String> list = new ArrayList<>(); synchronized (lock1) { for (ServiceDescriptor descr : descriptors.values()) { if (descr.hasRestApiEndpoints()) list.add(descr.getManifest().getId()); } } return list; } /** * Lists IDs of services that have private implementation. * @return List of services' IDs. */ public List<String> listPrivateServices() { ArrayList<String> list = new ArrayList<>(); synchronized (lock1) { for (ServiceDescriptor descr : descriptors.values()) { if (descr.hasPrivateService()) list.add(descr.getManifest().getId()); } } return list; } /** * Lists IDs of services that have public implementation. * @return List of services' IDs. */ public List<String> listPublicServices() { ArrayList<String> list = new ArrayList<>(); synchronized (lock1) { for (ServiceDescriptor descr : descriptors.values()) { if (descr.hasPublicService()) list.add(descr.getManifest().getId()); } } return list; } /** * Lists IDs of services that have job implementation. * @return List of services' IDs. */ public List<String> listJobServices() { ArrayList<String> list = new ArrayList<>(); synchronized (lock1) { for (ServiceDescriptor descr : descriptors.values()) { if (descr.hasJobService()) list.add(descr.getManifest().getId()); } } return list; } /** * Lists IDs of services that have userOption implementation. * @return List of services' IDs. */ public List<String> listUserOptionServices() { ArrayList<String> list = new ArrayList<>(); synchronized (lock1) { for (ServiceDescriptor descr : descriptors.values()) { if (descr.hasUserOptionsService()) list.add(descr.getManifest().getId()); } } return list; } /** * Checks if a specific user is ready for the specified service. * Firstly it will check initialization flag, if not present it * initializes the user calling the addProfile method implementation * related to IControllerHandlesProfiles interface (if present). * At the end of the process the user is initialized. * Lastly, if above initialization will not take place and the service * is upgraded for the user, the upgradeProfile method implementation * related to the IControllerHandlesProfiles interface will be called. * @param serviceId The service ID. * @param profileId The user ID. */ public void prepareProfile(String serviceId, UserProfileId profileId) { ServiceDescriptor descr = getDescriptor(serviceId); if (descr.doesControllerImplements(IControllerServiceHooks.class)) { BaseController instance = getController(serviceId); IControllerServiceHooks controller = (IControllerServiceHooks) instance; if (!checkAndSetProfileInitialization(serviceId, profileId)) { logger.debug("Initializing profile for service [{}]", serviceId); try { LoggerUtils.setContextDC(instance.SERVICE_ID); controller.initProfile(descr.getManifest().getVersion(), profileId); } catch (Throwable t) { //TODO: valutare se ritornare un booleano per verifica logger.error("Controller: initProfile() throws errors", t); } finally { LoggerUtils.clearContextServiceDC(); } } else { ProfileVersionEvaluationResult res = evaluateProfileVersion(descr.getManifest(), profileId); if (res.upgraded) { logger.debug("Upgrading profile for service [{}]", serviceId); try { LoggerUtils.setContextDC(instance.SERVICE_ID); controller.upgradeProfile(res.currentVersion, profileId, res.lastSeenVersion); } catch (Throwable t) { //TODO: valutare se ritornare un booleano per verifica logger.error("Controller: upgradeProfile() throws errors", t); } finally { LoggerUtils.clearContextServiceDC(); } } } } } /** * Cleanup the specified user calling the controller implementation * (if present) related to IControllerHandlesProfiles interface. * @param serviceId The service ID. * @param profileId The user profile ID. */ /* public void cleanupProfile(String serviceId, UserProfileId profileId) { ServiceDescriptor descr = getDescriptor(serviceId); if(descr.doesControllerImplements(IControllerHandlesProfiles.class)) { BaseController instance = getController(serviceId); IControllerHandlesProfiles controller = (IControllerHandlesProfiles)instance; logger.debug("Cleaning-up profile for service [{}]", serviceId); try { LoggerUtils.setContextDC(instance.SERVICE_ID); controller.removeProfile(profileId, false); } catch(Throwable t) { logger.error("Controller: removeProfile() throws errors", t); } finally { LoggerUtils.clearContextServiceDC(); } } } */ /** * Returns current initialization user-setting for a specific user. * If false, this method immediately set it to true value. * @param serviceId The service ID. * @param profileId The user profile ID. * @return The original value read before any update. */ private boolean checkAndSetProfileInitialization(String serviceId, UserProfileId profileId) { synchronized (lock2) { SettingsManager setMgr = wta.getSettingsManager(); boolean value = LangUtils.value(setMgr.getUserSetting(profileId.getDomainId(), profileId.getUserId(), serviceId, CoreSettings.INITIALIZED), false); if (!value) { setMgr.setUserSetting(profileId.getDomainId(), profileId.getUserId(), serviceId, CoreSettings.INITIALIZED, true); } return value; } } public ProfileVersionEvaluationResult evaluateProfileVersion(String serviceId, UserProfileId profileId) { ServiceDescriptor desc = getDescriptor(serviceId); return evaluateProfileVersion(desc.getManifest(), profileId); } public ProfileVersionEvaluationResult evaluateProfileVersion(ServiceManifest manifest, UserProfileId profileId) { SettingsManager setm = wta.getSettingsManager(); ServiceVersion manifestVer = manifest.getVersion(); ServiceVersion userVer = new ServiceVersion( CoreUserSettings.getWhatsnewVersion(setm, profileId, manifest.getId())); boolean upgraded = (manifestVer.compareTo(userVer) > 0); return new ProfileVersionEvaluationResult(manifestVer, userVer, upgraded); } public List<Throwable> invokeOnUserAdded(UserProfileId profileId) { ArrayList<Throwable> errors = new ArrayList<>(0); for (String serviceId : listRegisteredServices()) { Throwable t = invokeOnUserAdded(serviceId, profileId); if (t != null) errors.add(t); } return errors; } public Throwable invokeOnUserAdded(String serviceId, UserProfileId profileId) { ServiceDescriptor descr = getDescriptor(serviceId); if (descr.doesControllerImplements(IControllerUserEvents.class)) { BaseController instance = getController(serviceId); IControllerUserEvents iface = (IControllerUserEvents) instance; logger.debug("Calling onUserAdded [{}, {}]", serviceId, profileId.toString()); try { LoggerUtils.setContextDC(instance.SERVICE_ID); iface.onUserAdded(profileId); } catch (Throwable t) { logger.error("onUserAdded() throws errors [{}, {}]", serviceId, profileId.toString(), t); return t; } finally { LoggerUtils.clearContextServiceDC(); } } return null; } public List<Throwable> invokeOnUserRemoved(UserProfileId profileId) { ArrayList<Throwable> errors = new ArrayList<>(0); for (String serviceId : listRegisteredServices()) { Throwable t = invokeOnUserRemoved(serviceId, profileId); if (t != null) errors.add(t); } return errors; } public Throwable invokeOnUserRemoved(String serviceId, UserProfileId profileId) { ServiceDescriptor descr = getDescriptor(serviceId); if (descr.doesControllerImplements(IControllerUserEvents.class)) { BaseController instance = getController(serviceId); IControllerUserEvents iface = (IControllerUserEvents) instance; logger.debug("Calling onUserRemoved() [{}, {}]", serviceId, profileId.toString()); try { LoggerUtils.setContextDC(instance.SERVICE_ID); iface.onUserRemoved(profileId); } catch (Throwable t) { logger.error("onUserRemoved() throws errors [{}, {}]", serviceId, profileId.toString(), t); return t; } finally { LoggerUtils.clearContextServiceDC(); } } return null; } /** * Checks if a specific service needs to show whatsnew for passed user. * @param serviceId The service ID. * @param profile The user profile. * @return True if so, false otherwise. */ public boolean needWhatsnew(String serviceId, UserProfile profile) { ServiceDescriptor desc = getDescriptor(serviceId); // Gets current service's version info and last version for this user ProfileVersionEvaluationResult res = evaluateProfileVersion(desc.getManifest(), profile.getId()); boolean show = false; if (res.upgraded) { String html = desc.getWhatsnew(profile.getLocale(), res.lastSeenVersion); if (StringUtils.isEmpty(html)) { // If content is empty, updates whatsnew version for the user; // it basically realign versions in user-settings. logger.trace("Whatsnew empty [{}]", serviceId); resetWhatsnew(serviceId, profile.getId()); } else { show = true; } } logger.debug("Need to show whatsnew? {} [{}]", show, serviceId); return show; } /** * Loads whatsnew file for specified service. * If full parameter is true, all version paragraphs will be loaded; * otherwise current version only. * @param serviceId Service ID * @param profile The user profile. * @param full True to extract all version paragraphs. * @return HTML translated representation of loaded file. */ public String getWhatsnew(String serviceId, UserProfile profile, boolean full) { ServiceVersion fromVersion = null; ServiceDescriptor desc = getDescriptor(serviceId); if (!full) { SettingsManager setm = wta.getSettingsManager(); fromVersion = new ServiceVersion(CoreUserSettings.getWhatsnewVersion(setm, profile.getId(), serviceId)); } return desc.getWhatsnew(profile.getLocale(), fromVersion); } /** * Resets whatsnew updating service's version in user-setting * to the current one. * @param serviceId The service ID. * @param profileId The user profile ID. */ public void resetWhatsnew(String serviceId, UserProfileId profileId) { SettingsManager setm = wta.getSettingsManager(); ServiceVersion manifestVer = null; // Gets current service's version info manifestVer = getManifest(serviceId).getVersion(); CoreUserSettings.setWhatsnewVersion(setm, profileId, serviceId, manifestVer.toString()); } public void scheduleAllJobServicesTasks() { if (!wta.isLatest()) return; // Make sure we are in latest webapp synchronized (jobServices) { for (Entry<String, BaseJobService> entry : jobServices.entrySet()) { scheduleJobServiceTasks(entry.getKey(), entry.getValue()); } } } public void unscheduleAllJobServicesTasks() { synchronized (jobServices) { for (Entry<String, BaseJobService> entry : jobServices.entrySet()) { unscheduleJobServiceTasks(entry.getKey()); } } } public boolean canExecuteTaskWork(JobKey taskKey) { if (wta.isLatest()) { return true; } else { unscheduleAllJobServicesTasks(); return false; } } private boolean createController(String serviceId) { ServiceDescriptor descriptor = getDescriptor(serviceId); if (!descriptor.hasController()) return false; synchronized (controllers) { if (controllers.containsKey(serviceId)) throw new RuntimeException("Cannot add controller twice"); BaseController inst = instantiateController(descriptor); if (inst != null) { controllers.put(serviceId, inst); return true; } else { return false; } } } private BaseController instantiateController(ServiceDescriptor descriptor) { try { return (BaseController) descriptor.getControllerClass().newInstance(); } catch (Throwable t) { logger.error("Controller: instantiation failure [{}]", descriptor.getManifest().getControllerClassName(), t); return null; } } public CoreManager instantiateCoreManager(boolean fastInit, UserProfileId targetProfileId) { return new CoreManager(wta, fastInit, targetProfileId); } public CoreAdminManager instantiateCoreAdminManager(boolean fastInit, UserProfileId targetProfileId) { return new CoreAdminManager(wta, fastInit, targetProfileId); } public BaseManager instantiateServiceManager(String serviceId, boolean fastInit, UserProfileId targetProfileId) { ServiceDescriptor descr = getDescriptor(serviceId); if (!descr.hasManager()) return null; try { Class clazz = descr.getManagerClass(); Constructor<BaseManager> constructor = clazz.getConstructor(boolean.class, UserProfileId.class); return constructor.newInstance(fastInit, targetProfileId); } catch (Throwable t) { logger.error("Manager: instantiation failure [{}]", descr.getManifest().getManagerClassName(), t); return null; } } /* public BaseRestApi instantiateRestApi(String serviceId) { ServiceDescriptor descr = getDescriptor(serviceId); try { Class clazz = descr.getRestApiClass(); Constructor<BaseRestApi> constructor = clazz.getConstructor(); return constructor.newInstance(); } catch(Throwable t) { logger.error("RestApi: instantiation failure [{}]", descr.getManifest().getRestApiClassName(), t); return null; } } */ public BaseService instantiatePrivateService(String serviceId, PrivateEnvironment environment) { ServiceDescriptor descr = getDescriptor(serviceId); if (!descr.hasPrivateService()) throw new RuntimeException("Service has no default class"); // Creates service instance BaseService instance = null; try { instance = (BaseService) descr.getPrivateServiceClass().newInstance(); } catch (Throwable t) { logger.error("PrivateService: instantiation failure [{}]", t, descr.getManifest().getPrivateServiceClassName()); return null; } instance.configure(environment); // Calls initialization method long start = 0, end = 0; logger.trace("PrivateService: calling initialize() [{}]", serviceId); try { LoggerUtils.setContextDC(serviceId); start = System.nanoTime(); instance.initialize(); end = System.nanoTime(); } catch (Throwable t) { logger.error("PrivateService: initialize() throws errors [{}]", t, instance.getClass().getCanonicalName()); } finally { LoggerUtils.clearContextServiceDC(); } if (logger.isTraceEnabled() && (end != 0)) logger.trace("PrivateService: initialize() took {} ms [{}]", TimeUnit.MILLISECONDS.convert(end - start, TimeUnit.NANOSECONDS), serviceId); return instance; } public void cleanupPrivateService(BaseService instance) { long start = 0, end = 0; logger.trace("PrivateService: calling cleanup() [{}]", instance.SERVICE_ID); try { LoggerUtils.setContextDC(instance.SERVICE_ID); start = System.nanoTime(); instance.cleanup(); end = System.nanoTime(); } catch (Throwable t) { logger.error("PrivateService: cleanup() throws errors [{}]", t, instance.getClass().getCanonicalName()); } finally { LoggerUtils.clearContextServiceDC(); } if (logger.isTraceEnabled() && (end != 0)) logger.trace("PrivateService: cleanup() took {} ms [{}]", TimeUnit.MILLISECONDS.convert(end - start, TimeUnit.NANOSECONDS), instance.SERVICE_ID); } public BaseUserOptionsService instantiateUserOptionsService(UserProfile sessionProfile, String sessionId, String serviceId, UserProfileId targetProfileId) { ServiceDescriptor descr = getDescriptor(serviceId); if (!descr.hasUserOptionsService()) throw new RuntimeException("Service has no userOptions service class"); BaseUserOptionsService instance = null; try { instance = (BaseUserOptionsService) descr.getUserOptionsServiceClass().newInstance(); } catch (Throwable t) { logger.error("UserOptions: instantiation failure [{}]", t, descr.getManifest().getUserOptionsServiceClassName()); return null; } instance.configure(sessionProfile, targetProfileId); return instance; } public BasePublicService instantiatePublicService(String serviceId, PublicEnvironment environment) { ServiceDescriptor descr = getDescriptor(serviceId); if (!descr.hasPublicService()) throw new WTRuntimeException("Service [{}] has no public class", serviceId); BasePublicService instance = null; try { instance = (BasePublicService) descr.getPublicServiceClass().newInstance(); } catch (Throwable t) { logger.error("PublicService: instantiation failure [{}]", t, descr.getManifest().getPublicServiceClassName()); return null; } instance.configure(environment); // Calls initialization method long start = 0, end = 0; logger.trace("PublicService: calling initialize() [{}]", serviceId); try { LoggerUtils.setContextDC(instance.SERVICE_ID); start = System.nanoTime(); instance.initialize(); end = System.nanoTime(); } catch (Throwable t) { logger.error("PublicService: initialize() throws errors [{}]", t, instance.getClass().getCanonicalName()); } finally { LoggerUtils.clearContextServiceDC(); } if (logger.isTraceEnabled() && (end != 0)) logger.trace("PublicService: initialize() took {} ms [{}]", TimeUnit.MILLISECONDS.convert(end - start, TimeUnit.NANOSECONDS), serviceId); return instance; } public void cleanupPublicService(BasePublicService instance) { long start = 0, end = 0; logger.trace("PublicService: calling cleanup() [{}]", instance.SERVICE_ID); try { LoggerUtils.setContextDC(instance.SERVICE_ID); start = System.nanoTime(); instance.cleanup(); end = System.nanoTime(); } catch (Throwable t) { logger.error("PublicService: cleanup() throws errors [{}]", t, instance.getClass().getCanonicalName()); } finally { LoggerUtils.clearContextServiceDC(); } if (logger.isTraceEnabled() && (end != 0)) logger.trace("PublicService: cleanup() took {} ms [{}]", TimeUnit.MILLISECONDS.convert(end - start, TimeUnit.NANOSECONDS), instance.SERVICE_ID); } private boolean createJobService(String serviceId) { synchronized (jobServices) { if (jobServices.containsKey(serviceId)) throw new RuntimeException("Cannot add job service twice"); BaseJobService inst = instantiateJobService(serviceId); if (inst != null) { jobServices.put(serviceId, inst); return true; } else { return false; } } } private BaseJobService instantiateJobService(String serviceId) { ServiceDescriptor descr = getDescriptor(serviceId); if (!descr.hasJobService()) throw new RuntimeException("Service has no job class"); BaseJobService instance = null; try { instance = (BaseJobService) descr.getJobServiceClass().newInstance(); } catch (Throwable t) { logger.error("JobService: instantiation failure [{}]", t, descr.getManifest().getJobServiceClassName()); return null; } instance.configure(wta.getAdminSubject()); return instance; } private void initializeJobService(BaseJobService instance) { long start = 0, end = 0; logger.trace("JobService: calling initialize() [{}]", instance.SERVICE_ID); try { LoggerUtils.setContextDC(instance.SERVICE_ID); start = System.nanoTime(); instance.initialize(); end = System.nanoTime(); } catch (Throwable t) { logger.error("JobService: initialize() throws errors [{}]", t, instance.getClass().getCanonicalName()); } finally { LoggerUtils.clearContextServiceDC(); } if (logger.isTraceEnabled() && (end != 0)) logger.trace("JobService: initialize() took {} ms [{}]", TimeUnit.MILLISECONDS.convert(end - start, TimeUnit.NANOSECONDS), instance.SERVICE_ID); } private void cleanupJobService(BaseJobService instance) { long start = 0, end = 0; logger.trace("JobService: calling cleanup() [{}]", instance.SERVICE_ID); try { LoggerUtils.setContextDC(instance.getManifest().getId()); start = System.nanoTime(); instance.cleanup(); end = System.nanoTime(); } catch (Throwable t) { logger.error("JobService: cleanup() throws errors [{}]", t, instance.getClass().getCanonicalName()); } finally { LoggerUtils.clearContextServiceDC(); } if (logger.isTraceEnabled() && (end != 0)) logger.trace("JobService: cleanup() took {} ms [{}]", TimeUnit.MILLISECONDS.convert(end - start, TimeUnit.NANOSECONDS), instance.SERVICE_ID); } private String generatePublicName(String serviceId) { SettingsManager setm = wta.getSettingsManager(); String overriddenPublicName = setm.getServiceSetting(serviceId, CoreSettings.PUBLIC_NAME); if (!StringUtils.isEmpty(overriddenPublicName)) { return overriddenPublicName; } else { String[] tokens = StringUtils.split(serviceId, "."); return (tokens.length > 0) ? tokens[tokens.length - 1] : serviceId; } } private Map<String, ServiceManifest> discoverServices() throws IOException { ClassLoader cl = LangUtils.findClassLoader(getClass()); // Scans classpath looking for service descriptor files Enumeration<URL> enumResources = null; try { enumResources = cl.getResources(SERVICES_DESCRIPTOR_RESOURCE); } catch (IOException ex) { throw ex; } // Parses and splits descriptor files into a single manifest file for each service HashMap<String, ServiceManifest> manifests = new HashMap(); while (enumResources.hasMoreElements()) { URL url = enumResources.nextElement(); try { Collection<ServiceManifest> parsed = parseDescriptor(url); for (ServiceManifest manifest : parsed) { String key = manifest.getId(); if (!manifests.containsKey(key)) { manifests.put(manifest.getId(), manifest); } else if (manifest.getVersion().compareTo(manifests.get(key).getVersion()) > 0) { logger.warn("[{}] Version {} replaced by {}", manifest.getId(), manifests.get(key).getVersion(), manifest.getVersion()); manifests.put(manifest.getId(), manifest); } } } catch (ConfigurationException ex) { logger.error("Error while reading descriptor [{}]", url.toString(), ex); } } List<String> orderdKeys = manifests.keySet().stream().sorted((s1, s2) -> { if (StringUtils.startsWith(s1, CoreManifest.ID)) { return -1; } else { return s1.compareTo(s2); } }).collect(Collectors.toList()); LinkedHashMap<String, ServiceManifest> map = new LinkedHashMap<>(orderdKeys.size()); for (String key : orderdKeys) { map.put(key, manifests.get(key)); } return map; } private ArrayList<ServiceManifest> parseDescriptor(final URL descriptorUri) throws ConfigurationException { ArrayList<ServiceManifest> manifests = new ArrayList(); ServiceManifest manifest = null; logger.trace("Parsing descriptor [{}]", descriptorUri.toString()); XMLConfiguration config = new XMLConfiguration(descriptorUri); List<HierarchicalConfiguration> elServices = config.configurationsAt("service"); for (HierarchicalConfiguration elService : elServices) { try { manifest = new ServiceManifest(elService); manifests.add(manifest); } catch (Exception ex) { logger.warn("Service descriptor for '{}' version {} skipped. Cause: {}", elService.getString("shortName"), elService.getString("version"), ex.getMessage()); } } return manifests; } private ServiceDescriptor registerService(ServiceManifest manifest) { ConnectionManager conMgr = wta.getConnectionManager(); ServiceDescriptor desc = null; String serviceId = manifest.getId(); String xid = manifest.getXId(); logger.info("Registering service [{}]", serviceId); synchronized (lock1) { if (descriptors.containsKey(serviceId)) throw new WTRuntimeException("Service ID is already registered [{0}]", serviceId); if (xidToServiceId.containsKey(xid)) throw new WTRuntimeException("Service XID (short ID) is already bound to a service [{0} -> {1}]", xid, xidToServiceId.get(xid)); desc = new ServiceDescriptor(manifest); logger.info("[private:{}, public:{}, job:{}, userOptions:{}]", desc.hasPrivateService(), desc.hasPublicService(), desc.hasJobService(), desc.hasUserOptionsService()); // Register service's dataSources try { DataSourcesConfig config = conMgr.getConfiguration(); DataSourcesConfig.HikariConfigMap sources = config.getSources(serviceId); if (sources != null) { logger.debug("Registering {} dataSources", sources.size()); // If service provides its own sources, register them... for (Entry<String, HikariConfig> entry : sources.entrySet()) { if (!conMgr.isRegistered(serviceId, entry.getKey())) { conMgr.registerDataSource(serviceId, entry.getKey(), entry.getValue()); } } } else { logger.debug("No custom dataSources defined"); } } catch (Exception ex) { throw new WTRuntimeException(ex, "Error registering service dataSources"); } // Upgrade check boolean upgraded = upgradeCheck(manifest); desc.setUpgraded(upgraded); if (upgraded) { // Force whatsnew pre-caching desc.getWhatsnew(wta.getSystemLocale(), manifest.getOldVersion()); } descriptors.put(serviceId, desc); xidToServiceId.put(xid, serviceId); serviceIdToJsPath.put(serviceId, manifest.getJsPath()); String publicName = generatePublicName(serviceId); if (publicNameToServiceId.containsKey(publicName)) { logger.warn("Service public name [{}] conflict! [{} hides {}]", publicName, serviceId, publicNameToServiceId.get(publicName)); //TODO: valutare se portare il servizio in manutenzione } serviceIdToPublicName.put(serviceId, publicName); publicNameToServiceId.put(publicName, serviceId); // Adds service references into static map in order to facilitate ID lookup WT.registerManifest(serviceId, manifest); return desc; } } private boolean upgradeServiceDb(ServiceDescriptor desc, String upgradeTag, boolean autoUpdate) { ArrayList<SqlUpgradeScript> scripts = desc.getUpgradeScripts(); return upgradeServiceDb(desc, scripts, upgradeTag, autoUpdate); } private boolean upgradeServiceDb(ServiceDescriptor desc, ArrayList<SqlUpgradeScript> scripts, String upgradeTag, boolean autoUpdate) { ConnectionManager conMgr = wta.getConnectionManager(); UpgradeStatementDAO upgdao = UpgradeStatementDAO.getInstance(); boolean requireAdmin = false; Connection con = null; try { if (!scripts.isEmpty()) { String serviceId = desc.getManifest().getId(); // Transforms each statement into a specific object List<OUpgradeStatement> stmts = new ArrayList<>(); short sequence = 0; for (SqlUpgradeScript script : scripts) { // Extracts and inserts statements ArrayList<BaseScriptLine> scriptStatements = script.getStatements(); String targetDataSource = null; logger.trace("Script {}: found {} statement/s", script.getFileName(), scriptStatements.size()); for (BaseScriptLine statement : scriptStatements) { sequence++; String stmtType, stmtDs; if (statement instanceof AnnotationLine) { stmtDs = null; stmtType = OUpgradeStatement.STATEMENT_TYPE_ANNOTATION; if (statement instanceof DataSourceAnnotationLine) { targetDataSource = ((DataSourceAnnotationLine) statement).getTargetDataSource(); } } else if (statement instanceof SqlLine) { stmtDs = targetDataSource; stmtType = OUpgradeStatement.STATEMENT_TYPE_SQL; } else { stmtDs = null; stmtType = OUpgradeStatement.STATEMENT_TYPE_COMMENT; } stmts.add(new OUpgradeStatement(upgradeTag, serviceId, sequence, script.getFileName(), stmtDs, stmtType, statement.getText())); } } // Inserts extracted statements into a dedicated table con = conMgr.getConnection(); Integer maxId = upgdao.maxId(con); int count = upgdao.batchInsert(con, stmts); if (count != sequence) throw new WTException("Statements insertion not fully completed [total: {0}, inserted: {1}]", sequence, count); // Reads all inserted statements (NB: id needs to be refreshed because it comes from a db sequence) stmts = upgdao.selectFromIdByTagService(con, maxId == null ? -1 : maxId, upgradeTag, serviceId); if (stmts.isEmpty()) { logger.debug("DB upgrade is not necessary [{}]", serviceId); } else { if (!autoUpdate) { // Manual requireAdmin = true; } else { // Auto: Runs statements... HashMap<String, Connection> conCache = new HashMap<>(); try { logger.debug("Executing upgrade statements [{}, {}]", serviceId, stmts.size()); boolean ignoreErrors = false; for (OUpgradeStatement stmt : stmts) { if (stmt.getStatementType().equals(OUpgradeStatement.STATEMENT_TYPE_ANNOTATION)) { if (RequireAdminAnnotationLine.matches(stmt.getStatementBody())) { logger.trace("[{}, {}]: {}", stmt.getServiceId(), stmt.getSequenceNo(), stmt.getStatementBody()); requireAdmin = true; break; // Stops iteration! } else if (IgnoreErrorsAnnotationLine.matches(stmt.getStatementBody())) { ignoreErrors = true; // Sets value! (for next sql statement) } } else if (stmt.getStatementType().equals(OUpgradeStatement.STATEMENT_TYPE_SQL)) { final String sds = stmt.getStatementDataSource(); if (StringUtils.isBlank(sds)) throw new WTException("Statement does not provide a DataSource [{0}]", stmt.getUpgradeStatementId()); if (!conCache.containsKey(sds)) { conCache.put(sds, getUpgradeStatementConnection(conMgr, stmt)); } boolean ret = executeUpgradeStatement(conCache.get(sds), stmt, ignoreErrors); try { upgdao.update(con, stmt); } catch (DAOException ex) { throw new WTException("Unable to update statement status!", ex); } if (!ret) { // In case of errors... requireAdmin = true; break; // Stops iteration! } ignoreErrors = false; // Resets value! } else { logger.trace("[{}, {}]: !!! {}", stmt.getServiceId(), stmt.getSequenceNo(), StringUtils.replace(stmt.getStatementBody(), "\n", " ")); } } } finally { if (!conCache.isEmpty()) { logger.trace("Closing connections [{}]", conCache.size()); Iterator<Entry<String, Connection>> it = conCache.entrySet().iterator(); while (it.hasNext()) { final Entry<String, Connection> entry = it.next(); DbUtils.closeQuietly(entry.getValue()); it.remove(); } } } } } } } catch (Throwable t) { requireAdmin = true; logger.error("Error handling upgrade script", t); } finally { DbUtils.closeQuietly(con); return requireAdmin; } } private boolean postUpgradeServiceDb(ServiceDescriptor desc, String upgradeTag, boolean autoUpdate) { ArrayList<SqlUpgradeScript> scripts = getPostDbScripts(desc.getManifest().getId()); return upgradeServiceDb(desc, scripts, upgradeTag, autoUpdate); } private Connection getUpgradeStatementConnection(ConnectionManager conMgr, OUpgradeStatement statement) throws SQLException, WTException { String[] tokens = StringUtils.split(statement.getStatementDataSource(), "@", 2); if (tokens.length != 2) throw new WTException("Invalid DataSource [{0}]. It must in the following format: name@namespace", statement.getStatementDataSource()); String namespace = tokens[1]; String dataSourceName = tokens[0]; return conMgr.getFallbackConnection(namespace, dataSourceName); } public boolean executeUpgradeStatement(OUpgradeStatement statement, boolean ignoreErrors) throws WTException { ConnectionManager conMgr = wta.getConnectionManager(); UpgradeStatementDAO upgdao = UpgradeStatementDAO.getInstance(); Connection con = null, targetCon = null; try { targetCon = getUpgradeStatementConnection(conMgr, statement); boolean ret = executeUpgradeStatement(targetCon, statement, ignoreErrors); try { con = conMgr.getConnection(); upgdao.update(con, statement); } catch (DAOException ex) { throw new Exception("Unable to update statement status!", ex); } return ret; } catch (Throwable t) { throw new WTException(t); } finally { DbUtils.closeQuietly(con); DbUtils.closeQuietly(targetCon); } } public void skipUpgradeStatement(OUpgradeStatement statement) throws WTException { ConnectionManager conMgr = wta.getConnectionManager(); UpgradeStatementDAO upgdao = UpgradeStatementDAO.getInstance(); Connection con = null; try { con = conMgr.getConnection(); statement.setRunStatus(OUpgradeStatement.RUN_STATUS_SKIPPED); logger.trace("[{}, {}]: {}", statement.getServiceId(), statement.getSequenceNo(), statement.getStatementBody()); upgdao.update(con, statement); } catch (SQLException | DAOException ex) { throw new WTException("Unable to update statement status!", ex); } finally { DbUtils.closeQuietly(con); } } private boolean executeUpgradeStatement(Connection statementCon, OUpgradeStatement statement, boolean ignoreErrors) throws Throwable { Statement stmt = null; try { logger.trace("[{}]: {}", statement.getUpgradeStatementId(), statement.getStatementBody()); stmt = statementCon.createStatement(); statement.setRunTimestamp(DateTimeUtils.now(true)); int ret = stmt.executeUpdate(statement.getStatementBody()); statement.setRunStatus(OUpgradeStatement.RUN_STATUS_OK); statement.setRunMessage(MessageFormat.format("Affected rows: {0}", ret)); return true; } catch (SQLException ex) { if (!ignoreErrors) { statement.setRunStatus(OUpgradeStatement.RUN_STATUS_ERROR); statement.setRunMessage(ex.getMessage()); logger.trace("{}", statement.getRunMessage()); return false; } else { statement.setRunStatus(OUpgradeStatement.RUN_STATUS_WARNING); statement.setRunMessage(ex.getMessage()); return true; } } finally { StatementUtils.closeQuietly(stmt); } } private boolean upgradeCheck(ServiceManifest manifest) { SettingsManager setm = wta.getSettingsManager(); ServiceVersion manifestVer = null, currentVer = null; // Gets current service's version info manifestVer = manifest.getVersion(); currentVer = new ServiceVersion(setm.getServiceSetting(manifest.getId(), CoreSettings.MANIFEST_VERSION)); // Upgrade check! if (manifestVer.compareTo(currentVer) > 0) { logger.info("Upgrade found! [{} -> {}] Updating version setting...", currentVer.toString(), manifestVer.toString()); manifest.setOldVersion(currentVer); setm.setServiceSetting(manifest.getId(), CoreSettings.MANIFEST_VERSION, manifestVer.toString()); return true; } else { logger.info("Not upgraded! [{} = {}]", manifestVer.toString(), currentVer.toString()); return false; } } private ArrayList<SqlUpgradeScript> getPostDbScripts(String serviceId) { ArrayList<SqlUpgradeScript> scripts = new ArrayList<>(); FilenameFilter sqlFilter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith("sql"); } }; try { String postPath = wta.getDbScriptsPostPath(serviceId); logger.debug("Looking for post db-scripts in [{}]", postPath); File postDir = new File(postPath); if (postDir.exists()) { File[] files = postDir.listFiles(sqlFilter); if (files != null) { for (File file : files) { try { logger.debug("Reading post db-scripts [{}]", file.getName()); SqlUpgradeScript script = new SqlUpgradeScript(file); scripts.add(script); } catch (Exception ex1) { logger.warn(ex1.getMessage()); // increment error counter... } } } } } catch (Exception ex) { logger.error("Error loading post db-scripts", ex); } return scripts; } private void postponeJobsInitialization() { Thread engine = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); synchronized (jobServices) { for (Entry<String, BaseJobService> entry : jobServices.entrySet()) { initializeJobService(entry.getValue()); } } } catch (InterruptedException ex) { /* Do nothing... */ } } }); engine.start(); } private JobDetail createJobTask(String serviceId, BaseJobService service, TaskDefinition taskDef) { String classBaseName = taskDef.clazz.getSimpleName(); JobDataMap data = (taskDef.data != null) ? taskDef.data : new JobDataMap(); data.put("jobService", service); JobBuilder jb = JobBuilder.newJob(taskDef.clazz).usingJobData(data).withIdentity(classBaseName, serviceId); if (!StringUtils.isEmpty(taskDef.description)) jb.withDescription(taskDef.description); return jb.build(); } private Trigger createJobTaskTrigger(String serviceId, TaskDefinition taskDef) { String classBaseName = taskDef.clazz.getSimpleName(); TriggerBuilder tb = taskDef.trigger.getTriggerBuilder().withIdentity(classBaseName, serviceId).startNow(); return tb.build(); } private void scheduleJobServiceTasks(String serviceId, BaseJobService service) { List<TaskDefinition> taskDefs = null; // Gets task definitions from base service definition try { LoggerUtils.setContextDC(serviceId); taskDefs = service.returnTasks(); } catch (Throwable t) { logger.error("JobService method returns errors [returnTask()]", t); } finally { LoggerUtils.clearContextServiceDC(); } unscheduleJobServiceTasks(serviceId); if (taskDefs != null) { // Schedule job defining its trigger and details for (TaskDefinition taskDef : taskDefs) { JobDetail jobDetail = createJobTask(serviceId, service, taskDef); Trigger trigger = createJobTaskTrigger(serviceId, taskDef); try { scheduler.scheduleJob(jobDetail, trigger); logger.debug("Task scheduled [{}]", jobDetail.getKey().toString()); } catch (SchedulerException ex) { logger.error("Error scheduling task [{}]", jobDetail.getKey().toString(), ex); } } } } private boolean unscheduleJobServiceTasks(String serviceId) { try { Set<JobKey> keys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(serviceId)); scheduler.deleteJobs(new ArrayList<>(keys)); logger.debug("Deleted tasks for group [{}]", serviceId); return true; } catch (SchedulerException ex) { logger.error("Error deleting tasks for group [{}]", serviceId, ex); return false; } } public static class ProfileVersionEvaluationResult { public final ServiceVersion currentVersion; public final ServiceVersion lastSeenVersion; public final boolean upgraded; public ProfileVersionEvaluationResult(ServiceVersion currentVersion, ServiceVersion lastSeenVersion, boolean upgraded) { this.currentVersion = currentVersion; this.lastSeenVersion = lastSeenVersion; this.upgraded = upgraded; } } }