This repository has been archived by the owner on Mar 18, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 75
/
DefaultBootstrap.java
303 lines (255 loc) · 10.5 KB
/
DefaultBootstrap.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
package org.update4j.service;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Writer;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.update4j.Bootstrap;
import org.update4j.Configuration;
import org.update4j.SingleInstanceManager;
import org.update4j.Update;
public class DefaultBootstrap implements Delegate {
private String remote;
private String local;
private String cert;
private boolean syncLocal;
private boolean launchFirst;
private boolean stopOnUpdateError;
private boolean singleInstance;
private static final String PATTERN = "(?:\\s*=)?\\s*(.+)";
@Override
public long version() {
return Long.MIN_VALUE;
}
@Override
public void main(List<String> args) throws Throwable {
if (args.isEmpty()) {
welcome();
return;
}
for (String arg : args) {
arg = arg.trim();
// let's try first those who don't need regex for performance
if (arg.equals("--syncLocal")) {
validateNotSet(syncLocal, "syncLocal");
syncLocal = true;
continue;
} else if (arg.equals("--launchFirst")) {
validateNotSet(launchFirst, "launchFirst");
launchFirst = true;
continue;
} else if (arg.equals("--stopOnUpdateError")) {
validateNotSet(stopOnUpdateError, "stopOnUpdateError");
stopOnUpdateError = true;
continue;
} else if (arg.equals("--singleInstance")) {
validateNotSet(singleInstance, "singleInstance");
singleInstance = true;
continue;
}
Matcher m = Pattern.compile("--remote" + PATTERN).matcher(arg);
if (m.matches()) {
validateNotSet(remote, "remote");
remote = m.group(1);
continue;
}
m = Pattern.compile("--local" + PATTERN).matcher(arg);
if (m.matches()) {
validateNotSet(local, "local");
local = m.group(1);
continue;
}
m = Pattern.compile("--cert" + PATTERN).matcher(arg);
if (m.matches()) {
validateNotSet(cert, "cert");
cert = m.group(1);
continue;
}
}
if (remote == null && local == null) {
throw new IllegalArgumentException("One of --remote or --local must be supplied.");
}
if (launchFirst && local == null) {
throw new IllegalArgumentException("--launchFirst requires a local configuration.");
}
if (syncLocal && remote == null) {
throw new IllegalArgumentException("--syncLocal requires a remote configuration.");
}
if (syncLocal && local == null) {
throw new IllegalArgumentException("--syncLocal requires a local configuration.");
}
if (singleInstance) {
SingleInstanceManager.execute();
}
if (launchFirst) {
launchFirst(args);
} else {
updateFirst(args);
}
}
private void validateNotSet(boolean val, String command) {
if (val)
throw new IllegalArgumentException("Duplicate --" + command + " command.");
}
private void validateNotSet(String val, String command) {
if (val != null)
throw new IllegalArgumentException("Duplicate --" + command + " command.");
}
private void updateFirst(List<String> args) throws Throwable {
Configuration config = null;
if (remote != null) {
try (Reader in = new InputStreamReader(new URL(remote).openStream())) {
config = Configuration.read(in);
} catch (Exception e) {
e.printStackTrace();
}
}
if (config == null && local != null) {
try (Reader in = Files.newBufferedReader(Paths.get(local))) {
config = Configuration.read(in);
} catch (Exception e) {
e.printStackTrace();
}
}
if (config == null) {
return;
}
if (syncLocal) {
try (Writer out = Files.newBufferedWriter(Paths.get(local))) {
config.write(out);
}
}
if (config.requiresUpdate()) {
PublicKey pk = null;
if (cert != null) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
try (InputStream in = Files.newInputStream(Paths.get(cert))) {
pk = cf.generateCertificate(in).getPublicKey();
}
}
boolean success = config.update(pk);
if (!success && stopOnUpdateError) {
return;
}
}
config.launch(args);
}
private void launchFirst(List<String> args) throws Throwable {
Path tempDir = Paths.get("update");
if (Update.containsUpdate(tempDir)) {
Update.finalizeUpdate(tempDir);
}
Configuration localConfig = null;
try (Reader in = Files.newBufferedReader(Paths.get(local))) {
localConfig = Configuration.read(in);
} catch (Exception e) {
e.printStackTrace();
}
if (localConfig != null) {
Configuration finalConfig = localConfig;
Thread localApp = new Thread(() -> finalConfig.launch(args));
localApp.run();
}
Configuration remoteConfig = null;
try (Reader in = new InputStreamReader(new URL(remote).openStream())) {
remoteConfig = Configuration.read(in);
} catch (Exception e) {
e.printStackTrace();
}
if(remoteConfig != null) {
if(remoteConfig.requiresUpdate()) {
PublicKey pk = null;
if (cert != null) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
try (InputStream in = Files.newInputStream(Paths.get(cert))) {
pk = cf.generateCertificate(in).getPublicKey();
}
}
remoteConfig.updateTemp(tempDir, pk);
}
}
}
// @formatter:off
private static void welcome() {
System.out.println(getLogo() + "\tWelcome to the update4j framework.\n\n"
+ "\tYou started the framework with its default settings, which does\n"
+ "\tthe update and launch logic for you without complex setup. All you need is to\n"
+ "\tspecify some settings via command line arguments.\n\n"
+ "\tBefore you start, you first need to create a \"configuration\" file that contains\n"
+ "\tall details required to run. You can create one by using Configuration.builder()\n"
+ "\tBuilder API. You can sync an existing configuration when files are changed\n"
+ "\tusing one of the Configuration.sync() methods.\n\n"
+ "\tFor more details how to create a configuration please refer to the Javadoc:\n"
+ "\thttp://docs.update4j.org/javadoc/update4j/org/update4j/Configuration.html\n\n"
+ "\tWhile the default bootstrap works perfectly for a majority of cases, you might\n"
+ "\tfurther customize the update and launch life-cycle to the last detail by\n"
+ "\tusing the Configuration.update() and Configuration.launch() methods.\n\n"
+ "\tIf you choose to implement your own bootstrap, there are 2 ways to do it:\n\n"
+ "\t\t- Standard Mode: Start the bootstrap application using your own main method.\n"
+ "\t\t You will not be able to update the bootstrap application (as code cannot update itself),\n"
+ "\t\t only the business application will be updatable.\n\n"
+ "\t\t- Delegate Mode: Move your main method into an implementation of Delegate\n"
+ "\t\t and start the framework just as you did now, i.e. calling \"update4j's\" main method.\n"
+ "\t\t This allows you to update the bootstrap application by releasing a newer version\n"
+ "\t\t with a higher version() number and make it visible to the JVM boot classpath\n"
+ "\t\t or modulepath by placing it in the right directory.\n"
+ "\t\t It it recommended not to use this feature before you can get everything\n"
+ "\t\t to run smoothly in Standard Mode, as this adds an extra layer of complexity.\n\n"
+ "\tFor more details about implementing the bootstrap, please refer to the Github wiki:\n"
+ "\thttps://github.com/update4j/update4j/wiki/Documentation#lifecycle\n"
+ "\tFor more details how to register service providers please refer to the Github wiki:\n"
+ "\thttps://github.com/update4j/update4j/wiki/Documentation#dealing-with-providers\n\n");
usage();
}
private static String getLogo() {
return
"\n"
+ "\t _ _ ___ _ \n"
+ "\t | | | | / (_)\n"
+ "\t _ _ _ __ __| | __ _| |_ ___ / /| |_ \n"
+ "\t| | | | '_ \\ / _` |/ _` | __/ _ \\/ /_| | |\n"
+ "\t| |_| | |_) | (_| | (_| | || __/\\___ | |\n"
+"\t \\__,_| .__/ \\__,_|\\__,_|\\__\\___| |_/ |\n"
+ "\t | | _/ |\n"
+ "\t |_| |__/ \n\n\n"
;
}
private static void usage() {
System.err.println("To start in modulepath:\n\n"
+ "\tjava -p update4j-" + Bootstrap.VERSION + ".jar -m org.update4j [commands...]\n"
+ "\tjava -p . -m org.update4j [commands...]\n\n"
+ "To start in classpath:\n\n"
+ "\tjava -jar update4j-" + Bootstrap.VERSION + ".jar [commands...]\n"
+ "\tjava -cp update4j-" + Bootstrap.VERSION + ".jar org.update4j.Bootstrap [commands...]\n"
+ "\tjava -cp * org.update4j.Bootstrap [commands...]\n\n"
+ "Available commands:\n\n"
+ "\t--remote=[url] - The remote (or if using file:/// scheme - local) location of the\n"
+ "\t\tconfiguration file. If it fails to download or command is missing, it will\n"
+ "\t\tfall back to local.\n\n"
+ "\t--local=[path] - The path of a local configuration to use if the remote failed to download\n"
+ "\t\tor was not passed. If both remote and local fail, startup fails.\n\n"
+ "\t--syncLocal - Sync the local configuration with the remote if it downloaded successfully.\n"
+ "\t\tUseful to still allow launching without Internet connection. Default will not sync unless\n"
+ "\t\t--launchFirst was specified.\n"
+ "\t--cert=[path] - A path to an X.509 certificate file to use to verify signatures. If missing,\n"
+ "\t\tno signature verification will be performed.\n\n"
+ "\t--launchFirst - If specified, it will first launch the local application then silently\n"
+ "\t\tdownload the update. The update will be available only on next restart. If not specified\n"
+ "\t\tit will update before launch and hang the application until done. Must have a local\n"
+ "\t\tconfiguration\n\n"
+ "\t--stopOnUpdateError - If --launchFirst was not specified this will stop the launch\n"
+ "\t\tif an error occurred while downloading an update. This does not include if remote failed\n"
+ "\t\tto download and it used local as a fallback. Ignored if --launchFirst was used.\n\n"
+ "\t--singleInstance - Run the application as a single instance. Any subsequent attempts\n"
+ "\t\tto run will just exit. You can better control this feature by directly using the\n"
+ "\t\tSingleInstanceManager class.");
}
}