Java tutorial
/* * 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.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import com.mirth.commons.encryption.Encryptor; import com.mirth.connect.client.core.ControllerException; import com.mirth.connect.donkey.model.channel.ConnectorProperties; import com.mirth.connect.donkey.model.channel.DeployedState; import com.mirth.connect.donkey.model.channel.DestinationConnectorProperties; import com.mirth.connect.donkey.model.channel.DestinationConnectorPropertiesInterface; import com.mirth.connect.donkey.model.channel.SourceConnectorProperties; import com.mirth.connect.donkey.model.channel.SourceConnectorPropertiesInterface; import com.mirth.connect.donkey.model.event.ErrorEventType; import com.mirth.connect.donkey.model.event.Event; import com.mirth.connect.donkey.model.message.BatchRawMessage; import com.mirth.connect.donkey.model.message.MessageSerializer; import com.mirth.connect.donkey.model.message.MessageSerializerException; import com.mirth.connect.donkey.model.message.RawMessage; import com.mirth.connect.donkey.model.message.SerializationType; import com.mirth.connect.donkey.model.message.Status; import com.mirth.connect.donkey.model.message.attachment.AttachmentHandlerProperties; import com.mirth.connect.donkey.model.message.attachment.AttachmentHandlerProvider; import com.mirth.connect.donkey.server.Constants; import com.mirth.connect.donkey.server.DeployException; import com.mirth.connect.donkey.server.Donkey; import com.mirth.connect.donkey.server.DonkeyConfiguration; import com.mirth.connect.donkey.server.StartException; import com.mirth.connect.donkey.server.StopException; import com.mirth.connect.donkey.server.channel.Channel; import com.mirth.connect.donkey.server.channel.ChannelException; import com.mirth.connect.donkey.server.channel.ChannelProcessLock; import com.mirth.connect.donkey.server.channel.Connector; import com.mirth.connect.donkey.server.channel.DefaultChannelProcessLock; import com.mirth.connect.donkey.server.channel.DestinationChainProvider; import com.mirth.connect.donkey.server.channel.DestinationConnector; import com.mirth.connect.donkey.server.channel.DispatchResult; import com.mirth.connect.donkey.server.channel.FilterTransformerExecutor; import com.mirth.connect.donkey.server.channel.MetaDataReplacer; import com.mirth.connect.donkey.server.channel.ResponseSelector; import com.mirth.connect.donkey.server.channel.ResponseTransformerExecutor; import com.mirth.connect.donkey.server.channel.SourceConnector; import com.mirth.connect.donkey.server.channel.Statistics; import com.mirth.connect.donkey.server.channel.StorageSettings; import com.mirth.connect.donkey.server.channel.components.PostProcessor; import com.mirth.connect.donkey.server.channel.components.PreProcessor; import com.mirth.connect.donkey.server.data.DonkeyDao; import com.mirth.connect.donkey.server.data.buffered.BufferedDaoFactory; import com.mirth.connect.donkey.server.data.passthru.PassthruDaoFactory; import com.mirth.connect.donkey.server.event.ErrorEvent; import com.mirth.connect.donkey.server.event.EventDispatcher; import com.mirth.connect.donkey.server.message.DataType; import com.mirth.connect.donkey.server.message.ResponseValidator; import com.mirth.connect.donkey.server.message.batch.BatchAdaptorFactory; import com.mirth.connect.donkey.server.message.batch.BatchMessageException; import com.mirth.connect.donkey.server.message.batch.BatchMessageReader; import com.mirth.connect.donkey.server.message.batch.ResponseHandler; import com.mirth.connect.donkey.server.message.batch.SimpleResponseHandler; import com.mirth.connect.donkey.server.queue.DestinationQueue; import com.mirth.connect.donkey.server.queue.SourceQueue; import com.mirth.connect.donkey.util.Serializer; import com.mirth.connect.donkey.util.SerializerProvider; import com.mirth.connect.model.ChannelProperties; import com.mirth.connect.model.ChannelStatistics; import com.mirth.connect.model.ConnectorMetaData; import com.mirth.connect.model.DashboardStatus; import com.mirth.connect.model.DashboardStatus.StatusType; import com.mirth.connect.model.DeployedChannelInfo; import com.mirth.connect.model.Filter; import com.mirth.connect.model.InvalidChannel; import com.mirth.connect.model.MessageStorageMode; import com.mirth.connect.model.ServerEventContext; import com.mirth.connect.model.Transformer; import com.mirth.connect.model.attachments.AttachmentHandlerType; import com.mirth.connect.model.converters.ObjectXMLSerializer; import com.mirth.connect.model.datatype.BatchProperties; import com.mirth.connect.model.datatype.DataTypeProperties; import com.mirth.connect.model.datatype.SerializerProperties; import com.mirth.connect.plugins.ChannelPlugin; import com.mirth.connect.plugins.DataTypeServerPlugin; import com.mirth.connect.server.ExtensionLoader; import com.mirth.connect.server.attachments.MirthAttachmentHandlerProvider; import com.mirth.connect.server.attachments.passthru.PassthruAttachmentHandlerProvider; import com.mirth.connect.server.builders.JavaScriptBuilder; import com.mirth.connect.server.channel.ChannelFuture; import com.mirth.connect.server.channel.ChannelTask; import com.mirth.connect.server.channel.ChannelTaskHandler; import com.mirth.connect.server.channel.DelegateErrorTaskHandler; import com.mirth.connect.server.channel.LoggingTaskHandler; import com.mirth.connect.server.channel.MirthMessageMaps; import com.mirth.connect.server.channel.MirthMetaDataReplacer; import com.mirth.connect.server.message.DataTypeFactory; import com.mirth.connect.server.message.DefaultResponseValidator; import com.mirth.connect.server.mybatis.MessageSearchResult; import com.mirth.connect.server.transformers.JavaScriptFilterTransformer; import com.mirth.connect.server.transformers.JavaScriptInitializationException; import com.mirth.connect.server.transformers.JavaScriptPostprocessor; import com.mirth.connect.server.transformers.JavaScriptPreprocessor; import com.mirth.connect.server.transformers.JavaScriptResponseTransformer; import com.mirth.connect.server.util.ChannelDependencyServerUtil; import com.mirth.connect.server.util.GlobalChannelVariableStoreFactory; import com.mirth.connect.server.util.GlobalVariableStore; import com.mirth.connect.server.util.javascript.JavaScriptExecutorException; import com.mirth.connect.server.util.javascript.JavaScriptUtil; import com.mirth.connect.server.util.javascript.MirthContextFactory; import com.mirth.connect.util.ChannelDependencyException; import com.mirth.connect.util.ChannelDependencyGraph; import com.mirth.connect.util.ChannelDependencyUtil; import com.mirth.connect.util.ChannelDependencyUtil.OrderedChannels; public class DonkeyEngineController implements EngineController { private static EngineController instance = null; public static EngineController getInstance() { synchronized (DonkeyEngineController.class) { if (instance == null) { instance = ExtensionLoader.getInstance().getControllerInstance(EngineController.class); if (instance == null) { instance = new DonkeyEngineController(); } } return instance; } } private Donkey donkey = Donkey.getInstance(); private Logger logger = Logger.getLogger(this.getClass()); private ConfigurationController configurationController = ControllerFactory.getFactory() .createConfigurationController(); private ScriptController scriptController = ControllerFactory.getFactory().createScriptController(); private ChannelController channelController = ControllerFactory.getFactory().createChannelController(); private com.mirth.connect.donkey.server.controllers.ChannelController donkeyChannelController = com.mirth.connect.donkey.server.controllers.ChannelController .getInstance(); private EventController eventController = ControllerFactory.getFactory().createEventController(); private ExtensionController extensionController = ControllerFactory.getFactory().createExtensionController(); private ContextFactoryController contextFactoryController = ControllerFactory.getFactory() .createContextFactoryController(); private CodeTemplateController codeTemplateController = ControllerFactory.getFactory() .createCodeTemplateController(); private Map<String, ExecutorService> engineExecutors = new ConcurrentHashMap<String, ExecutorService>(); private Set<Channel> deployingChannels = Collections.synchronizedSet(new HashSet<Channel>()); private Set<Channel> undeployingChannels = Collections.synchronizedSet(new HashSet<Channel>()); protected AtomicInteger queueBufferSize = new AtomicInteger(Constants.DEFAULT_QUEUE_BUFFER_SIZE); private enum StatusTask { START, STOP, PAUSE, RESUME }; protected DonkeyEngineController() { } @Override public void startEngine() throws StartException, StopException, ControllerException, InterruptedException { logger.debug("starting donkey engine"); Integer queueBufferSize = configurationController.getServerSettings().getQueueBufferSize(); if (queueBufferSize != null && queueBufferSize > 0) { this.queueBufferSize.set(queueBufferSize); } final Encryptor encryptor = configurationController.getEncryptor(); com.mirth.connect.donkey.server.Encryptor donkeyEncryptor = new com.mirth.connect.donkey.server.Encryptor() { @Override public String encrypt(String text) { return encryptor.encrypt(text); } @Override public String decrypt(String text) { return encryptor.decrypt(text); } }; EventDispatcher eventDispatcher = new EventDispatcher() { @Override public void dispatchEvent(Event event) { eventController.dispatchEvent(event); } }; Properties donkeyProperties = configurationController.getDatabaseSettings().getProperties(); donkeyProperties.setProperty("donkey.statsupdateinterval", String.valueOf(configurationController.getStatsUpdateInterval())); donkey.startEngine(new DonkeyConfiguration(configurationController.getApplicationDataDir(), donkeyProperties, donkeyEncryptor, eventDispatcher, configurationController.getServerId())); } @Override public void stopEngine() throws StopException, InterruptedException { undeployChannels(getDeployedIds(), ServerEventContext.SYSTEM_USER_EVENT_CONTEXT, null); donkey.stopEngine(); } @Override public boolean isRunning() { return donkey.isRunning(); } @Override public void startupDeploy() { deployChannels(channelController.getChannelIds(), ServerEventContext.SYSTEM_USER_EVENT_CONTEXT, null); } @Override public void deployChannels(Set<String> channelIds, ServerEventContext context, ChannelTaskHandler handler) { List<ChannelTask> unorderedUndeployTasks = new ArrayList<ChannelTask>(); List<ChannelTask> unorderedDeployTasks = new ArrayList<ChannelTask>(); List<List<ChannelTask>> orderedUndeployTasks = new ArrayList<List<ChannelTask>>(); List<List<ChannelTask>> orderedDeployTasks = new ArrayList<List<ChannelTask>>(); boolean hasUndeployTasks = false; boolean hasDeployTasks = false; Set<String> unorderedIds; List<Set<String>> orderedIds; ChannelDependencyGraph dependencyGraph; try { dependencyGraph = ChannelDependencyServerUtil.getDependencyGraph(); OrderedChannels orderedChannels = ChannelDependencyUtil.getOrderedChannels(channelIds, dependencyGraph); unorderedIds = orderedChannels.getUnorderedIds(); orderedIds = orderedChannels.getOrderedIds(); } catch (ChannelDependencyException e) { // Should never happen logger.error("Error deploying channels: " + e.getMessage(), e); return; } // Add all unordered undeploy/deploy tasks for (String channelId : unorderedIds) { if (isDeployed(channelId)) { unorderedUndeployTasks.add(new UndeployTask(channelId, context)); hasUndeployTasks = true; } unorderedDeployTasks.add(new DeployTask(channelId, null, null, context)); hasDeployTasks = true; } if (CollectionUtils.isNotEmpty(orderedIds)) { // Add ordered undeploy/deploy tasks one tier at a time for (Set<String> set : orderedIds) { List<ChannelTask> undeployTasks = new ArrayList<ChannelTask>(); List<ChannelTask> deployTasks = new ArrayList<ChannelTask>(); for (String channelId : set) { if (isDeployed(channelId)) { undeployTasks.add(new UndeployTask(channelId, context)); hasUndeployTasks = true; } deployTasks.add(new DeployTask(channelId, null, null, context)); hasDeployTasks = true; } // Add a tier for the ordered undeploy tasks if any were needed if (!undeployTasks.isEmpty()) { orderedUndeployTasks.add(undeployTasks); } /* * Add a tier for deploy tasks, but in reverse order. The dependency channels should * be deployed before the dependent ones. */ orderedDeployTasks.add(0, deployTasks); } } if (hasUndeployTasks) { // First submit undeploy tasks for all unordered channels; don't wait for them yet. List<ChannelFuture> unorderedUndeployFutures = null; if (CollectionUtils.isNotEmpty(unorderedUndeployTasks)) { unorderedUndeployFutures = submitTasks(unorderedUndeployTasks, handler); } // Submit and wait for all ordered undeploy tasks, one tier at a time. if (CollectionUtils.isNotEmpty(orderedUndeployTasks)) { for (List<ChannelTask> taskList : orderedUndeployTasks) { waitForTasks(submitTasks(taskList, handler)); } } // Wait for any unordered undeploy tasks submitted previously if (CollectionUtils.isNotEmpty(unorderedUndeployFutures)) { waitForTasks(unorderedUndeployFutures); } executeChannelPluginOnUndeploy(context); executeGlobalUndeployScript(); } if (hasDeployTasks) { // Update the default queue buffer size on deploy try { Integer queueBufferSize = configurationController.getServerSettings().getQueueBufferSize(); if (queueBufferSize != null && queueBufferSize > 0) { this.queueBufferSize.set(queueBufferSize); } } catch (ControllerException e) { } executeGlobalDeployScript(); executeChannelPluginOnDeploy(context); // First submit deploy tasks for all unordered channels; don't wait for them yet. List<ChannelFuture> unorderedDeployFutures = null; if (CollectionUtils.isNotEmpty(unorderedDeployTasks)) { unorderedDeployFutures = submitTasks(unorderedDeployTasks, handler); } // Submit and wait for all ordered deploy tasks, one tier at a time. if (CollectionUtils.isNotEmpty(orderedDeployTasks)) { for (int i = 0; i < orderedDeployTasks.size(); i++) { List<ChannelTask> taskList = orderedDeployTasks.get(i); DelegateErrorTaskHandler orderedHandler = new DelegateErrorTaskHandler(handler); waitForTasks(submitTasks(taskList, orderedHandler)); if (orderedHandler.isErrored()) { // Don't allow dependent channels in higher tiers to deploy Map<String, Exception> errorMap = orderedHandler.getErrorMap(); // Get all dependent IDs of any channel that failed to deploy Set<String> dependentIdsToRemove = new HashSet<String>(); for (String channelId : errorMap.keySet()) { Set<String> ids = dependencyGraph.getNode(channelId).getAllDependentElements(); if (CollectionUtils.isNotEmpty(ids)) { logger.error("Channel " + channelId + " failed to deploy. The following dependent channels will not be deployed:\n\t" + StringUtils.join(ids, "\n\t")); dependentIdsToRemove.addAll(ids); } } if (CollectionUtils.isNotEmpty(dependentIdsToRemove)) { // Iterate through the remaining tiers for (int j = i + 1; j < orderedDeployTasks.size(); j++) { List<ChannelTask> nextTaskList = orderedDeployTasks.get(j); // Remove any channel task associated with one of the IDs to remove for (Iterator<ChannelTask> it = nextTaskList.iterator(); it.hasNext();) { ChannelTask task = it.next(); if (dependentIdsToRemove.contains(task.getChannelId())) { it.remove(); } } // If there are no channel tasks left in the list, remove this tier altogether if (CollectionUtils.isEmpty(nextTaskList)) { orderedDeployTasks.remove(j); j--; } } } } } } // Wait for any unordered deploy tasks submitted previously if (CollectionUtils.isNotEmpty(unorderedDeployFutures)) { waitForTasks(unorderedDeployFutures); } } } @Override public void undeployChannels(Set<String> channelIds, ServerEventContext context, ChannelTaskHandler handler) { List<ChannelTask> unorderedUndeployTasks = new ArrayList<ChannelTask>(); List<List<ChannelTask>> orderedUndeployTasks = new ArrayList<List<ChannelTask>>(); Set<String> unorderedIds; List<Set<String>> orderedIds; try { OrderedChannels orderedChannels = ChannelDependencyServerUtil.getOrderedChannels(channelIds); unorderedIds = orderedChannels.getUnorderedIds(); orderedIds = orderedChannels.getOrderedIds(); } catch (ChannelDependencyException e) { // Should never happen logger.error("Error undeploying channels: " + e.getMessage(), e); return; } // Add all unordered undeploy tasks for (String channelId : unorderedIds) { unorderedUndeployTasks.add(new UndeployTask(channelId, context)); } if (CollectionUtils.isNotEmpty(orderedIds)) { // Add ordered undeploy tasks one tier at a time for (Set<String> set : orderedIds) { List<ChannelTask> undeployTasks = new ArrayList<ChannelTask>(); for (String channelId : set) { undeployTasks.add(new UndeployTask(channelId, context)); } orderedUndeployTasks.add(undeployTasks); } } if (CollectionUtils.isNotEmpty(unorderedUndeployTasks) || CollectionUtils.isNotEmpty(orderedUndeployTasks)) { // First submit undeploy tasks for all unordered channels; don't wait for them yet. List<ChannelFuture> unorderedUndeployFutures = null; if (CollectionUtils.isNotEmpty(unorderedUndeployTasks)) { unorderedUndeployFutures = submitTasks(unorderedUndeployTasks, handler); } // Submit and wait for all ordered undeploy tasks, one tier at a time. if (CollectionUtils.isNotEmpty(orderedUndeployTasks)) { for (List<ChannelTask> taskList : orderedUndeployTasks) { waitForTasks(submitTasks(taskList, handler)); } } // Wait for any unordered undeploy tasks submitted previously if (CollectionUtils.isNotEmpty(unorderedUndeployFutures)) { waitForTasks(unorderedUndeployFutures); } executeChannelPluginOnUndeploy(context); executeGlobalUndeployScript(); } } @Override public void redeployAllChannels(ServerEventContext context, ChannelTaskHandler handler) { undeployChannels(getDeployedIds(), context, handler); clearGlobalMap(); deployChannels(channelController.getChannelIds(), context, handler); } @Override public void startChannels(Set<String> channelIds, ChannelTaskHandler handler) { executeChannelStatusTasks(channelIds, handler, StatusTask.START); } @Override public void stopChannels(Set<String> channelIds, ChannelTaskHandler handler) { executeChannelStatusTasks(channelIds, handler, StatusTask.STOP); } @Override public void pauseChannels(Set<String> channelIds, ChannelTaskHandler handler) { executeChannelStatusTasks(channelIds, handler, StatusTask.PAUSE); } @Override public void resumeChannels(Set<String> channelIds, ChannelTaskHandler handler) { executeChannelStatusTasks(channelIds, handler, StatusTask.RESUME); } private void executeChannelStatusTasks(Set<String> channelIds, ChannelTaskHandler handler, StatusTask task) { List<ChannelTask> unorderedTasks = new ArrayList<ChannelTask>(); List<List<ChannelTask>> orderedTasks = new ArrayList<List<ChannelTask>>(); Set<String> unorderedIds; List<Set<String>> orderedIds; ChannelDependencyGraph dependencyGraph; try { dependencyGraph = ChannelDependencyServerUtil.getDependencyGraph(); OrderedChannels orderedChannels = ChannelDependencyUtil.getOrderedChannels(channelIds, dependencyGraph); unorderedIds = orderedChannels.getUnorderedIds(); orderedIds = orderedChannels.getOrderedIds(); } catch (ChannelDependencyException e) { // Should never happen logger.error("Error executing channel tasks: " + e.getMessage(), e); return; } // Add all unordered tasks for (String channelId : unorderedIds) { unorderedTasks.add(new ChannelStatusTask(channelId, task)); } if (CollectionUtils.isNotEmpty(orderedIds)) { // Add all ordered tasks, one tier at a time for (Set<String> set : orderedIds) { List<ChannelTask> tasks = new ArrayList<ChannelTask>(); for (String channelId : set) { tasks.add(new ChannelStatusTask(channelId, task)); } /* * For the start/resume tasks add the tier in reverse order. The dependency channels * should be started/resumed before the dependent ones. */ if (task == StatusTask.START || task == StatusTask.RESUME) { orderedTasks.add(0, tasks); } else if (task == StatusTask.STOP || task == StatusTask.PAUSE) { orderedTasks.add(tasks); } } } if (CollectionUtils.isNotEmpty(unorderedTasks) || CollectionUtils.isNotEmpty(orderedTasks)) { // First submit tasks for all unordered channels; don't wait for them yet. List<ChannelFuture> unorderedFutures = null; if (CollectionUtils.isNotEmpty(unorderedTasks)) { unorderedFutures = submitTasks(unorderedTasks, handler); } // Submit and wait for all ordered tasks, one tier at a time. if (CollectionUtils.isNotEmpty(orderedTasks)) { for (int i = 0; i < orderedTasks.size(); i++) { List<ChannelTask> taskList = orderedTasks.get(i); DelegateErrorTaskHandler orderedHandler = new DelegateErrorTaskHandler(handler); waitForTasks(submitTasks(taskList, orderedHandler)); if (orderedHandler.isErrored()) { // Don't allow dependent/dependency channel tasks in higher tiers to execute Map<String, Exception> errorMap = orderedHandler.getErrorMap(); Set<String> idsToRemove = new HashSet<String>(); for (String channelId : errorMap.keySet()) { if (task == StatusTask.START || task == StatusTask.RESUME) { // Get all dependent IDs of any channel that failed to start/resume Set<String> ids = dependencyGraph.getNode(channelId).getAllDependentElements(); if (CollectionUtils.isNotEmpty(ids)) { logger.error( "Channel " + channelId + " failed to " + task.toString().toLowerCase() + ". The following dependent channels will not be " + (task == StatusTask.START ? "started" : "resumed") + ":\n\t" + StringUtils.join(ids, "\n\t")); idsToRemove.addAll(ids); } } else if (task == StatusTask.STOP || task == StatusTask.PAUSE) { // Get all dependency IDs of any channel that failed to stop/pause Set<String> ids = dependencyGraph.getNode(channelId).getAllDependencyElements(); if (CollectionUtils.isNotEmpty(ids)) { logger.error( "Channel " + channelId + " failed to " + task.toString().toLowerCase() + ". The following dependency channels will not be " + (task == StatusTask.STOP ? "stopped" : "paused") + ":\n\t" + StringUtils.join(ids, "\n\t")); idsToRemove.addAll(ids); } } } if (CollectionUtils.isNotEmpty(idsToRemove)) { // Iterate through the remaining tiers for (int j = i + 1; j < orderedTasks.size(); j++) { List<ChannelTask> nextTaskList = orderedTasks.get(j); // Remove any channel task associated with one of the IDs to remove for (Iterator<ChannelTask> it = nextTaskList.iterator(); it.hasNext();) { ChannelTask channelTask = it.next(); if (idsToRemove.contains(channelTask.getChannelId())) { it.remove(); } } // If there are no channel tasks left in the list, remove this tier altogether if (CollectionUtils.isEmpty(nextTaskList)) { orderedTasks.remove(j); j--; } } } } } } // Wait for any unordered tasks submitted previously if (CollectionUtils.isNotEmpty(unorderedFutures)) { waitForTasks(unorderedFutures); } } } @Override public void startConnector(Map<String, List<Integer>> connectorInfo, ChannelTaskHandler handler) { waitForTasks(submitTasks(buildConnectorStatusTasks(connectorInfo, StatusTask.START), handler)); } @Override public void stopConnector(Map<String, List<Integer>> connectorInfo, ChannelTaskHandler handler) { waitForTasks(submitTasks(buildConnectorStatusTasks(connectorInfo, StatusTask.STOP), handler)); } @Override public void haltChannels(Set<String> channelIds, ChannelTaskHandler handler) { waitForTasks(submitHaltTasks(channelIds, handler)); } @Override public void removeChannels(Set<String> channelIds, ServerEventContext context, ChannelTaskHandler handler) { List<ChannelTask> tasks = new ArrayList<ChannelTask>(); for (com.mirth.connect.model.Channel channelModel : channelController.getChannels(channelIds)) { tasks.add(new UndeployTask(channelModel.getId(), context)); tasks.add(new RemoveTask(channelModel, context)); } if (CollectionUtils.isNotEmpty(tasks)) { waitForTasks(submitTasks(tasks, handler)); executeChannelPluginOnUndeploy(context); } } @Override public void removeMessages(String channelId, Map<Long, MessageSearchResult> results, ChannelTaskHandler handler) { List<ChannelTask> tasks = new ArrayList<ChannelTask>(); tasks.add(new RemoveMessagesTask(channelId, results)); waitForTasks(submitTasks(tasks, handler)); }; @Override public void removeAllMessages(Set<String> channelIds, boolean force, boolean clearStatistics, ChannelTaskHandler handler) { List<ChannelTask> tasks = new ArrayList<ChannelTask>(); for (String channelId : channelIds) { tasks.add(new RemoveAllMessagesTask(channelId, force, clearStatistics)); } waitForTasks(submitTasks(tasks, handler)); } @Override public DashboardStatus getChannelStatus(String channelId) { Channel channel = donkey.getDeployedChannels().get(channelId); if (channel != null) { return getDashboardStatuses(Collections.singleton(channel)).get(0); } return null; } @Override public List<DashboardStatus> getChannelStatusList() { return getChannelStatusList(null); } @Override public List<DashboardStatus> getChannelStatusList(Set<String> channelIds) { return getChannelStatusList(channelIds, false); } private Map<String, Channel> getDashboardChannels(Set<String> channelIds) { Map<String, Channel> channels = null; if (CollectionUtils.isNotEmpty(channelIds)) { channels = new HashMap<String, Channel>(); for (Channel channel : donkey.getDeployedChannels().values()) { if (channelIds.contains(channel.getChannelId())) { channels.put(channel.getChannelId(), channel); } } } else { channels = new HashMap<String, Channel>(donkey.getDeployedChannels()); } synchronized (deployingChannels) { for (Channel channel : deployingChannels) { if (!channels.containsKey(channel.getChannelId())) { channels.put(channel.getChannelId(), channel); } } } synchronized (undeployingChannels) { for (Channel channel : undeployingChannels) { if (!channels.containsKey(channel.getChannelId())) { channels.put(channel.getChannelId(), channel); } } } return channels; } @Override public List<DashboardStatus> getChannelStatusList(Set<String> channelIds, boolean includeUndeployed) { List<DashboardStatus> statusList = new ArrayList<>(); Map<String, Channel> dashboardChannels = getDashboardChannels(channelIds); statusList.addAll(getDashboardStatuses(dashboardChannels.values())); if (includeUndeployed) { Map<String, com.mirth.connect.model.Channel> channelModels = new HashMap<String, com.mirth.connect.model.Channel>(); for (com.mirth.connect.model.Channel channelModel : channelController.getChannels(null)) { if ((CollectionUtils.isEmpty(channelIds) || channelIds.contains(channelModel.getId())) && !dashboardChannels.keySet().contains(channelModel.getId())) { channelModels.put(channelModel.getId(), channelModel); } } statusList.addAll(getUndeployedDashboardStatuses(channelModels.values())); } return statusList; } private List<DashboardStatus> getUndeployedDashboardStatuses( Collection<com.mirth.connect.model.Channel> channelModels) { List<DashboardStatus> statuses = new ArrayList<DashboardStatus>(); Statistics stats = channelController.getStatisticsFromStorage(configurationController.getServerId()); Statistics lifetimeStats = channelController .getTotalStatisticsFromStorage(configurationController.getServerId()); String serverId = configurationController.getServerId(); for (com.mirth.connect.model.Channel channelModel : channelModels) { if (!(channelModel instanceof InvalidChannel)) { String channelId = channelModel.getId(); DashboardStatus status = new DashboardStatus(); status.setStatusType(StatusType.CHANNEL); status.setChannelId(channelId); status.setName(channelModel.getName()); status.setState(DeployedState.UNDEPLOYED); status.setDeployedDate(null); // TODO maybe look up the last deployed date? status.setDeployedRevisionDelta(0); status.setStatistics(stats.getConnectorStats(channelId, null)); status.setLifetimeStatistics(lifetimeStats.getConnectorStats(channelId, null)); status.setTags(channelModel.getProperties().getTags()); DashboardStatus sourceStatus = new DashboardStatus(); sourceStatus.setStatusType(StatusType.SOURCE_CONNECTOR); sourceStatus.setChannelId(channelId); sourceStatus.setMetaDataId(0); sourceStatus.setName("Source"); sourceStatus.setState(DeployedState.UNDEPLOYED); sourceStatus.setStatistics(stats.getConnectorStats(channelId, 0)); sourceStatus.setLifetimeStatistics(lifetimeStats.getConnectorStats(channelId, 0)); SourceConnectorProperties sourceProps = ((SourceConnectorPropertiesInterface) channelModel .getSourceConnector().getProperties()).getSourceConnectorProperties(); sourceStatus.setQueueEnabled(!sourceProps.isRespondAfterProcessing()); if (sourceStatus.isQueueEnabled()) { sourceStatus.setQueued((long) channelController.getConnectorMessageCount(channelId, serverId, 0, Status.RECEIVED)); } status.setQueued(sourceStatus.getQueued()); status.getChildStatuses().add(sourceStatus); for (com.mirth.connect.model.Connector destination : channelModel.getDestinationConnectors()) { Integer metaDataId = destination.getMetaDataId(); DestinationConnectorProperties destProps = ((DestinationConnectorPropertiesInterface) destination .getProperties()).getDestinationConnectorProperties(); DashboardStatus destinationStatus = new DashboardStatus(); destinationStatus.setStatusType(StatusType.DESTINATION_CONNECTOR); destinationStatus.setChannelId(channelId); destinationStatus.setMetaDataId(metaDataId); destinationStatus.setName(destination.getName()); destinationStatus.setState(DeployedState.UNDEPLOYED); destinationStatus.setStatistics(stats.getConnectorStats(channelId, metaDataId)); destinationStatus.setLifetimeStatistics(lifetimeStats.getConnectorStats(channelId, metaDataId)); destinationStatus.setQueueEnabled(destProps.isQueueEnabled()); destinationStatus.setQueued((long) channelController.getConnectorMessageCount(channelId, serverId, metaDataId, Status.QUEUED)); status.setQueued(status.getQueued() + destinationStatus.getQueued()); status.getChildStatuses().add(destinationStatus); } statuses.add(status); } } return statuses; } private List<DashboardStatus> getDashboardStatuses(Collection<Channel> channels) { List<DashboardStatus> statuses = new ArrayList<DashboardStatus>(); Map<String, Integer> channelRevisions = null; try { channelRevisions = channelController.getChannelRevisions(); } catch (ControllerException e) { logger.error("Error retrieving channel revisions", e); } for (Channel channel : channels) { String channelId = channel.getChannelId(); com.mirth.connect.model.Channel channelModel = channelController.getDeployedChannelById(channelId); // Make sure the channel is actually still deployed if (channelModel != null) { Statistics stats = channelController.getStatistics(); Statistics lifetimeStats = channelController.getTotalStatistics(); DashboardStatus status = new DashboardStatus(); status.setStatusType(StatusType.CHANNEL); status.setChannelId(channelId); status.setName(channel.getName()); status.setState(channel.getCurrentState()); status.setDeployedDate(channel.getDeployDate()); int channelRevision = 0; // Just in case the channel no longer exists if (channelRevisions != null && channelRevisions.containsKey(channelId)) { channelRevision = channelRevisions.get(channelId); status.setDeployedRevisionDelta(channelRevision - channelModel.getRevision()); try { DeployedChannelInfo deployedChannelInfo = channelController .getDeployedChannelInfoById(channelId); if (deployedChannelInfo != null && deployedChannelInfo.getCodeTemplateRevisions() != null && !deployedChannelInfo.getCodeTemplateRevisions().equals( codeTemplateController.getCodeTemplateRevisionsForChannel(channelId))) { status.setCodeTemplatesChanged(true); } } catch (ControllerException e) { } } status.setStatistics(stats.getConnectorStats(channelId, null)); status.setLifetimeStatistics(lifetimeStats.getConnectorStats(channelId, null)); status.setTags(channelModel.getProperties().getTags()); DashboardStatus sourceStatus = new DashboardStatus(); sourceStatus.setStatusType(StatusType.SOURCE_CONNECTOR); sourceStatus.setChannelId(channelId); sourceStatus.setMetaDataId(0); sourceStatus.setName("Source"); sourceStatus.setState(channel.getSourceConnector().getCurrentState()); sourceStatus.setStatistics(stats.getConnectorStats(channelId, 0)); sourceStatus.setLifetimeStatistics(lifetimeStats.getConnectorStats(channelId, 0)); sourceStatus.setQueueEnabled(!channel.getSourceConnector().isRespondAfterProcessing()); sourceStatus.setQueued(new Long(channel.getSourceQueue().size())); status.setQueued(sourceStatus.getQueued()); status.getChildStatuses().add(sourceStatus); for (DestinationChainProvider chainProvider : channel.getDestinationChainProviders()) { for (Entry<Integer, DestinationConnector> connectorEntry : chainProvider .getDestinationConnectors().entrySet()) { Integer metaDataId = connectorEntry.getKey(); DestinationConnector connector = connectorEntry.getValue(); DashboardStatus destinationStatus = new DashboardStatus(); destinationStatus.setStatusType(StatusType.DESTINATION_CONNECTOR); destinationStatus.setChannelId(channelId); destinationStatus.setMetaDataId(metaDataId); destinationStatus.setName(connector.getDestinationName()); destinationStatus.setState(connector.getCurrentState()); destinationStatus.setStatistics(stats.getConnectorStats(channelId, metaDataId)); destinationStatus .setLifetimeStatistics(lifetimeStats.getConnectorStats(channelId, metaDataId)); destinationStatus.setQueueEnabled(connector.isQueueEnabled()); destinationStatus.setQueued(new Long(connector.getQueue().size())); status.setQueued(status.getQueued() + destinationStatus.getQueued()); status.getChildStatuses().add(destinationStatus); } } statuses.add(status); } } Collections.sort(statuses, new Comparator<DashboardStatus>() { public int compare(DashboardStatus o1, DashboardStatus o2) { Calendar c1 = o1.getDeployedDate(); Calendar c2 = o2.getDeployedDate(); return ObjectUtils.compare(c1, c2); } }); return statuses; } @Override public List<ChannelStatistics> getChannelStatisticsList(Set<String> channelIds, boolean includeUndeployed) { return getChannelStatisticsList(channelIds, includeUndeployed, null, null); } @Override public List<ChannelStatistics> getChannelStatisticsList(Set<String> channelIds, boolean includeUndeployed, Set<Integer> includeMetadataIds, Set<Integer> excludeMetadataIds) { List<ChannelStatistics> statistics = new ArrayList<ChannelStatistics>(); Map<String, Channel> dashboardChannels = getDashboardChannels(channelIds); statistics.addAll( getDashboardChannelStatistics(dashboardChannels.values(), includeMetadataIds, excludeMetadataIds)); if (includeUndeployed) { Map<String, com.mirth.connect.model.Channel> channelModels = new HashMap<String, com.mirth.connect.model.Channel>(); for (com.mirth.connect.model.Channel channelModel : channelController.getChannels(null)) { if ((CollectionUtils.isEmpty(channelIds) || channelIds.contains(channelModel.getId())) && !dashboardChannels.keySet().contains(channelModel.getId())) { channelModels.put(channelModel.getId(), channelModel); } } statistics.addAll( getUndeployedChannelStatistics(channelModels.values(), includeMetadataIds, excludeMetadataIds)); } return statistics; } private List<ChannelStatistics> getDashboardChannelStatistics(Collection<Channel> channels, Set<Integer> includeMetaDataIds, Set<Integer> excludeMetaDataIds) { List<ChannelStatistics> statisticsList = new ArrayList<ChannelStatistics>(); Statistics stats = channelController.getStatistics(); String serverId = configurationController.getServerId(); for (Channel channel : channels) { String channelId = channel.getChannelId(); com.mirth.connect.model.Channel channelModel = channelController.getDeployedChannelById(channelId); // Make sure the channel is actually still deployed if (channelModel != null) { ChannelStatistics statistics = new ChannelStatistics(); statistics.setChannelId(channelId); statistics.setServerId(serverId); if (includeConnectorId(0, includeMetaDataIds, excludeMetaDataIds)) { Map<Status, Long> sourceConnectorStats = stats.getConnectorStats(channelId, 0); addConnectorToChannelStatistics(sourceConnectorStats, statistics, true); statistics.setQueued(new Long(channel.getSourceQueue().size())); } for (DestinationChainProvider chainProvider : channel.getDestinationChainProviders()) { for (Entry<Integer, DestinationConnector> connectorEntry : chainProvider .getDestinationConnectors().entrySet()) { DestinationConnector connector = connectorEntry.getValue(); Integer metaDataId = connector.getMetaDataId(); if (includeConnectorId(metaDataId, includeMetaDataIds, excludeMetaDataIds)) { Map<Status, Long> destinationConnectorStats = stats.getConnectorStats(channelId, metaDataId); addConnectorToChannelStatistics(destinationConnectorStats, statistics, false); statistics.setQueued(statistics.getQueued() + new Long(connector.getQueue().size())); } } } statisticsList.add(statistics); } } return statisticsList; } private List<ChannelStatistics> getUndeployedChannelStatistics( Collection<com.mirth.connect.model.Channel> channelModels, Set<Integer> includeMetaDataIds, Set<Integer> excludeMetaDataIds) { List<ChannelStatistics> statisticsList = new ArrayList<ChannelStatistics>(); Statistics stats = channelController.getStatisticsFromStorage(configurationController.getServerId()); String serverId = configurationController.getServerId(); for (com.mirth.connect.model.Channel channelModel : channelModels) { if (!(channelModel instanceof InvalidChannel)) { ChannelStatistics statistics = new ChannelStatistics(); String channelId = channelModel.getId(); statistics.setChannelId(channelId); statistics.setServerId(serverId); if (includeConnectorId(0, includeMetaDataIds, excludeMetaDataIds)) { Map<Status, Long> sourceConnectorStats = stats.getConnectorStats(channelId, 0); addConnectorToChannelStatistics(sourceConnectorStats, statistics, true); if (!((SourceConnectorPropertiesInterface) channelModel.getSourceConnector().getProperties()) .getSourceConnectorProperties().isRespondAfterProcessing()) { statistics.setQueued((long) channelController.getConnectorMessageCount(channelId, serverId, 0, Status.RECEIVED)); } } for (com.mirth.connect.model.Connector destination : channelModel.getDestinationConnectors()) { Integer metaDataId = destination.getMetaDataId(); if (includeConnectorId(metaDataId, includeMetaDataIds, excludeMetaDataIds)) { Map<Status, Long> destinationConnectorStats = stats.getConnectorStats(channelId, metaDataId); addConnectorToChannelStatistics(destinationConnectorStats, statistics, false); statistics.setQueued(statistics.getQueued() + (long) channelController .getConnectorMessageCount(channelId, serverId, metaDataId, Status.QUEUED)); } } statisticsList.add(statistics); } } return statisticsList; } private ChannelStatistics addConnectorToChannelStatistics(Map<Status, Long> stats, ChannelStatistics statistics, boolean sourceConnector) { if (statistics == null) { statistics = new ChannelStatistics(); } if (sourceConnector) { statistics.setReceived(statistics.getReceived() + stats.get(Status.RECEIVED)); } else { statistics.setSent(statistics.getSent() + stats.get(Status.SENT)); } statistics.setError(statistics.getError() + stats.get(Status.ERROR)); statistics.setFiltered(statistics.getFiltered() + stats.get(Status.FILTERED)); return statistics; } private boolean includeConnectorId(Integer metaDataId, Set<Integer> includeMetaDataIds, Set<Integer> excludeMetaDataIds) { return (CollectionUtils.isEmpty(includeMetaDataIds) || includeMetaDataIds.contains(metaDataId)) && (CollectionUtils.isEmpty(excludeMetaDataIds) || !excludeMetaDataIds.contains(metaDataId)); } @Override public Set<String> getDeployedIds() { return donkey.getDeployedChannelIds(); } @Override public boolean isDeployed(String channelId) { return donkey.getDeployedChannels().containsKey(channelId); } @Override public Channel getDeployedChannel(String channelId) { return donkey.getDeployedChannels().get(channelId); } @Override public DispatchResult dispatchRawMessage(String channelId, RawMessage rawMessage, boolean force, boolean canBatch) throws ChannelException, BatchMessageException { if (!isDeployed(channelId)) { ChannelException e = new ChannelException(true); logger.error("Could not find channel to route to: " + channelId, e); throw e; } SourceConnector sourceConnector = donkey.getDeployedChannels().get(channelId).getSourceConnector(); if (canBatch && sourceConnector.isProcessBatch()) { if (rawMessage.isBinary()) { throw new BatchMessageException("Batch processing is not supported for binary data."); } else { BatchRawMessage batchRawMessage = new BatchRawMessage( new BatchMessageReader(rawMessage.getRawData()), rawMessage.getSourceMap()); ResponseHandler responseHandler = new SimpleResponseHandler(); sourceConnector.dispatchBatchMessage(batchRawMessage, responseHandler, rawMessage.getDestinationMetaDataIds()); return responseHandler.getResultForResponse(); } } else { DispatchResult dispatchResult = null; try { dispatchResult = sourceConnector.dispatchRawMessage(rawMessage, force); dispatchResult.setAttemptedResponse(true); } finally { sourceConnector.finishDispatch(dispatchResult); } return dispatchResult; } } protected Channel createChannelFromModel(com.mirth.connect.model.Channel channelModel) throws Exception { String channelId = channelModel.getId(); ChannelProperties channelProperties = channelModel.getProperties(); StorageSettings storageSettings = getStorageSettings(channelProperties.getMessageStorageMode(), channelProperties); Channel channel = new Channel(); channel.setResourceIds(channelModel.getProperties().getResourceIds().keySet()); MirthContextFactory contextFactory = contextFactoryController .getContextFactory(channelModel.getProperties().getResourceIds().keySet()); channel.setContextFactoryId(contextFactory.getId()); Map<String, Integer> destinationIdMap = new LinkedHashMap<String, Integer>(); channel.setChannelId(channelId); channel.setLocalChannelId(donkeyChannelController.getLocalChannelId(channelId)); channel.setServerId(ConfigurationController.getInstance().getServerId()); channel.setName(channelModel.getName()); channel.setEnabled(channelModel.isEnabled()); channel.setRevision(channelModel.getRevision()); channel.setInitialState(channelProperties.getInitialState()); channel.setStorageSettings(storageSettings); channel.setMetaDataColumns(channelProperties.getMetaDataColumns()); channel.setAttachmentHandlerProvider(createAttachmentHandlerProvider(channel, contextFactory, channelProperties.getAttachmentProperties())); channel.setPreProcessor(createPreProcessor(channel, channelModel.getPreprocessingScript())); channel.setPostProcessor(createPostProcessor(channel, channelModel.getPostprocessingScript())); channel.setSourceConnector(createSourceConnector(channel, channelModel.getSourceConnector(), storageSettings, destinationIdMap)); channel.setResponseSelector(new ResponseSelector(channel.getSourceConnector().getInboundDataType())); channel.setMessageMaps(new MirthMessageMaps(channelId)); SourceConnectorProperties sourceConnectorProperties = ((SourceConnectorPropertiesInterface) channelModel .getSourceConnector().getProperties()).getSourceConnectorProperties(); channel.getResponseSelector().setRespondFromName(sourceConnectorProperties.getResponseVariable()); SourceQueue sourceQueue = new SourceQueue(); if (sourceConnectorProperties.getQueueBufferSize() > 0) { sourceQueue.setBufferCapacity(sourceConnectorProperties.getQueueBufferSize()); } else { sourceQueue.setBufferCapacity(queueBufferSize.get()); } channel.setSourceQueue(sourceQueue); channel.setProcessLock(getChannelProcessLock(channelModel)); if (storageSettings.isEnabled()) { SerializerProvider serializerProvider = createSerializerProvider(channelModel); BufferedDaoFactory bufferedDaoFactory = new BufferedDaoFactory(donkey.getDaoFactory(), serializerProvider, donkey.getStatisticsUpdater()); bufferedDaoFactory.setEncryptData(channelProperties.isEncryptData()); channel.setDaoFactory(bufferedDaoFactory); } else { channel.setDaoFactory(new PassthruDaoFactory(donkey.getStatisticsUpdater())); } DestinationChainProvider chain = createDestinationChain(channel); for (com.mirth.connect.model.Connector connectorModel : channelModel.getDestinationConnectors()) { if (connectorModel.isEnabled()) { // read 'waitForPrevious' property and add new chains as needed // if there are currently no chains, add a new one regardless of 'waitForPrevious' if (!connectorModel.isWaitForPrevious() || channel.getDestinationChainProviders().size() == 0) { chain = createDestinationChain(channel); channel.addDestinationChainProvider(chain); } Integer metaDataId = connectorModel.getMetaDataId(); destinationIdMap.put(connectorModel.getName(), metaDataId); if (metaDataId == null) { metaDataId = channelModel.getNextMetaDataId(); channelModel.setNextMetaDataId(metaDataId + 1); connectorModel.setMetaDataId(metaDataId); } chain.addDestination(connectorModel.getMetaDataId(), createDestinationConnector(channel, connectorModel, storageSettings, destinationIdMap)); } } return channel; } protected ChannelProcessLock getChannelProcessLock(com.mirth.connect.model.Channel channelModel) { int processingThreads = ((SourceConnectorPropertiesInterface) channelModel.getSourceConnector() .getProperties()).getSourceConnectorProperties().getProcessingThreads(); if (processingThreads < 1) { processingThreads = 1; } return new DefaultChannelProcessLock(processingThreads); } protected SerializerProvider createSerializerProvider(com.mirth.connect.model.Channel channelModel) { final Map<Integer, Set<String>> resourceIdMap = new HashMap<Integer, Set<String>>(); resourceIdMap.put(null, channelModel.getProperties().getResourceIds().keySet()); resourceIdMap.put(0, ((SourceConnectorPropertiesInterface) channelModel.getSourceConnector().getProperties()) .getSourceConnectorProperties().getResourceIds().keySet()); for (com.mirth.connect.model.Connector destinationConnector : channelModel.getDestinationConnectors()) { resourceIdMap.put(destinationConnector.getMetaDataId(), ((DestinationConnectorPropertiesInterface) destinationConnector.getProperties()) .getDestinationConnectorProperties().getResourceIds().keySet()); } return new SerializerProvider() { @Override public Serializer getSerializer(Integer metaDataId) { try { MirthContextFactory contextFactory = contextFactoryController .getContextFactory(resourceIdMap.get(metaDataId)); if (contextFactory != null) { return contextFactory.getSerializer(); } } catch (Throwable t) { } return ObjectXMLSerializer.getInstance(); } }; } public static StorageSettings getStorageSettings(MessageStorageMode messageStorageMode, ChannelProperties channelProperties) { StorageSettings storageSettings = new StorageSettings(); storageSettings.setRemoveContentOnCompletion(channelProperties.isRemoveContentOnCompletion()); storageSettings.setRemoveOnlyFilteredOnCompletion(channelProperties.isRemoveOnlyFilteredOnCompletion()); storageSettings.setRemoveAttachmentsOnCompletion(channelProperties.isRemoveAttachmentsOnCompletion()); storageSettings.setStoreAttachments(channelProperties.isStoreAttachments()); // we assume that all storage settings are enabled by default switch (messageStorageMode) { case PRODUCTION: storageSettings.setStoreProcessedRaw(false); storageSettings.setStoreTransformed(false); storageSettings.setStoreResponseTransformed(false); storageSettings.setStoreProcessedResponse(false); break; case RAW: storageSettings.setMessageRecoveryEnabled(false); storageSettings.setDurable(false); storageSettings.setStoreMaps(false); storageSettings.setStoreResponseMap(false); storageSettings.setStoreMergedResponseMap(false); storageSettings.setStoreProcessedRaw(false); storageSettings.setStoreTransformed(false); storageSettings.setStoreSourceEncoded(false); storageSettings.setStoreDestinationEncoded(false); storageSettings.setStoreSent(false); storageSettings.setStoreResponseTransformed(false); storageSettings.setStoreProcessedResponse(false); storageSettings.setStoreResponse(false); storageSettings.setStoreSentResponse(false); break; case METADATA: storageSettings.setMessageRecoveryEnabled(false); storageSettings.setDurable(false); storageSettings.setRawDurable(false); storageSettings.setStoreMaps(false); storageSettings.setStoreResponseMap(false); storageSettings.setStoreMergedResponseMap(false); storageSettings.setStoreRaw(false); storageSettings.setStoreProcessedRaw(false); storageSettings.setStoreTransformed(false); storageSettings.setStoreSourceEncoded(false); storageSettings.setStoreDestinationEncoded(false); storageSettings.setStoreSent(false); storageSettings.setStoreResponseTransformed(false); storageSettings.setStoreProcessedResponse(false); storageSettings.setStoreResponse(false); storageSettings.setStoreSentResponse(false); break; case DISABLED: storageSettings.setEnabled(false); storageSettings.setMessageRecoveryEnabled(false); storageSettings.setDurable(false); storageSettings.setRawDurable(false); storageSettings.setStoreCustomMetaData(false); storageSettings.setStoreMaps(false); storageSettings.setStoreResponseMap(false); storageSettings.setStoreMergedResponseMap(false); storageSettings.setStoreRaw(false); storageSettings.setStoreProcessedRaw(false); storageSettings.setStoreTransformed(false); storageSettings.setStoreSourceEncoded(false); storageSettings.setStoreDestinationEncoded(false); storageSettings.setStoreSent(false); storageSettings.setStoreResponseTransformed(false); storageSettings.setStoreProcessedResponse(false); storageSettings.setStoreResponse(false); storageSettings.setStoreSentResponse(false); break; } return storageSettings; } private AttachmentHandlerProvider createAttachmentHandlerProvider(Channel channel, MirthContextFactory contextFactory, AttachmentHandlerProperties attachmentHandlerProperties) throws Exception { AttachmentHandlerProvider attachmentHandlerProvider = null; if (AttachmentHandlerType.fromString(attachmentHandlerProperties.getType()) != AttachmentHandlerType.NONE) { Class<?> attachmentHandlerProviderClass = Class.forName(attachmentHandlerProperties.getClassName(), true, contextFactory.getApplicationClassLoader()); if (MirthAttachmentHandlerProvider.class.isAssignableFrom(attachmentHandlerProviderClass)) { attachmentHandlerProvider = (MirthAttachmentHandlerProvider) attachmentHandlerProviderClass .newInstance(); attachmentHandlerProvider.setProperties(channel, attachmentHandlerProperties); } else { throw new Exception(attachmentHandlerProperties.getClassName() + " does not extend " + MirthAttachmentHandlerProvider.class.getName()); } } else { attachmentHandlerProvider = new PassthruAttachmentHandlerProvider(); } return attachmentHandlerProvider; } private PreProcessor createPreProcessor(Channel channel, String preProcessingScript) throws JavaScriptInitializationException { return new JavaScriptPreprocessor(channel, preProcessingScript); } private PostProcessor createPostProcessor(Channel channel, String postProcessingScript) throws JavaScriptInitializationException { return new JavaScriptPostprocessor(channel, postProcessingScript); } private SourceConnector createSourceConnector(Channel channel, com.mirth.connect.model.Connector connectorModel, StorageSettings storageSettings, Map<String, Integer> destinationIdMap) throws Exception { ExtensionController extensionController = ControllerFactory.getFactory().createExtensionController(); ConnectorProperties connectorProperties = connectorModel.getProperties(); ConnectorMetaData connectorMetaData = extensionController.getConnectorMetaData() .get(connectorProperties.getName()); SourceConnector sourceConnector = (SourceConnector) Class.forName(connectorMetaData.getServerClassName()) .newInstance(); setCommonConnectorProperties(channel.getChannelId(), sourceConnector, connectorModel, destinationIdMap); sourceConnector.setMetaDataReplacer(createMetaDataReplacer(connectorModel)); sourceConnector.setChannel(channel); SourceConnectorProperties sourceConnectorProperties = ((SourceConnectorPropertiesInterface) connectorProperties) .getSourceConnectorProperties(); sourceConnector.setRespondAfterProcessing(sourceConnectorProperties.isRespondAfterProcessing()); sourceConnector.setResourceIds(sourceConnectorProperties.getResourceIds().keySet()); DataTypeServerPlugin dataTypePlugin = ExtensionController.getInstance().getDataTypePlugins() .get(connectorModel.getTransformer().getInboundDataType()); DataTypeProperties dataTypeProperties = connectorModel.getTransformer().getInboundProperties(); SerializerProperties serializerProperties = dataTypeProperties.getSerializerProperties(); BatchProperties batchProperties = serializerProperties.getBatchProperties(); if (batchProperties != null && sourceConnectorProperties.isProcessBatch()) { BatchAdaptorFactory batchAdaptorFactory = dataTypePlugin.getBatchAdaptorFactory(sourceConnector, serializerProperties); batchAdaptorFactory.setUseFirstReponse(sourceConnectorProperties.isFirstResponse()); sourceConnector.setBatchAdaptorFactory(batchAdaptorFactory); } sourceConnector.setFilterTransformerExecutor( createFilterTransformerExecutor(sourceConnector, connectorModel, destinationIdMap)); return sourceConnector; } private FilterTransformerExecutor createFilterTransformerExecutor(Connector connector, com.mirth.connect.model.Connector connectorModel, Map<String, Integer> destinationIdMap) throws Exception { boolean runFilterTransformer = false; String template = null; Transformer transformer = connectorModel.getTransformer(); Filter filter = connectorModel.getFilter(); DataType inboundDataType = DataTypeFactory.getDataType(transformer.getInboundDataType(), transformer.getInboundProperties()); DataType outboundDataType = DataTypeFactory.getDataType(transformer.getOutboundDataType(), transformer.getOutboundProperties()); // Check the conditions for skipping transformation // 1. Script is not empty // 2. Data Types are different // 3. The data type has properties settings that require a transformation // 4. The outbound template is not empty if (!filter.getRules().isEmpty() || !transformer.getSteps().isEmpty() || !transformer.getInboundDataType().equals(transformer.getOutboundDataType())) { runFilterTransformer = true; } // Ask the inbound serializer if it needs to be transformed with serialization if (!runFilterTransformer) { runFilterTransformer = inboundDataType.getSerializer().isSerializationRequired(true); } // Ask the outbound serializier if it needs to be transformed with serialization if (!runFilterTransformer) { runFilterTransformer = outboundDataType.getSerializer().isSerializationRequired(false); } // Serialize the outbound template if needed if (StringUtils.isNotBlank(transformer.getOutboundTemplate())) { DataTypeServerPlugin outboundServerPlugin = ExtensionController.getInstance().getDataTypePlugins() .get(transformer.getOutboundDataType()); MessageSerializer serializer = outboundServerPlugin .getSerializer(transformer.getOutboundProperties().getSerializerProperties()); // Serialize template to XML only if serialization type is XML if (outboundServerPlugin.isBinary() || outboundServerPlugin.getSerializationType() != SerializationType.XML) { template = transformer.getOutboundTemplate(); } else { try { template = serializer.toXML(transformer.getOutboundTemplate()); } catch (MessageSerializerException e) { throw new MessageSerializerException( "Error serializing transformer outbound template for connector \"" + connectorModel.getName() + "\": " + e.getMessage(), e.getCause(), e.getFormattedError()); } } runFilterTransformer = true; } FilterTransformerExecutor filterTransformerExecutor = new FilterTransformerExecutor(inboundDataType, outboundDataType); if (runFilterTransformer) { String script = JavaScriptBuilder.generateFilterTransformerScript(filter, transformer); filterTransformerExecutor.setFilterTransformer( new JavaScriptFilterTransformer(connector, connectorModel.getName(), script, template)); } return filterTransformerExecutor; } private ResponseTransformerExecutor createResponseTransformerExecutor(Connector connector, com.mirth.connect.model.Connector connectorModel, Map<String, Integer> destinationIdMap) throws Exception { boolean runResponseTransformer = false; String template = null; Transformer transformer = connectorModel.getResponseTransformer(); DataType inboundDataType = DataTypeFactory.getDataType(transformer.getInboundDataType(), transformer.getInboundProperties()); DataType outboundDataType = DataTypeFactory.getDataType(transformer.getOutboundDataType(), transformer.getOutboundProperties()); // Check the conditions for skipping transformation // 1. Script is not empty // 2. Data Types are different // 3. The data type has properties settings that require a transformation // 4. The outbound template is not empty if (!transformer.getSteps().isEmpty() || !transformer.getInboundDataType().equals(transformer.getOutboundDataType())) { runResponseTransformer = true; } // Ask the inbound serializer if it needs to be transformed with serialization if (!runResponseTransformer) { runResponseTransformer = inboundDataType.getSerializer().isSerializationRequired(true); } // Ask the outbound serializier if it needs to be transformed with serialization if (!runResponseTransformer) { runResponseTransformer = outboundDataType.getSerializer().isSerializationRequired(false); } // Serialize the outbound template if needed if (StringUtils.isNotBlank(transformer.getOutboundTemplate())) { DataTypeServerPlugin outboundServerPlugin = ExtensionController.getInstance().getDataTypePlugins() .get(transformer.getOutboundDataType()); MessageSerializer serializer = outboundServerPlugin .getSerializer(transformer.getOutboundProperties().getSerializerProperties()); // Serialize template to XML only if serialization type is XML if (outboundServerPlugin.isBinary() || outboundServerPlugin.getSerializationType() != SerializationType.XML) { template = transformer.getOutboundTemplate(); } else { try { template = serializer.toXML(transformer.getOutboundTemplate()); } catch (MessageSerializerException e) { throw new MessageSerializerException( "Error serializing response transformer outbound template for connector \"" + connectorModel.getName() + "\": " + e.getMessage(), e.getCause(), e.getFormattedError()); } } runResponseTransformer = true; } ResponseTransformerExecutor responseTransformerExecutor = new ResponseTransformerExecutor(inboundDataType, outboundDataType); if (runResponseTransformer) { String script = JavaScriptBuilder.generateResponseTransformerScript(transformer); responseTransformerExecutor.setResponseTransformer( new JavaScriptResponseTransformer(connector, connectorModel.getName(), script, template)); } return responseTransformerExecutor; } private DestinationChainProvider createDestinationChain(Channel channel) { DestinationChainProvider chain = new DestinationChainProvider(); chain.setChannelId(channel.getChannelId()); return chain; } private DestinationConnector createDestinationConnector(Channel channel, com.mirth.connect.model.Connector connectorModel, StorageSettings storageSettings, Map<String, Integer> destinationIdMap) throws Exception { ExtensionController extensionController = ControllerFactory.getFactory().createExtensionController(); ConnectorProperties connectorProperties = connectorModel.getProperties(); ConnectorMetaData connectorMetaData = extensionController.getConnectorMetaData() .get(connectorProperties.getName()); String className = connectorMetaData.getServerClassName(); DestinationConnector destinationConnector = (DestinationConnector) Class.forName(className).newInstance(); setCommonConnectorProperties(channel.getChannelId(), destinationConnector, connectorModel, destinationIdMap); destinationConnector.setChannel(channel); DestinationConnectorProperties destinationConnectorProperties = ((DestinationConnectorPropertiesInterface) connectorProperties) .getDestinationConnectorProperties(); destinationConnector.setResourceIds(destinationConnectorProperties.getResourceIds().keySet()); destinationConnector.setFilterTransformerExecutor( createFilterTransformerExecutor(destinationConnector, connectorModel, destinationIdMap)); destinationConnector.setDestinationName(connectorModel.getName()); destinationConnector.setMetaDataReplacer(channel.getSourceConnector().getMetaDataReplacer()); destinationConnector.setMetaDataColumns(channel.getMetaDataColumns()); // Create the response validator DataTypeServerPlugin dataTypePlugin = ExtensionController.getInstance().getDataTypePlugins() .get(connectorModel.getResponseTransformer().getInboundDataType()); DataTypeProperties dataTypeProperties = connectorModel.getResponseTransformer().getInboundProperties(); SerializerProperties serializerProperties = dataTypeProperties.getSerializerProperties(); ResponseValidator responseValidator = dataTypePlugin.getResponseValidator( serializerProperties.getSerializationProperties(), dataTypeProperties.getResponseValidationProperties()); if (responseValidator == null) { responseValidator = new DefaultResponseValidator(); } destinationConnector.setResponseValidator(responseValidator); destinationConnector.setResponseTransformerExecutor( createResponseTransformerExecutor(destinationConnector, connectorModel, destinationIdMap)); DestinationQueue queue = new DestinationQueue(destinationConnectorProperties.getThreadAssignmentVariable(), destinationConnectorProperties.getThreadCount(), destinationConnectorProperties.isRegenerateTemplate(), destinationConnector.getSerializer(), destinationConnector.getMessageMaps()); queue.setRotate(destinationConnector.isQueueRotate()); if (destinationConnectorProperties.getQueueBufferSize() > 0) { queue.setBufferCapacity(destinationConnectorProperties.getQueueBufferSize()); } else { queue.setBufferCapacity(queueBufferSize.get()); } destinationConnector.setQueue(queue); return destinationConnector; } private void setCommonConnectorProperties(String channelId, Connector connector, com.mirth.connect.model.Connector connectorModel, Map<String, Integer> destinationIdMap) { connector.setChannelId(channelId); connector.setMetaDataId(connectorModel.getMetaDataId()); connector.setConnectorProperties(connectorModel.getProperties()); connector.setDestinationIdMap(destinationIdMap); Transformer transformerModel = connectorModel.getTransformer(); connector.setInboundDataType(DataTypeFactory.getDataType(transformerModel.getInboundDataType(), transformerModel.getInboundProperties())); connector.setOutboundDataType(DataTypeFactory.getDataType(transformerModel.getOutboundDataType(), transformerModel.getOutboundProperties())); } private MetaDataReplacer createMetaDataReplacer(com.mirth.connect.model.Connector connectorModel) { // TODO: Extract this from the Connector model based on the inbound data type return new MirthMetaDataReplacer(); } private void clearGlobalChannelMap(com.mirth.connect.model.Channel channelModel) { if (channelModel.getProperties().isClearGlobalChannelMap()) { logger.debug("clearing global channel map for channel: " + channelModel.getId()); GlobalChannelVariableStoreFactory.getInstance().get(channelModel.getId()).clear(); GlobalChannelVariableStoreFactory.getInstance().get(channelModel.getId()).clearSync(); } } private void clearGlobalMap() { try { if (configurationController.getServerSettings().getClearGlobalMap() == null || configurationController.getServerSettings().getClearGlobalMap()) { logger.debug("clearing global map"); GlobalVariableStore globalVariableStore = GlobalVariableStore.getInstance(); globalVariableStore.clear(); globalVariableStore.clearSync(); } } catch (ControllerException e) { logger.error("Could not clear the global map.", e); } } protected void executeGlobalDeployScript() { try { scriptController.executeGlobalDeployScript(); } catch (Exception e) { logger.error("Error executing global deploy script.", e); } } protected void executeGlobalUndeployScript() { try { scriptController.executeGlobalUndeployScript(); } catch (Exception e) { logger.error("Error executing global undeploy script.", e); } } protected void executeChannelPluginOnDeploy(ServerEventContext context) { // Execute the overall channel plugin deploy hook for (ChannelPlugin channelPlugin : extensionController.getChannelPlugins().values()) { channelPlugin.deploy(context); } } protected void executeChannelPluginOnUndeploy(ServerEventContext context) { // Execute the overall channel plugin undeploy hook for (ChannelPlugin channelPlugin : extensionController.getChannelPlugins().values()) { channelPlugin.undeploy(context); } } protected void shutdownExecutor(String channelId) { ExecutorService engineExecutor = engineExecutors.get(channelId); if (engineExecutor != null) { List<Runnable> tasks = engineExecutor.shutdownNow(); // Cancel any tasks that had not yet started. Otherwise those tasks would be blocked at future.get() indefinitely. for (Runnable task : tasks) { ((Future<?>) task).cancel(true); } } } protected synchronized void removeExecutor(String channelId) { // Shutdown the executor to prevent any new tasks from being submitted. shutdownExecutor(channelId); // Remove the executor since it has been shutdown. If another task comes in for this channel Id, a new executor will be created. engineExecutors.remove(channelId); } private List<ChannelTask> buildConnectorStatusTasks(Map<String, List<Integer>> connectorInfo, StatusTask task) { List<ChannelTask> tasks = new ArrayList<ChannelTask>(); for (Entry<String, List<Integer>> entry : connectorInfo.entrySet()) { String channelId = entry.getKey(); List<Integer> metaDataIds = entry.getValue(); for (Integer metaDataId : metaDataIds) { tasks.add(new ConnectorStatusTask(channelId, metaDataId, task)); } } return tasks; } @Override public synchronized List<ChannelFuture> submitTasks(List<ChannelTask> tasks, ChannelTaskHandler handler) { List<ChannelFuture> futures = new ArrayList<ChannelFuture>(); /* * If no handler is given then use the default handler to that at least errors will be * logged out. */ if (handler == null) { handler = new LoggingTaskHandler(); } for (ChannelTask task : tasks) { ExecutorService engineExecutor = engineExecutors.get(task.getChannelId()); if (engineExecutor == null) { engineExecutor = new ThreadPoolExecutor(0, 1, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); engineExecutors.put(task.getChannelId(), engineExecutor); } task.setHandler(handler); try { futures.add(task.submitTo(engineExecutor)); } catch (RejectedExecutionException e) { /* * This can happen if a channel was halted, in which case we don't want to perform * whatever task this was anyway. */ handler.taskErrored(task.getChannelId(), task.getMetaDataId(), e); } } return futures; } private List<ChannelFuture> submitHaltTasks(Set<String> channelIds, ChannelTaskHandler handler) { List<ChannelFuture> futures = new ArrayList<ChannelFuture>(); /* * If no handler is given then use the default handler to that at least errors will be * logged out. */ if (handler == null) { handler = new LoggingTaskHandler(); } for (String channelId : channelIds) { /* * Shutdown the executor to prevent any new tasks from being submitted. This needs to be * called once outside of the synchronized block in order to halt certain actions such * as restoring server configuration. */ shutdownExecutor(channelId); synchronized (this) { /* * Shutdown the executor to prevent any new tasks from being submitted. This needs * to be called once inside the synchronized block in case multiple halts were * performed. */ shutdownExecutor(channelId); /* * Create a new executor to submit the halt task to. Since all the submit methods * are synchronized, it is not possible for any other tasks for this channel to * occur before the halt task. */ ExecutorService engineExecutor = new ThreadPoolExecutor(0, 1, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); engineExecutors.put(channelId, engineExecutor); ChannelTask haltTask = new HaltTask(channelId); haltTask.setHandler(handler); futures.add(haltTask.submitTo(engineExecutor)); } } return futures; } protected void waitForTasks(List<ChannelFuture> futures) { /* * Create a new list to prevent modifying the one that is passed in, in case it will be used * afterwards. */ List<ChannelFuture> remainingFutures = new ArrayList<ChannelFuture>(futures); int attemptsUntilPause = 10; while (CollectionUtils.isNotEmpty(remainingFutures)) { if (attemptsUntilPause > 0) { attemptsUntilPause--; } else { /* * After 10 attempts we will pause longer to lighten the CPU load during long * running tasks. */ try { Thread.sleep(500); } catch (InterruptedException e) { } } Iterator<ChannelFuture> iterator = remainingFutures.iterator(); while (iterator.hasNext()) { boolean finished = false; ChannelFuture future = iterator.next(); try { if (remainingFutures.size() == 1) { // Wait indefinitely when only one future remains. future.get(); } else { // When multiple futures remain, timeout the wait so we can check others in the meantime. future.get(50, TimeUnit.MILLISECONDS); } finished = true; } catch (TimeoutException e) { } finally { if (finished) { iterator.remove(); } } } } } protected class DeployTask extends ChannelTask { private DeployedState initialState; private Set<Integer> connectorsToStart; private ServerEventContext context; public DeployTask(String channelId, DeployedState initialState, Set<Integer> connectorsToStart, ServerEventContext context) { super(channelId); this.initialState = initialState; this.connectorsToStart = connectorsToStart; this.context = context; } @Override public Void execute() throws Exception { doDeploy(channelController.getChannelById(channelId)); return null; } protected void doDeploy(com.mirth.connect.model.Channel channelModel) throws Exception { if (channelModel == null || !channelModel.isEnabled() || isDeployed(channelId)) { return; } Channel channel = null; try { channel = createChannelFromModel(channelModel); } catch (Exception e) { throw new DeployException(e.getMessage(), e); } try { channel.updateCurrentState(DeployedState.DEPLOYING); deployingChannels.add(channel); channelController.putDeployedChannelInCache(channelModel); MirthContextFactory contextFactory; try { contextFactory = contextFactoryController .getContextFactory(channelModel.getProperties().getResourceIds().keySet()); } catch (Exception e) { throw new DeployException("Failed to deploy channel " + channelId + ".", e); } try { scriptController.compileChannelScripts(contextFactory, channelModel); } catch (ScriptCompileException e) { throw new DeployException("Failed to deploy channel " + channelId + ".", e); } clearGlobalChannelMap(channelModel); try { scriptController.executeChannelDeployScript(contextFactory, channelId, channel.getName()); } catch (Exception e) { Throwable t = e; if (e instanceof JavaScriptExecutorException) { t = e.getCause(); } eventController.dispatchEvent(new ErrorEvent(channelModel.getId(), null, null, ErrorEventType.DEPLOY_SCRIPT, null, null, "Error running channel deploy script", t)); throw new DeployException("Failed to deploy channel " + channelId + ".", e); } // Execute the individual channel plugin deploy hook for (ChannelPlugin channelPlugin : extensionController.getChannelPlugins().values()) { channelPlugin.deploy(channelModel, context); } // TODO This may not be necessary anymore channel.setRevision(channelModel.getRevision()); channel.setDeployDate(Calendar.getInstance()); donkey.getDeployedChannels().put(channelId, channel); try { channel.deploy(); } catch (DeployException e) { donkey.getDeployedChannels().remove(channelId); throw e; } // Use the initial state from the channel settings if none are provided if (initialState == null) { initialState = channel.getInitialState(); } // Use all connectors if none are provided if (connectorsToStart == null) { connectorsToStart = new HashSet<Integer>(channel.getMetaDataIds()); } if (initialState == DeployedState.PAUSED) { // If the initial state is paused, never start the source connector connectorsToStart.remove(0); } else if (initialState == DeployedState.STOPPED) { // If the initial state is stopped, never start any connector connectorsToStart.clear(); } // For connectors that won't be started, update their state to stopped to dispatch their event if (!connectorsToStart.contains(0)) { channel.getSourceConnector().updateCurrentState(DeployedState.STOPPED); } for (DestinationChainProvider destinationChainProvider : channel.getDestinationChainProviders()) { for (Entry<Integer, DestinationConnector> entry : destinationChainProvider .getDestinationConnectors().entrySet()) { if (!connectorsToStart.contains(entry.getKey())) { entry.getValue().updateCurrentState(DeployedState.STOPPED); } } } if (initialState == DeployedState.STOPPED) { // If the initial state is stopped, update the channel's state to dispatch its event channel.updateCurrentState(DeployedState.STOPPED); } else { // Unless the initial state is stopped, always start the channel channel.start(connectorsToStart); } } catch (DeployException e) { // Remove the channel from the deployed channel cache if an exception occurred on deploy. channelController.removeDeployedChannelFromCache(channelId); // Remove the channel scripts from the script cache if an exception occurred on deploy. scriptController.removeChannelScriptsFromCache(channelId); throw e; } finally { deployingChannels.remove(channel); } } } protected class UndeployTask extends ChannelTask { private ServerEventContext context; public UndeployTask(String channelId, ServerEventContext context) { super(channelId); this.context = context; } @Override public Void execute() throws Exception { // Get a reference to the deployed channel for later Channel channel = getDeployedChannel(channelId); if (channel != null) { if (channel.isActive()) { channel.stop(); } try { undeployingChannels.add(channel); donkey.getDeployedChannels().remove(channelId); channel.undeploy(); // Remove connector scripts if (channel.getSourceConnector().getFilterTransformerExecutor() .getFilterTransformer() != null) { channel.getSourceConnector().getFilterTransformerExecutor().getFilterTransformer() .dispose(); } for (DestinationChainProvider chainProvider : channel.getDestinationChainProviders()) { for (Integer metaDataId : chainProvider.getDestinationConnectors().keySet()) { if (chainProvider.getDestinationConnectors().get(metaDataId) .getFilterTransformerExecutor().getFilterTransformer() != null) { chainProvider.getDestinationConnectors().get(metaDataId) .getFilterTransformerExecutor().getFilterTransformer().dispose(); } if (chainProvider.getDestinationConnectors().get(metaDataId) .getResponseTransformerExecutor().getResponseTransformer() != null) { chainProvider.getDestinationConnectors().get(metaDataId) .getResponseTransformerExecutor().getResponseTransformer().dispose(); } } } // Execute the individual channel plugin undeploy hook for (ChannelPlugin channelPlugin : extensionController.getChannelPlugins().values()) { channelPlugin.undeploy(channelId, context); } // Execute channel undeploy script try { MirthContextFactory contextFactory = contextFactoryController .getContextFactory(channel.getResourceIds()); if (!channel.getContextFactoryId().equals(contextFactory.getId())) { JavaScriptUtil.recompileChannelScript(contextFactory, channelId, ScriptController.UNDEPLOY_SCRIPT_KEY); channel.setContextFactoryId(contextFactory.getId()); } scriptController.executeChannelUndeployScript(contextFactory, channelId, channel.getName()); } catch (Exception e) { Throwable t = e; if (e instanceof JavaScriptExecutorException) { t = e.getCause(); } eventController .dispatchEvent(new ErrorEvent(channelId, null, null, ErrorEventType.UNDEPLOY_SCRIPT, null, null, "Error running channel undeploy script", t)); logger.error("Error executing undeploy script for channel " + channelId + ".", e); } // Remove channel scripts scriptController.removeChannelScriptsFromCache(channelId); channelController.removeDeployedChannelFromCache(channelId); } finally { undeployingChannels.remove(channel); } } return null; } } protected class ChannelStatusTask extends ChannelTask { private StatusTask task; public ChannelStatusTask(String channelId, StatusTask task) { super(channelId); this.task = task; } @Override public Void execute() throws Exception { Channel channel = getDeployedChannel(channelId); if (channel != null) { if (task == StatusTask.START) { channel.start(null); } else if (task == StatusTask.STOP) { channel.stop(); } else if (task == StatusTask.PAUSE) { channel.pause(); } else if (task == StatusTask.RESUME) { channel.resume(); } } return null; } } protected class ConnectorStatusTask extends ChannelTask { private StatusTask task; public ConnectorStatusTask(String channelId, Integer metaDataId, StatusTask task) { super(channelId, metaDataId); this.task = task; } @Override public Void execute() throws Exception { Channel channel = getDeployedChannel(channelId); if (channel != null) { if (task == StatusTask.START) { channel.startConnector(metaDataId); } else if (task == StatusTask.STOP) { channel.stopConnector(metaDataId); } } return null; } } protected class HaltTask extends ChannelTask { public HaltTask(String channelId) { super(channelId); } @Override public Void execute() throws Exception { Channel channel = getDeployedChannel(channelId); if (channel != null) { channel.halt(); } return null; } } protected class RemoveTask extends ChannelTask { private com.mirth.connect.model.Channel channelModel; private ServerEventContext context; public RemoveTask(com.mirth.connect.model.Channel channelModel, ServerEventContext context) { super(channelModel.getId()); this.channelModel = channelModel; this.context = context; } @Override public Void execute() throws Exception { channelController.removeChannel(channelModel, context); removeExecutor(channelId); return null; } } protected class RemoveMessagesTask extends ChannelTask { private Map<Long, MessageSearchResult> results; public RemoveMessagesTask(String channelId, Map<Long, MessageSearchResult> results) { super(channelId); this.results = results; } @Override public Void execute() throws Exception { Map<Long, Set<Integer>> messages = new HashMap<Long, Set<Integer>>(); // For each message that was retrieved for (Entry<Long, MessageSearchResult> entry : results.entrySet()) { Long messageId = entry.getKey(); MessageSearchResult result = entry.getValue(); Set<Integer> metaDataIds = result.getMetaDataIdSet(); boolean processed = result.isProcessed(); Channel channel = getDeployedChannel(channelId); // Allow unprocessed messages to be deleted only if the channel is undeployed or stopped. if (channel == null || channel.getCurrentState() == DeployedState.STOPPED || processed) { if (metaDataIds.contains(0)) { // Delete the entire message if the source connector message is to be deleted messages.put(messageId, null); } else { // Otherwise only deleted the destination connector message messages.put(messageId, metaDataIds); } } } Channel.DELETE_PERMIT.acquire(); try { com.mirth.connect.donkey.server.controllers.MessageController.getInstance() .deleteMessages(channelId, messages); } finally { Channel.DELETE_PERMIT.release(); } return null; } } protected class RemoveAllMessagesTask extends ChannelTask { private boolean force; private boolean clearStatistics; public RemoveAllMessagesTask(String channelId, boolean force, boolean clearStatistics) { super(channelId); this.force = force; this.clearStatistics = clearStatistics; } @Override public Void execute() throws Exception { Channel channel = getDeployedChannel(channelId); if (channel != null) { channel.removeAllMessages(force, clearStatistics); } else { com.mirth.connect.model.Channel channelModel = channelController.getChannelById(channelId); if (channelModel != null) { DonkeyDao dao = null; try { dao = donkey.getDaoFactory().getDao(); dao.deleteAllMessages(channelId); if (clearStatistics) { Set<Status> statuses = Statistics.getTrackedStatuses(); dao.resetStatistics(channelId, null, statuses); for (com.mirth.connect.model.Connector connector : channelModel .getDestinationConnectors()) { dao.resetStatistics(channelId, connector.getMetaDataId(), statuses); } } dao.commit(); } finally { if (dao != null) { dao.close(); } } } } return null; } } }