1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.newsclub.net.unix.rmi;
19
20 import java.io.Closeable;
21 import java.io.IOException;
22 import java.net.MalformedURLException;
23 import java.rmi.AccessException;
24 import java.rmi.AlreadyBoundException;
25 import java.rmi.ConnectIOException;
26 import java.rmi.Naming;
27 import java.rmi.NoSuchObjectException;
28 import java.rmi.NotBoundException;
29 import java.rmi.Remote;
30 import java.rmi.RemoteException;
31 import java.rmi.ServerException;
32 import java.rmi.registry.LocateRegistry;
33 import java.rmi.registry.Registry;
34 import java.rmi.server.RMISocketFactory;
35 import java.rmi.server.UnicastRemoteObject;
36 import java.util.HashMap;
37 import java.util.Map;
38 import java.util.Objects;
39 import java.util.concurrent.TimeUnit;
40 import java.util.concurrent.atomic.AtomicBoolean;
41
42 import org.eclipse.jdt.annotation.NonNull;
43 import org.newsclub.net.unix.AFSocket;
44 import org.newsclub.net.unix.rmi.ShutdownHookSupport.ShutdownHook;
45
46 import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
47
48
49
50
51
52
53
54 public abstract class AFNaming extends AFRegistryAccess {
55 private static final String RMI_SERVICE_NAME = AFRMIService.class.getName();
56
57 private static final Map<AFNamingRef, AFNaming> INSTANCES = new HashMap<>();
58
59 private AFRegistry registry = null;
60 private AFRMIService rmiService = null;
61 private final int registryPort;
62 private final int servicePort;
63 AFRMISocketFactory socketFactory;
64 private final AtomicBoolean remoteShutdownAllowed = new AtomicBoolean(true);
65 private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false);
66 private final AtomicBoolean addedShutdownHook = new AtomicBoolean(false);
67
68
69
70
71
72
73
74 protected AFNaming(final int registryPort, final int servicePort) {
75 super();
76 this.registryPort = registryPort;
77 this.servicePort = servicePort;
78 }
79
80
81
82
83
84
85
86
87 protected abstract AFRegistry newAFRegistry(Registry impl) throws RemoteException;
88
89
90
91
92
93
94
95 protected abstract AFRMISocketFactory initSocketFactory() throws IOException;
96
97 @SuppressWarnings("unchecked")
98 static <T extends AFNaming> T getInstance(final int registryPort,
99 @NonNull AFNamingProvider<T> provider) throws RemoteException {
100 Objects.requireNonNull(provider);
101 final AFNamingRef sap = new AFNamingRef(provider, registryPort);
102 T instance;
103 synchronized (AFNaming.class) {
104 instance = (T) INSTANCES.get(sap);
105 if (instance == null) {
106 try {
107 instance = provider.newInstance(registryPort);
108 Objects.requireNonNull(instance);
109 synchronized (instance) {
110 instance.socketFactory = instance.initSocketFactory();
111 }
112 } catch (RemoteException e) {
113 throw e;
114 } catch (IOException e) {
115 throw new RemoteException(e.getMessage(), e);
116 }
117 INSTANCES.put(sap, instance);
118 }
119 }
120 return instance;
121 }
122
123
124
125
126
127
128 @SuppressFBWarnings("EI_EXPOSE_REP")
129 public synchronized AFRMISocketFactory getSocketFactory() {
130 return socketFactory;
131 }
132
133
134
135
136
137
138 public final int getRegistryPort() {
139 return registryPort;
140 }
141
142 AFRMIService getRMIService() throws RemoteException, NotBoundException {
143 return getRMIService(getRegistry());
144 }
145
146 synchronized AFRMIService getRMIService(AFRegistry reg) throws RemoteException,
147 NotBoundException {
148 if (rmiService == null) {
149 this.rmiService = getRMIServiceFromRegistry(reg);
150 }
151 return rmiService;
152 }
153
154 AFRMIService getRMIServiceFromRegistry(AFRegistry reg) throws RemoteException, NotBoundException {
155 AFRMIService service;
156 service = (AFRMIService) reg.lookup(RMI_SERVICE_NAME, 5, TimeUnit.SECONDS);
157 this.remoteShutdownAllowed.set(service.isShutdownAllowed());
158 return service;
159 }
160
161 private void closeUponRuntimeShutdown() {
162 if (addedShutdownHook.compareAndSet(false, true)) {
163 ShutdownHookSupport.addWeakShutdownHook(new ShutdownHook() {
164
165 @Override
166 @SuppressWarnings("LockOnNonEnclosingClassLiteral" )
167 public synchronized void onRuntimeShutdown(Thread thread) throws IOException {
168 if (registry != null && registry.isLocal()) {
169 shutdownRegistry();
170 }
171 }
172 });
173 }
174 }
175
176 private synchronized void rebindRMIService(final AFRMIService assigner) throws RemoteException {
177 rmiService = assigner;
178 getRegistry().rebind(RMI_SERVICE_NAME, assigner);
179 }
180
181 @Override
182 public AFRegistry getRegistry() throws RemoteException {
183 return getRegistry(0, TimeUnit.SECONDS);
184 }
185
186
187
188
189
190
191
192
193
194
195
196
197 public AFRegistry getRegistry(long timeout, TimeUnit unit) throws RemoteException {
198 if (shutdownInProgress.get()) {
199 throw new ShutdownException();
200 }
201 synchronized (this) {
202 AFRegistry reg = getRegistry(false);
203 if (reg == null) {
204 reg = openRegistry(timeout, unit);
205 }
206 return reg;
207 }
208 }
209
210
211
212
213
214
215
216
217
218 protected abstract AFRegistry openRegistry(long timeout, TimeUnit unit) throws RemoteException;
219
220
221
222
223
224
225
226
227
228
229
230 public AFRegistry getRegistry(boolean create) throws RemoteException {
231 if (shutdownInProgress.get()) {
232 throw new ShutdownException();
233 }
234 synchronized (this) {
235 if (registry != null) {
236 return registry;
237 } else if (!socketFactory.hasRegisteredPort(registryPort)) {
238 return create ? createRegistry() : null;
239 }
240
241 AFRegistry reg = locateRegistry();
242 setRegistry(reg);
243
244 try {
245 getRMIService(reg);
246 } catch (NotBoundException | NoSuchObjectException | ConnectIOException e) {
247 if (create) {
248 setRegistry(null);
249 return createRegistry();
250 } else {
251 throw new ServerException("Could not access " + AFRMIService.class.getName(), e);
252 }
253 }
254
255 return registry;
256 }
257 }
258
259 private AFRegistry locateRegistry() throws RemoteException {
260 Registry regImpl = LocateRegistry.getRegistry(null, registryPort, socketFactory);
261 return regImpl == null ? null : newAFRegistry(regImpl);
262 }
263
264
265
266
267
268
269 public void shutdownRegistry() throws RemoteException {
270 synchronized (this) {
271 if (registry == null) {
272 return;
273 }
274
275 AFRegistry registryToBeClosed = registry;
276 AFRMIService rmiServiceToBeClosed = rmiService;
277
278 if (!registryToBeClosed.isLocal()) {
279 if (!isRemoteShutdownAllowed()) {
280 throw new ServerException("The server refuses to be shutdown remotely");
281 }
282 setRegistry(null);
283
284 try {
285 shutdownViaRMIService(registryToBeClosed, rmiServiceToBeClosed);
286 } catch (Exception e) {
287
288 }
289 return;
290 }
291
292 setRegistry(null);
293
294 if (!shutdownInProgress.compareAndSet(false, true)) {
295 return;
296 }
297 try {
298 unexportRMIService(registryToBeClosed, (AFRMIServiceImpl) rmiServiceToBeClosed);
299 forceUnexportBound(registryToBeClosed);
300 closeSocketFactory();
301 shutdownRegistryFinishingTouches();
302 } finally {
303 shutdownInProgress.set(false);
304 }
305 }
306 }
307
308
309
310
311 protected abstract void shutdownRegistryFinishingTouches();
312
313 private synchronized void unexportRMIService(AFRegistry reg, AFRMIServiceImpl serv)
314 throws AccessException, RemoteException {
315 if (serv != null) {
316 serv.shutdownRegisteredCloseables();
317 }
318
319 try {
320 if (serv != null) {
321 unexportObject(serv);
322 }
323 reg.unbind(RMI_SERVICE_NAME);
324 } catch (ShutdownException | NotBoundException e) {
325
326 }
327 this.rmiService = null;
328 }
329
330 private void forceUnexportBound(AFRegistry reg) {
331 try {
332 reg.forceUnexportBound();
333 } catch (Exception e) {
334
335 }
336 }
337
338 private void closeSocketFactory() {
339 if (socketFactory != null) {
340 try {
341 socketFactory.close();
342 } catch (IOException e) {
343
344 }
345 }
346 }
347
348 private void shutdownViaRMIService(AFRegistry reg, AFRMIService serv) throws RemoteException {
349 try {
350 if (serv == null) {
351 serv = getRMIService(reg);
352 }
353 if (serv.isShutdownAllowed()) {
354 serv.shutdown();
355 }
356 } catch (ServerException | ConnectIOException | NotBoundException e) {
357
358 }
359 }
360
361
362
363
364
365
366
367
368
369
370
371
372
373 public synchronized AFRegistry createRegistry() throws RemoteException {
374 AFRegistry existingRegistry = registry;
375 if (existingRegistry == null) {
376 try {
377 existingRegistry = getRegistry(false);
378 } catch (ServerException e) {
379 Throwable cause = e.getCause();
380 if (cause instanceof NotBoundException || cause instanceof ConnectIOException) {
381 existingRegistry = null;
382 } else {
383 throw e;
384 }
385 }
386 }
387 if (existingRegistry != null) {
388 if (!isRemoteShutdownAllowed()) {
389 throw new ServerException("The server refuses to be shutdown remotely");
390 }
391 shutdownRegistry();
392 }
393
394 initRegistryPrerequisites();
395 AFRegistry newAFRegistry = newAFRegistry(LocateRegistry.createRegistry(registryPort,
396 socketFactory, socketFactory));
397 setRegistry(newAFRegistry);
398
399 final AFRMIService service = new AFRMIServiceImpl(this);
400 UnicastRemoteObject.exportObject(service, servicePort, socketFactory, socketFactory);
401
402 rebindRMIService(service);
403
404 return registry;
405 }
406
407
408
409
410
411
412 protected abstract void initRegistryPrerequisites() throws ServerException;
413
414
415
416
417
418
419 public boolean isRemoteShutdownAllowed() {
420 return remoteShutdownAllowed.get();
421 }
422
423
424
425
426
427
428 public void setRemoteShutdownAllowed(boolean remoteShutdownAllowed) {
429 this.remoteShutdownAllowed.set(remoteShutdownAllowed);
430 }
431
432
433
434
435
436
437
438
439
440
441 public void exportAndBind(String name, Remote obj) throws RemoteException, AlreadyBoundException {
442 exportObject(obj, getSocketFactory());
443
444 getRegistry().bind(name, obj);
445 }
446
447
448
449
450
451
452
453
454
455 public void exportAndRebind(String name, Remote obj) throws RemoteException {
456 exportObject(obj, getSocketFactory());
457
458 getRegistry().rebind(name, obj);
459 }
460
461
462
463
464
465
466
467
468
469 public void unexportAndUnbind(String name, Remote obj) throws RemoteException {
470 unexportObject(obj);
471 try {
472 unbind(name);
473 } catch (MalformedURLException | NotBoundException e) {
474
475 }
476 }
477
478
479
480
481
482
483
484
485
486
487
488 public static Remote exportObject(Remote obj, RMISocketFactory socketFactory)
489 throws RemoteException {
490 return UnicastRemoteObject.exportObject(obj, 0, socketFactory, socketFactory);
491 }
492
493
494
495
496
497
498
499
500
501 public static void unexportObject(Remote obj) {
502 try {
503 UnicastRemoteObject.unexportObject(obj, true);
504 } catch (NoSuchObjectException e) {
505
506 }
507 }
508
509 private synchronized void setRegistry(AFRegistry registry) {
510 this.registry = registry;
511 if (registry == null) {
512 rmiService = null;
513 } else if (registry.isLocal()) {
514 closeUponRuntimeShutdown();
515 }
516 }
517 }