1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package com.linecorp.centraldogma.server;
17
18 import static com.google.common.collect.ImmutableList.toImmutableList;
19 import static java.util.Objects.requireNonNull;
20
21 import java.util.List;
22 import java.util.Optional;
23 import java.util.ServiceLoader;
24 import java.util.concurrent.CompletableFuture;
25 import java.util.concurrent.CompletionStage;
26 import java.util.concurrent.Executor;
27 import java.util.concurrent.Executors;
28 import java.util.concurrent.ScheduledExecutorService;
29
30 import javax.annotation.Nullable;
31
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import com.google.common.collect.ImmutableList;
36 import com.google.common.collect.ImmutableList.Builder;
37 import com.spotify.futures.CompletableFutures;
38
39 import com.linecorp.armeria.common.util.StartStopSupport;
40 import com.linecorp.centraldogma.server.command.CommandExecutor;
41 import com.linecorp.centraldogma.server.plugin.Plugin;
42 import com.linecorp.centraldogma.server.plugin.PluginContext;
43 import com.linecorp.centraldogma.server.plugin.PluginTarget;
44 import com.linecorp.centraldogma.server.storage.project.ProjectManager;
45
46 import io.micrometer.core.instrument.MeterRegistry;
47 import io.netty.util.concurrent.DefaultThreadFactory;
48
49
50
51
52 final class PluginGroup {
53
54 private static final Logger logger = LoggerFactory.getLogger(PluginGroup.class);
55
56
57
58
59
60
61
62
63 @Nullable
64 static PluginGroup loadPlugins(PluginTarget target, CentralDogmaConfig config) {
65 return loadPlugins(PluginGroup.class.getClassLoader(), target, config);
66 }
67
68
69
70
71
72
73
74
75
76 @Nullable
77 static PluginGroup loadPlugins(ClassLoader classLoader, PluginTarget target, CentralDogmaConfig config) {
78 requireNonNull(classLoader, "classLoader");
79 requireNonNull(target, "target");
80 requireNonNull(config, "config");
81
82 final ServiceLoader<Plugin> loader = ServiceLoader.load(Plugin.class, classLoader);
83 final Builder<Plugin> plugins = new Builder<>();
84 for (Plugin plugin : loader) {
85 if (target == plugin.target() && plugin.isEnabled(config)) {
86 plugins.add(plugin);
87 }
88 }
89
90 final List<Plugin> list = plugins.build();
91 if (list.isEmpty()) {
92 return null;
93 }
94
95 return new PluginGroup(list, Executors.newSingleThreadExecutor(new DefaultThreadFactory(
96 "plugins-for-" + target.name().toLowerCase().replace("_", "-"), true)));
97 }
98
99 private final List<Plugin> plugins;
100 private final PluginGroupStartStop startStop;
101
102 private PluginGroup(Iterable<Plugin> plugins, Executor executor) {
103 this.plugins = ImmutableList.copyOf(requireNonNull(plugins, "plugins"));
104 startStop = new PluginGroupStartStop(requireNonNull(executor, "executor"));
105 }
106
107
108
109
110 List<Plugin> plugins() {
111 return plugins;
112 }
113
114
115
116
117 <T extends Plugin> Optional<T> findFirstPlugin(Class<T> clazz) {
118 requireNonNull(clazz, "clazz");
119 return plugins.stream().filter(clazz::isInstance).map(clazz::cast).findFirst();
120 }
121
122
123
124
125 CompletableFuture<Void> start(CentralDogmaConfig config, ProjectManager projectManager,
126 CommandExecutor commandExecutor, MeterRegistry meterRegistry,
127 ScheduledExecutorService purgeWorker) {
128 final PluginContext context = new PluginContext(config, projectManager, commandExecutor, meterRegistry,
129 purgeWorker);
130 return startStop.start(context, context, true);
131 }
132
133
134
135
136 CompletableFuture<Void> stop(CentralDogmaConfig config, ProjectManager projectManager,
137 CommandExecutor commandExecutor, MeterRegistry meterRegistry,
138 ScheduledExecutorService purgeWorker) {
139 return startStop.stop(
140 new PluginContext(config, projectManager, commandExecutor, meterRegistry, purgeWorker));
141 }
142
143 private class PluginGroupStartStop extends StartStopSupport<PluginContext, PluginContext, Void, Void> {
144
145 PluginGroupStartStop(Executor executor) {
146 super(executor);
147 }
148
149 @Override
150 protected CompletionStage<Void> doStart(@Nullable PluginContext arg) throws Exception {
151 assert arg != null;
152 final List<CompletionStage<Void>> futures = plugins.stream().map(
153 plugin -> plugin.start(arg)
154 .thenAccept(unused -> logger.info("Plugin started: {}", plugin))
155 .exceptionally(cause -> {
156 logger.info("Failed to start plugin: {}", plugin, cause);
157 return null;
158 })).collect(toImmutableList());
159 return CompletableFutures.allAsList(futures).thenApply(unused -> null);
160 }
161
162 @Override
163 protected CompletionStage<Void> doStop(@Nullable PluginContext arg) throws Exception {
164 assert arg != null;
165 final List<CompletionStage<Void>> futures = plugins.stream().map(
166 plugin -> plugin.stop(arg)
167 .thenAccept(unused -> logger.info("Plugin stopped: {}", plugin))
168 .exceptionally(cause -> {
169 logger.info("Failed to stop plugin: {}", plugin, cause);
170 return null;
171 })).collect(toImmutableList());
172 return CompletableFutures.allAsList(futures).thenApply(unused -> null);
173 }
174 }
175 }