com.mirth.connect.server.controllers.DefaultCodeTemplateController.java Source code

Java tutorial

Introduction

Here is the source code for com.mirth.connect.server.controllers.DefaultCodeTemplateController.java

Source

/*
 * Copyright (c) Mirth Corporation. All rights reserved.
 * 
 * http://www.mirthcorp.com
 * 
 * The software in this package is published under the terms of the MPL license a copy of which has
 * been included with this distribution in the LICENSE.txt file.
 */

package com.mirth.connect.server.controllers;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.log4j.Logger;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.EvaluatorException;

import com.mirth.connect.client.core.ControllerException;
import com.mirth.connect.model.CodeTemplate;
import com.mirth.connect.model.CodeTemplateLibrary;
import com.mirth.connect.model.CodeTemplateLibrarySaveResult;
import com.mirth.connect.model.CodeTemplateLibrarySaveResult.CodeTemplateUpdateResult;
import com.mirth.connect.model.CodeTemplateLibrarySaveResult.LibraryUpdateResult;
import com.mirth.connect.model.CodeTemplateSummary;
import com.mirth.connect.model.ServerEventContext;
import com.mirth.connect.plugins.CodeTemplateServerPlugin;
import com.mirth.connect.server.ExtensionLoader;
import com.mirth.connect.server.util.DatabaseUtil;
import com.mirth.connect.server.util.SqlConfig;

public class DefaultCodeTemplateController extends CodeTemplateController {

    private static CodeTemplateController instance = null;

    private Logger logger = Logger.getLogger(getClass());
    private ExtensionController extensionController = ControllerFactory.getFactory().createExtensionController();
    private ScriptController scriptController = ControllerFactory.getFactory().createScriptController();
    private ContextFactoryController contextFactoryController = ControllerFactory.getFactory()
            .createContextFactoryController();
    private Cache<CodeTemplateLibrary> libraryCache = new Cache<CodeTemplateLibrary>("Code Template Library",
            "CodeTemplate.getLibraryRevision", "CodeTemplate.getLibrary");
    private Cache<CodeTemplate> codeTemplateCache = new Cache<CodeTemplate>("Code Template",
            "CodeTemplate.getCodeTemplateRevision", "CodeTemplate.getCodeTemplate", false);

    private DefaultCodeTemplateController() {
    }

    public static CodeTemplateController create() {
        synchronized (DefaultCodeTemplateController.class) {
            if (instance == null) {
                instance = ExtensionLoader.getInstance().getControllerInstance(CodeTemplateController.class);

                if (instance == null) {
                    instance = new DefaultCodeTemplateController();
                }
            }

            return instance;
        }
    }

    @Override
    public List<CodeTemplateLibrary> getLibraries(Set<String> libraryIds, boolean includeCodeTemplates)
            throws ControllerException {
        logger.debug("Getting code template libraries, libraryIds=" + String.valueOf(libraryIds));
        if (CollectionUtils.isEmpty(libraryIds)) {
            libraryIds = null;
        } else {
            libraryIds = new HashSet<String>(libraryIds);
        }

        Map<String, CodeTemplateLibrary> libraryMap = libraryCache.getAllItems();
        List<CodeTemplateLibrary> libraries = new ArrayList<CodeTemplateLibrary>();
        Map<String, CodeTemplate> codeTemplateMap = codeTemplateCache.getAllItems();

        for (CodeTemplateLibrary library : libraryMap.values()) {
            if (libraryIds == null || libraryIds.contains(library.getId())) {
                addCodeTemplatesToLibrary(library, codeTemplateMap);
                if (!includeCodeTemplates) {
                    library.replaceCodeTemplatesWithIds();
                }

                libraries.add(library);

                if (libraryIds != null) {
                    libraryIds.remove(library.getId());
                }
            }

            for (CodeTemplate codeTemplate : library.getCodeTemplates()) {
                codeTemplateMap.remove(codeTemplate.getId());
            }
        }

        if (libraryIds != null) {
            for (String libraryId : libraryIds) {
                logger.warn("Cannot find code template library, it may have been removed: " + libraryId);
            }
        }

        return libraries;
    }

    private void addCodeTemplatesToLibrary(CodeTemplateLibrary library, Map<String, CodeTemplate> codeTemplateMap) {
        List<CodeTemplate> codeTemplates = new ArrayList<CodeTemplate>();

        for (CodeTemplate codeTemplate : library.getCodeTemplates()) {
            if (codeTemplateMap.containsKey(codeTemplate.getId())) {
                codeTemplates.add(codeTemplateMap.get(codeTemplate.getId()));
            } else {
                logger.warn("Cannot find code template, it may have been removed: " + codeTemplate.getId());
            }
        }

        library.setCodeTemplates(codeTemplates);
        library.sortCodeTemplates();
    }

    @Override
    public CodeTemplateLibrary getLibraryById(String libraryId) throws ControllerException {
        return libraryCache.getCachedItemById(libraryId);
    }

    @Override
    public CodeTemplateLibrary getLibraryByName(String libraryName) throws ControllerException {
        return libraryCache.getCachedItemByName(libraryName);
    }

    @Override
    public synchronized boolean updateLibraries(Set<CodeTemplateLibrary> libraries, ServerEventContext context,
            boolean override) throws ControllerException {
        Map<String, CodeTemplateLibrary> libraryMap = libraryCache.getAllItems();
        List<CodeTemplateLibrary> librariesToRemove = new ArrayList<CodeTemplateLibrary>(libraryMap.values());
        Map<String, String> codeTemplateMap = new HashMap<String, String>();
        Set<String> libraryNames = new HashSet<String>();
        Set<String> unchangedLibraryIds = new HashSet<String>();

        for (CodeTemplateLibrary library : libraries) {
            for (CodeTemplate codeTemplate : library.getCodeTemplates()) {
                // Make sure this library's code templates aren't contained in any other library
                if (codeTemplateMap.put(codeTemplate.getId(), library.getId()) != null) {
                    String errorMessage = "Code Template \"" + codeTemplate.getName()
                            + "\" belongs to more than one library.";
                    logger.error(errorMessage);
                    throw new ControllerException(errorMessage);
                }
            }

            /*
             * Code templates are stored separately in the database. Only the code template ID is
             * needed when storing the library.
             */
            library.replaceCodeTemplatesWithIds();

            // Make sure there isn't another library with the same name
            if (!libraryNames.add(library.getName())) {
                String errorMessage = "There is already a code template library with the name " + library.getName();
                logger.error(errorMessage);
                throw new ControllerException(errorMessage);
            }

            CodeTemplateLibrary matchingLibrary = libraryMap.get(library.getId());

            if (matchingLibrary != null) {
                if (EqualsBuilder.reflectionEquals(library, matchingLibrary, "lastModified", "revision")) {
                    unchangedLibraryIds.add(library.getId());
                } else {
                    /*
                     * If it's not a new library, and its version is different from the one in the
                     * database (in case it has been changed on the server since the client started
                     * modifying it), and override is not enabled, then don't allow the update
                     */
                    if (!library.getRevision().equals(matchingLibrary.getRevision()) && !override) {
                        return false;
                    } else {
                        library.setRevision(matchingLibrary.getRevision() + 1);
                    }
                }

                // Either way, this library is not being removed
                librariesToRemove.remove(matchingLibrary);
            } else {
                // Always start at revision 1 for new libraries
                library.setRevision(1);
            }
        }

        // Remove libraries
        for (CodeTemplateLibrary library : librariesToRemove) {
            try {
                SqlConfig.getSqlSessionManager().delete("CodeTemplate.deleteLibrary", library.getId());

                if (DatabaseUtil.statementExists("CodeTemplate.vacuumLibraryTable")) {
                    SqlConfig.getSqlSessionManager().update("CodeTemplate.vacuumLibraryTable");
                }

                // Invoke the code template plugins
                for (CodeTemplateServerPlugin codeTemplateServerPlugin : extensionController
                        .getCodeTemplateServerPlugins().values()) {
                    codeTemplateServerPlugin.remove(library, context);
                }
            } catch (Exception e) {
                throw new ControllerException(e);
            }
        }

        // Insert or update libraries
        for (CodeTemplateLibrary library : libraries) {
            // Only if it actually changed
            if (!unchangedLibraryIds.contains(library.getId())) {
                try {
                    library.setLastModified(Calendar.getInstance());

                    Map<String, Object> params = new HashMap<String, Object>();
                    params.put("id", library.getId());
                    params.put("name", library.getName());
                    params.put("revision", library.getRevision());
                    params.put("library", library);

                    // Put the new library in the database
                    if (getLibraryById(library.getId()) == null) {
                        logger.debug("Inserting code template library");
                        SqlConfig.getSqlSessionManager().insert("CodeTemplate.insertLibrary", params);
                    } else {
                        logger.debug("Updating code template library");
                        SqlConfig.getSqlSessionManager().update("CodeTemplate.updateLibrary", params);
                    }

                    // Invoke the code template plugins
                    for (CodeTemplateServerPlugin codeTemplateServerPlugin : extensionController
                            .getCodeTemplateServerPlugins().values()) {
                        codeTemplateServerPlugin.save(library, context);
                    }
                } catch (Exception e) {
                    throw new ControllerException(e);
                }
            }
        }

        return true;
    }

    @Override
    public List<CodeTemplate> getCodeTemplates(Set<String> codeTemplateIds) throws ControllerException {
        logger.debug("Getting code templates, codeTemplateIds=" + String.valueOf(codeTemplateIds));
        if (CollectionUtils.isEmpty(codeTemplateIds)) {
            codeTemplateIds = null;
        }

        Map<String, CodeTemplate> codeTemplateMap = codeTemplateCache.getAllItems();
        List<CodeTemplate> codeTemplates = new ArrayList<CodeTemplate>();

        if (codeTemplateIds == null) {
            codeTemplates.addAll(codeTemplateMap.values());
        } else {
            for (String codeTemplateId : codeTemplateIds) {
                CodeTemplate codeTemplate = codeTemplateMap.get(codeTemplateId);
                if (codeTemplate == null) {
                    logger.error("Cannot find code template, it may have been removed: " + codeTemplateId);
                } else {
                    codeTemplates.add(codeTemplate);
                }
            }
        }

        return codeTemplates;
    }

    @Override
    public List<CodeTemplateSummary> getCodeTemplateSummary(Map<String, Integer> clientRevisions)
            throws ControllerException {
        logger.debug("Getting code template summary");
        List<CodeTemplateSummary> codeTemplateSummaries = new ArrayList<CodeTemplateSummary>();

        try {
            Map<String, CodeTemplate> serverCodeTemplates = codeTemplateCache.getAllItems();

            /*
             * Iterate through the cached code template list and check if a code template with the
             * id exists on the server. If it does, and the revision numbers aren't equal, then add
             * the code template to the updated list. Otherwise, if the code template is not found,
             * add it to the deleted list.
             */
            for (Entry<String, Integer> entry : clientRevisions.entrySet()) {
                String cachedCodeTemplateId = entry.getKey();
                CodeTemplateSummary summary = new CodeTemplateSummary(cachedCodeTemplateId);
                boolean addSummary = false;

                if (serverCodeTemplates.containsKey(cachedCodeTemplateId)) {
                    // If the revision numbers aren't equal, add the updated CodeTemplate object
                    CodeTemplate serverCodeTemplate = serverCodeTemplates.get(cachedCodeTemplateId);
                    Integer serverRevision = serverCodeTemplate.getRevision();

                    if (!serverRevision.equals(entry.getValue())) {
                        summary.setCodeTemplate(serverCodeTemplate);
                        addSummary = true;
                    }
                } else {
                    // If a code template with the ID is not found on the server, add it as deleted
                    summary.setDeleted(true);
                    addSummary = true;
                }

                if (addSummary) {
                    codeTemplateSummaries.add(summary);
                }
            }

            /*
             * Add summaries for any entries on the server but not in the client's cache.
             */
            for (Entry<String, CodeTemplate> serverEntry : serverCodeTemplates.entrySet()) {
                if (!clientRevisions.containsKey(serverEntry.getKey())) {
                    codeTemplateSummaries
                            .add(new CodeTemplateSummary(serverEntry.getKey(), serverEntry.getValue()));
                }
            }

            return codeTemplateSummaries;
        } catch (Exception e) {
            throw new ControllerException(e);
        }
    }

    @Override
    public CodeTemplate getCodeTemplateById(String codeTemplateId) throws ControllerException {
        return codeTemplateCache.getCachedItemById(codeTemplateId);
    }

    @Override
    public Map<String, Integer> getCodeTemplateRevisionsForChannel(String channelId) throws ControllerException {
        Map<String, Integer> revisions = new HashMap<String, Integer>();

        for (CodeTemplateLibrary library : getLibraries(null, true)) {
            if (library.getEnabledChannelIds().contains(channelId)
                    || (library.isIncludeNewChannels() && !library.getDisabledChannelIds().contains(channelId))) {
                for (CodeTemplate codeTemplate : library.getCodeTemplates()) {
                    if (codeTemplate.isAddToScripts()) {
                        revisions.put(codeTemplate.getId(), codeTemplate.getRevision());
                    }
                }
            }
        }

        return revisions;
    }

    @Override
    public synchronized boolean updateCodeTemplate(CodeTemplate codeTemplate, ServerEventContext context,
            boolean override) throws ControllerException {
        CodeTemplate matchingCodeTemplate = getCodeTemplateById(codeTemplate.getId());
        int currentRevision = 0;

        if (matchingCodeTemplate != null) {
            if (EqualsBuilder.reflectionEquals(codeTemplate, matchingCodeTemplate, "lastModified", "revision")) {
                return true;
            }

            currentRevision = matchingCodeTemplate.getRevision();
        }

        /*
         * If it's not a new code template, and its version is different from the one in the
         * database (in case it has been changed on the server since the client started modifying
         * it), and override is not enabled
         */
        if ((currentRevision > 0) && (currentRevision != codeTemplate.getRevision()) && !override) {
            return false;
        } else {
            codeTemplate.setRevision(currentRevision + 1);
        }

        try {
            for (CodeTemplateLibrary library : getLibraries(null, true)) {
                Set<String> codeTemplateNames = new HashSet<String>();
                boolean found = false;

                for (CodeTemplate template : library.getCodeTemplates()) {
                    if (template.getId().equals(codeTemplate.getId())) {
                        found = true;
                    } else {
                        codeTemplateNames.add(template.getName());
                    }
                }

                if (found && codeTemplateNames.contains(codeTemplate.getName())) {
                    String errorMessage = "There is already a code template with the name "
                            + codeTemplate.getName();
                    logger.error(errorMessage);
                    throw new ControllerException(errorMessage);
                }
            }

            // Check on the server side to ensure that the code template doesn't have syntax errors
            String validationMessage = null;
            Throwable validationCause = null;
            try {
                Context.enter().compileString("function rhinoWrapper() {" + codeTemplate.getCode() + "\n}",
                        UUID.randomUUID().toString(), 1, null);
            } catch (EvaluatorException e) {
                validationMessage = "Error on line " + e.lineNumber() + ": " + e.getMessage() + ".";
                validationCause = e;
            } catch (Exception e) {
                validationMessage = "Unknown error occurred during validation.";
                validationCause = e;
            } finally {
                Context.exit();
            }

            if (validationMessage != null) {
                String errorMessage = "Unable to save code template \"" + codeTemplate.getName() + "\": "
                        + validationMessage;
                logger.error(errorMessage, validationCause);
                throw new ControllerException(errorMessage, validationCause);
            }

            codeTemplate.setLastModified(Calendar.getInstance());

            Map<String, Object> params = new HashMap<String, Object>();
            params.put("id", codeTemplate.getId());
            params.put("name", codeTemplate.getName());
            params.put("revision", codeTemplate.getRevision());
            params.put("codeTemplate", codeTemplate);

            // Put the new code template in the database
            if (getCodeTemplateById(codeTemplate.getId()) == null) {
                logger.debug("Inserting code template");
                SqlConfig.getSqlSessionManager().insert("CodeTemplate.insertCodeTemplate", params);
            } else {
                logger.debug("Updating code template");
                SqlConfig.getSqlSessionManager().update("CodeTemplate.updateCodeTemplate", params);
            }

            // Invoke the code template plugins
            for (CodeTemplateServerPlugin codeTemplateServerPlugin : extensionController
                    .getCodeTemplateServerPlugins().values()) {
                codeTemplateServerPlugin.save(codeTemplate, context);
            }

            // Re-compile the global scripts
            scriptController.compileGlobalScripts(contextFactoryController.getGlobalScriptContextFactory());

            return true;
        } catch (Exception e) {
            if (e instanceof ControllerException) {
                throw (ControllerException) e;
            }
            throw new ControllerException(e);
        }
    }

    @Override
    public synchronized void removeCodeTemplate(String codeTemplateId, ServerEventContext context)
            throws ControllerException {
        // Do a lookup to get the latest model object
        CodeTemplate codeTemplate = getCodeTemplateById(codeTemplateId);
        if (codeTemplate == null) {
            return;
        }

        try {
            SqlConfig.getSqlSessionManager().delete("CodeTemplate.deleteCodeTemplate", codeTemplate.getId());

            if (DatabaseUtil.statementExists("CodeTemplate.vacuumCodeTemplateTable")) {
                SqlConfig.getSqlSessionManager().update("CodeTemplate.vacuumCodeTemplateTable");
            }

            // Invoke the code template plugins
            for (CodeTemplateServerPlugin codeTemplateServerPlugin : extensionController
                    .getCodeTemplateServerPlugins().values()) {
                codeTemplateServerPlugin.remove(codeTemplate, context);
            }

            // Re-compile the global scripts
            scriptController.compileGlobalScripts(contextFactoryController.getGlobalScriptContextFactory());
        } catch (Exception e) {
            throw new ControllerException(e);
        }

        // Update any libraries that were using the code template
        Set<CodeTemplateLibrary> libraries = new HashSet<CodeTemplateLibrary>(libraryCache.getAllItems().values());
        boolean changed = false;

        for (CodeTemplateLibrary library : libraries) {
            for (Iterator<CodeTemplate> it = library.getCodeTemplates().iterator(); it.hasNext();) {
                if (it.next().getId().equals(codeTemplate.getId())) {
                    it.remove();
                    changed = true;
                }
            }
        }

        if (changed) {
            updateLibraries(libraries, context, true);
        }
    }

    @Override
    public synchronized CodeTemplateLibrarySaveResult updateLibrariesAndTemplates(
            Set<CodeTemplateLibrary> libraries, Set<String> removedLibraryIds,
            Set<CodeTemplate> updatedCodeTemplates, Set<String> removedCodeTemplateIds, ServerEventContext context,
            boolean override) {
        // If override is disabled, first check all libraries and templates to make sure they haven't been modified already
        if (!override) {
            Map<String, CodeTemplateLibrary> libraryMap = libraryCache.getAllItems();

            for (CodeTemplateLibrary library : libraries) {
                CodeTemplateLibrary matchingLibrary = libraryMap.get(library.getId());

                if (matchingLibrary != null) {
                    if (!EqualsBuilder.reflectionEquals(library, matchingLibrary, "lastModified", "revision")) {
                        /*
                         * If it's not a new library, and its version is different from the one in
                         * the database (in case it has been changed on the server since the client
                         * started modifying it), and override is not enabled, then don't allow the
                         * update
                         */
                        if (!library.getRevision().equals(matchingLibrary.getRevision())) {
                            return new CodeTemplateLibrarySaveResult(true);
                        }
                    }

                    // If a matching library was found, always remove it from the map
                    libraryMap.remove(library.getId());
                }
            }

            // Remove any libraries that were expected to be removed
            for (String removedLibraryId : removedLibraryIds) {
                libraryMap.remove(removedLibraryId);
            }

            // If any libraries are left, the client is out of sync
            if (!libraryMap.isEmpty()) {
                return new CodeTemplateLibrarySaveResult(true);
            }

            Map<String, CodeTemplate> codeTemplateMap = codeTemplateCache.getAllItems();

            for (CodeTemplate codeTemplate : updatedCodeTemplates) {
                CodeTemplate matchingCodeTemplate = codeTemplateMap.get(codeTemplate.getId());

                if (matchingCodeTemplate != null) {
                    if (!EqualsBuilder.reflectionEquals(codeTemplate, matchingCodeTemplate, "lastModified",
                            "revision")) {
                        /*
                         * If it's not a new code template, and its version is different from the
                         * one in the database (in case it has been changed on the server since the
                         * client started modifying it), and override is not enabled, then don't
                         * allow the update
                         */
                        if (!matchingCodeTemplate.getRevision().equals(codeTemplate.getRevision())) {
                            return new CodeTemplateLibrarySaveResult(true);
                        }
                    }
                }
            }
        }

        CodeTemplateLibrarySaveResult updateSummary = new CodeTemplateLibrarySaveResult();

        try {
            updateSummary.setLibrariesSuccess(updateLibraries(libraries, context, override));

            for (CodeTemplateLibrary library : libraries) {
                LibraryUpdateResult result = new LibraryUpdateResult();
                result.setNewRevision(library.getRevision());
                result.setNewLastModified(library.getLastModified());
                updateSummary.getLibraryResults().put(library.getId(), result);
            }
        } catch (Throwable t) {
            updateSummary.setLibrariesSuccess(false);
            updateSummary.setLibrariesCause(convertUpdateCause(t));

            // If updating the libraries failed, don't go any further
            return updateSummary;
        }

        // Try updating each code template, storing the result in the summary
        for (CodeTemplate codeTemplate : updatedCodeTemplates) {
            CodeTemplateUpdateResult result = new CodeTemplateUpdateResult();

            try {
                result.setSuccess(updateCodeTemplate(codeTemplate, context, override));
                result.setNewRevision(codeTemplate.getRevision());
                result.setNewLastModified(codeTemplate.getLastModified());
            } catch (Throwable t) {
                result.setSuccess(false);
                result.setCause(convertUpdateCause(t));
            }

            updateSummary.getCodeTemplateResults().put(codeTemplate.getId(), result);
        }

        // Try removing each code template, storing the result in the summary
        for (String removedCodeTemplateId : removedCodeTemplateIds) {
            CodeTemplateUpdateResult result = new CodeTemplateUpdateResult();

            try {
                removeCodeTemplate(removedCodeTemplateId, context);
                result.setSuccess(true);
            } catch (Throwable t) {
                result.setSuccess(false);
                result.setCause(convertUpdateCause(t));
            }

            updateSummary.getCodeTemplateResults().put(removedCodeTemplateId, result);
        }

        return updateSummary;
    }

    private Throwable convertUpdateCause(Throwable t) {
        if (t instanceof ControllerException) {
            if (t.getCause() != null) {
                t = t.getCause();
            } else {
                StackTraceElement[] stackTrace = t.getStackTrace();
                t = new Exception(t.getMessage());
                t.setStackTrace(stackTrace);
            }
        }

        return t;
    }
}