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.base.MoreObjects.firstNonNull;
19
20 import java.io.File;
21 import java.io.IOException;
22 import java.nio.file.AtomicMoveNotSupportedException;
23 import java.nio.file.Files;
24 import java.nio.file.Path;
25 import java.nio.file.StandardCopyOption;
26
27 import javax.annotation.Nullable;
28
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 import com.beust.jcommander.JCommander;
33 import com.beust.jcommander.Parameter;
34 import com.beust.jcommander.converters.FileConverter;
35
36 import com.linecorp.armeria.common.util.SystemInfo;
37
38
39
40
41 public final class Main {
42
43 private static final Logger logger = LoggerFactory.getLogger(Main.class);
44
45 static {
46 try {
47
48 final Class<?> bridgeHandler =
49 Class.forName("org.slf4j.bridge.SLF4JBridgeHandler", true, Main.class.getClassLoader());
50 bridgeHandler.getMethod("removeHandlersForRootLogger").invoke(null);
51 bridgeHandler.getMethod("install").invoke(null);
52 logger.debug("Installed the java.util.logging-to-SLF4J bridge.");
53 } catch (Throwable cause) {
54 logger.debug("Failed to install the java.util.logging-to-SLF4J bridge:", cause);
55 }
56 }
57
58 enum State {
59 NONE,
60 INITIALIZED,
61 STARTED,
62 STOPPED,
63 DESTROYED
64 }
65
66 private static final File DEFAULT_DATA_DIR =
67 new File(System.getProperty("user.dir", ".") + File.separatorChar + "data");
68
69 private static final File DEFAULT_CONFIG_FILE =
70 new File(System.getProperty("user.dir", ".") +
71 File.separatorChar + "conf" +
72 File.separatorChar + "dogma.json");
73
74 private static final File DEFAULT_PID_FILE =
75 new File(System.getProperty("user.dir", ".") +
76 File.separatorChar + "dogma.pid");
77
78 @Nullable
79 @Parameter(names = "-config", description = "The path to the config file", converter = FileConverter.class)
80 private File configFile;
81
82 @Nullable
83 @Parameter(names = "-pidfile",
84 description = "The path to the file containing the pid of the server" +
85 " (defaults to ./dogma.pid)",
86 converter = FileConverter.class)
87 private File pidFile;
88
89
90
91
92
93 @Nullable
94 @Parameter(names = { "-help", "-h" }, description = "Prints the usage", help = true)
95 private Boolean help;
96
97 private State state = State.NONE;
98 @Nullable
99 private CentralDogma dogma;
100 @Nullable
101 private PidFile procIdFile;
102 private boolean procIdFileCreated;
103
104 private Main(String[] args) {
105 final JCommander commander = new JCommander(this);
106 commander.setProgramName(getClass().getName());
107 commander.parse(args);
108
109 if (help != null && help) {
110 commander.usage();
111 } else {
112 procIdFile = new PidFile(firstNonNull(pidFile, DEFAULT_PID_FILE));
113 state = State.INITIALIZED;
114 }
115 }
116
117 synchronized void start() throws Exception {
118 switch (state) {
119 case NONE:
120 throw new IllegalStateException("not initialized");
121 case STARTED:
122 throw new IllegalStateException("started already");
123 case DESTROYED:
124 throw new IllegalStateException("can't start after destruction");
125 default:
126 break;
127 }
128
129 final File configFile = findConfigFile(this.configFile, DEFAULT_CONFIG_FILE);
130
131 final CentralDogma dogma;
132 if (configFile == null) {
133 dogma = new CentralDogmaBuilder(DEFAULT_DATA_DIR).build();
134 } else {
135 dogma = CentralDogma.forConfig(configFile);
136 }
137
138 dogma.start().get();
139
140 this.dogma = dogma;
141 state = State.STARTED;
142
143
144
145 assert procIdFile != null;
146 procIdFile.create();
147 procIdFileCreated = true;
148 }
149
150 @Nullable
151 private static File findConfigFile(@Nullable File file, File defaultFile) {
152 if (file != null) {
153 if (file.isFile() && file.canRead()) {
154 return file;
155 } else {
156 throw new IllegalStateException("cannot access the specified config file: " + file);
157 }
158 }
159
160
161 if (defaultFile.isFile() && defaultFile.canRead()) {
162 return defaultFile;
163 }
164 return null;
165 }
166
167 synchronized void stop() throws Exception {
168 switch (state) {
169 case NONE:
170 case INITIALIZED:
171 case STOPPED:
172 return;
173 case DESTROYED:
174 throw new IllegalStateException("can't stop after destruction");
175 }
176
177 final CentralDogma dogma = this.dogma;
178 assert dogma != null;
179 this.dogma = null;
180 dogma.stop().get();
181
182 state = State.STOPPED;
183 }
184
185 void destroy() {
186 switch (state) {
187 case NONE:
188 return;
189 case STARTED:
190 throw new IllegalStateException("can't destroy while running");
191 case DESTROYED:
192 return;
193 }
194
195 assert procIdFile != null;
196 if (procIdFileCreated) {
197 try {
198 procIdFile.destroy();
199 } catch (IOException e) {
200 logger.warn("Failed to destroy the PID file:", e);
201 }
202 }
203
204 state = State.DESTROYED;
205 }
206
207
208
209
210 public static void main(String[] args) throws Exception {
211 final Main main = new Main(args);
212
213
214 Runtime.getRuntime().addShutdownHook(new Thread("Central Dogma shutdown hook") {
215 @Override
216 public void run() {
217 try {
218 main.stop();
219 } catch (Exception e) {
220 logger.warn("Failed to stop the Central Dogma:", e);
221 }
222
223 try {
224 main.destroy();
225 } catch (Exception e) {
226 logger.warn("Failed to destroy the Central Dogma:", e);
227 }
228 }
229 });
230
231
232 if (main.state != State.INITIALIZED) {
233 System.exit(1);
234 return;
235 }
236
237 try {
238 main.start();
239 } catch (Throwable cause) {
240 logger.error("Failed to start the Central Dogma:", cause);
241
242 System.exit(1);
243 }
244 }
245
246
247
248
249 static final class PidFile {
250
251 private final File file;
252
253 private PidFile(File file) {
254 this.file = file;
255 }
256
257 void create() throws IOException {
258 if (file.exists()) {
259 throw new IllegalStateException("Failed to create a PID file. A file already exists: " +
260 file.getPath());
261 }
262
263 final int pid = SystemInfo.pid();
264 final Path temp = Files.createTempFile("central-dogma", ".tmp");
265 Files.write(temp, Integer.toString(pid).getBytes());
266 try {
267 Files.move(temp, file.toPath(), StandardCopyOption.ATOMIC_MOVE);
268 } catch (AtomicMoveNotSupportedException e) {
269 Files.move(temp, file.toPath());
270 }
271
272 logger.debug("A PID file has been created: {}", file);
273 }
274
275 void destroy() throws IOException {
276 if (Files.deleteIfExists(file.toPath())) {
277 logger.debug("Successfully deleted the PID file: {}", file);
278 }
279 }
280 }
281 }