com.sonicle.webtop.core.app.ServiceManager.java Source code

Java tutorial

Introduction

Here is the source code for com.sonicle.webtop.core.app.ServiceManager.java

Source

/*
 * 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;
        }
    }
}