Skip to content

Commit aac9558

Browse files
ggiannolamgaffigan
authored andcommitted
Fix #120: start and stop each server plugin only once
initPlugins registers a plugin instance once for every plugin type interface it implements, adding it to the serverPlugins list up to eight times. startPlugins and stopPlugins iterate that list, so a plugin implementing more than one interface (for example both ServicePlugin and ChannelPlugin) had start() and stop() invoked once per interface instead of once per plugin. Route the registrations through a helper that ignores an instance which is already registered. The comparison is by identity so that two distinct instances are still both registered even if the plugin class reports them as equal. Adds tests covering duplicate registration, stop() being called once for a plugin registered for multiple types, and distinct-but-equal instances still being registered separately. Signed-off-by: Giovanni Giannola <giogiannola@globalesm.com>
1 parent 776690e commit aac9558

2 files changed

Lines changed: 132 additions & 8 deletions

File tree

‎server/src/main/java/com/mirth/connect/server/controllers/DefaultExtensionController.java‎

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -240,42 +240,42 @@ public void initPlugins() {
240240
*/
241241
servicePlugin.init(currentProperties);
242242
servicePlugins.put(servicePlugin.getPluginPointName(), servicePlugin);
243-
serverPlugins.add(servicePlugin);
243+
addServerPlugin(servicePlugin);
244244
logger.debug("sucessfully loaded server plugin: " + serverPlugin.getPluginPointName());
245245
}
246246

247247
if (serverPlugin instanceof ChannelPlugin) {
248248
ChannelPlugin channelPlugin = (ChannelPlugin) serverPlugin;
249249
channelPlugins.put(channelPlugin.getPluginPointName(), channelPlugin);
250-
serverPlugins.add(channelPlugin);
250+
addServerPlugin(channelPlugin);
251251
logger.debug("sucessfully loaded server channel plugin: " + serverPlugin.getPluginPointName());
252252
}
253253

254254
if (serverPlugin instanceof CodeTemplateServerPlugin) {
255255
CodeTemplateServerPlugin codeTemplateServerPlugin = (CodeTemplateServerPlugin) serverPlugin;
256256
codeTemplateServerPlugins.put(codeTemplateServerPlugin.getPluginPointName(), codeTemplateServerPlugin);
257-
serverPlugins.add(codeTemplateServerPlugin);
257+
addServerPlugin(codeTemplateServerPlugin);
258258
logger.debug("sucessfully loaded server code template plugin: " + serverPlugin.getPluginPointName());
259259
}
260260

261261
if (serverPlugin instanceof DataTypeServerPlugin) {
262262
DataTypeServerPlugin dataTypePlugin = (DataTypeServerPlugin) serverPlugin;
263263
dataTypePlugins.put(dataTypePlugin.getPluginPointName(), dataTypePlugin);
264-
serverPlugins.add(dataTypePlugin);
264+
addServerPlugin(dataTypePlugin);
265265
logger.debug("sucessfully loaded server data type plugin: " + serverPlugin.getPluginPointName());
266266
}
267267

268268
if (serverPlugin instanceof ResourcePlugin) {
269269
ResourcePlugin resourcePlugin = (ResourcePlugin) serverPlugin;
270270
resourcePlugins.put(resourcePlugin.getPluginPointName(), resourcePlugin);
271-
serverPlugins.add(resourcePlugin);
271+
addServerPlugin(resourcePlugin);
272272
logger.debug("Successfully loaded resource plugin: " + resourcePlugin.getPluginPointName());
273273
}
274274

275275
if (serverPlugin instanceof TransmissionModeProvider) {
276276
TransmissionModeProvider transmissionModeProvider = (TransmissionModeProvider) serverPlugin;
277277
transmissionModeProviders.put(transmissionModeProvider.getPluginPointName(), transmissionModeProvider);
278-
serverPlugins.add(transmissionModeProvider);
278+
addServerPlugin(transmissionModeProvider);
279279
logger.debug("Successfully loaded transmission mode provider plugin: " + transmissionModeProvider.getPluginPointName());
280280
}
281281

@@ -287,7 +287,7 @@ public void initPlugins() {
287287
}
288288

289289
this.authorizationPlugin = authorizationPlugin;
290-
serverPlugins.add(authorizationPlugin);
290+
addServerPlugin(authorizationPlugin);
291291
logger.debug("sucessfully loaded server authorization plugin: " + serverPlugin.getPluginPointName());
292292
}
293293

@@ -299,7 +299,7 @@ public void initPlugins() {
299299
}
300300

301301
this.multiFactorAuthenticationPlugin = multiFactorAuthenticationPlugin;
302-
serverPlugins.add(multiFactorAuthenticationPlugin);
302+
addServerPlugin(multiFactorAuthenticationPlugin);
303303
logger.debug("sucessfully loaded server multi-factor authentication plugin: " + serverPlugin.getPluginPointName());
304304
}
305305
} catch (Exception e) {
@@ -309,6 +309,32 @@ public void initPlugins() {
309309
}
310310
}
311311

312+
/**
313+
* Registers a plugin in the list used to start and stop all server plugins.
314+
* <p>
315+
* A single plugin class may implement more than one of the plugin type interfaces (for example
316+
* both {@link ServicePlugin} and {@link ChannelPlugin}). Such a plugin is registered against
317+
* each type it implements, so this method guards against adding the same instance more than
318+
* once. Without the guard, {@link #startPlugins()} and {@link #stopPlugins()} would invoke
319+
* {@link ServerPlugin#start()} and {@link ServerPlugin#stop()} once per implemented interface
320+
* rather than once per plugin.
321+
* <p>
322+
* Instances are compared by identity on purpose. Two distinct plugin instances must both be
323+
* registered even if the plugin class considers them equal.
324+
* <p>
325+
* Package private so it can be exercised directly by unit tests without going through the full
326+
* extension loading process.
327+
*/
328+
void addServerPlugin(ServerPlugin serverPlugin) {
329+
for (ServerPlugin registeredPlugin : serverPlugins) {
330+
if (registeredPlugin == serverPlugin) {
331+
return;
332+
}
333+
}
334+
335+
serverPlugins.add(serverPlugin);
336+
}
337+
312338
/* These are the maps for the different types of plugins */
313339
/* ********************************************************************** */
314340

‎server/src/test/java/com/mirth/connect/server/controllers/DefaultExtensionControllerTest.java‎

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@
99

1010
package com.mirth.connect.server.controllers;
1111

12+
import static org.junit.Assert.assertEquals;
13+
import static org.junit.Assert.assertSame;
1214
import static org.junit.Assert.assertTrue;
1315

1416
import java.io.File;
17+
import java.util.List;
1518
import java.util.zip.ZipEntry;
1619
import java.util.zip.ZipException;
1720
import java.util.zip.ZipFile;
@@ -20,6 +23,7 @@
2023
import org.junit.Before;
2124
import org.junit.Test;
2225

26+
import com.mirth.connect.plugins.ServerPlugin;
2327
import com.mirth.connect.util.ZipTestUtils;
2428

2529
public class DefaultExtensionControllerTest {
@@ -72,4 +76,98 @@ public void cleanupTestFolder() {
7276
private ZipFile createTempZipFile(String fileName) throws Exception {
7377
return new ZipFile(ZipTestUtils.createTempZipFile(fileName));
7478
}
79+
80+
/*
81+
* A plugin class may implement several of the plugin type interfaces (ServicePlugin,
82+
* ChannelPlugin, and so on). initPlugins registers the instance once per interface it
83+
* implements, so registration must ignore an instance that is already registered. Otherwise
84+
* start() and stop() get invoked once per implemented interface instead of once per plugin.
85+
*/
86+
@Test
87+
public void testAddServerPluginIgnoresDuplicateRegistrationOfSameInstance() {
88+
DefaultExtensionController extensionController = new DefaultExtensionController();
89+
CountingServerPlugin plugin = new CountingServerPlugin("multi-type plugin");
90+
91+
extensionController.addServerPlugin(plugin);
92+
extensionController.addServerPlugin(plugin);
93+
extensionController.addServerPlugin(plugin);
94+
95+
List<ServerPlugin> registeredPlugins = extensionController.getServerPlugins();
96+
assertEquals(1, registeredPlugins.size());
97+
assertSame(plugin, registeredPlugins.get(0));
98+
}
99+
100+
@Test
101+
public void testStopPluginsStopsPluginRegisteredForMultipleTypesOnce() {
102+
DefaultExtensionController extensionController = new DefaultExtensionController();
103+
CountingServerPlugin plugin = new CountingServerPlugin("multi-type plugin");
104+
105+
extensionController.addServerPlugin(plugin);
106+
extensionController.addServerPlugin(plugin);
107+
108+
extensionController.stopPlugins();
109+
110+
assertEquals(1, plugin.stopCount);
111+
}
112+
113+
/*
114+
* Registration deliberately compares instances by identity, so two separate plugin instances
115+
* are both registered and both stopped even when the plugin class reports them as equal.
116+
*/
117+
@Test
118+
public void testAddServerPluginRegistersDistinctButEqualInstancesSeparately() {
119+
DefaultExtensionController extensionController = new DefaultExtensionController();
120+
AlwaysEqualServerPlugin firstPlugin = new AlwaysEqualServerPlugin();
121+
AlwaysEqualServerPlugin secondPlugin = new AlwaysEqualServerPlugin();
122+
123+
extensionController.addServerPlugin(firstPlugin);
124+
extensionController.addServerPlugin(secondPlugin);
125+
126+
assertEquals(2, extensionController.getServerPlugins().size());
127+
128+
extensionController.stopPlugins();
129+
130+
assertEquals(1, firstPlugin.stopCount);
131+
assertEquals(1, secondPlugin.stopCount);
132+
}
133+
134+
private static class CountingServerPlugin implements ServerPlugin {
135+
private final String pluginPointName;
136+
int stopCount;
137+
138+
private CountingServerPlugin(String pluginPointName) {
139+
this.pluginPointName = pluginPointName;
140+
}
141+
142+
@Override
143+
public String getPluginPointName() {
144+
return pluginPointName;
145+
}
146+
147+
@Override
148+
public void start() {
149+
// Not exercised here: startPlugins() also reaches into ControllerFactory.
150+
}
151+
152+
@Override
153+
public void stop() {
154+
stopCount++;
155+
}
156+
}
157+
158+
private static class AlwaysEqualServerPlugin extends CountingServerPlugin {
159+
private AlwaysEqualServerPlugin() {
160+
super("always equal plugin");
161+
}
162+
163+
@Override
164+
public boolean equals(Object other) {
165+
return other instanceof AlwaysEqualServerPlugin;
166+
}
167+
168+
@Override
169+
public int hashCode() {
170+
return AlwaysEqualServerPlugin.class.hashCode();
171+
}
172+
}
75173
}

0 commit comments

Comments
 (0)