View Javadoc
1   /*
2    * junixsocket
3    *
4    * Copyright 2009-2024 Christian Kohlschütter
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.newsclub.net.unix.rmi;
19  
20  import java.io.IOException;
21  import java.rmi.AccessException;
22  import java.rmi.AlreadyBoundException;
23  import java.rmi.ConnectIOException;
24  import java.rmi.NoSuchObjectException;
25  import java.rmi.NotBoundException;
26  import java.rmi.Remote;
27  import java.rmi.RemoteException;
28  import java.rmi.registry.Registry;
29  import java.rmi.server.RemoteObject;
30  import java.rmi.server.RemoteServer;
31  import java.util.HashMap;
32  import java.util.Map;
33  import java.util.concurrent.TimeUnit;
34  import java.util.concurrent.atomic.AtomicBoolean;
35  
36  import org.newsclub.net.unix.rmi.ShutdownHookSupport.ShutdownHook;
37  
38  import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
39  
40  /**
41   * A wrapper for RMI registries, both remote and local, to allow for a clean removal of bound
42   * resources upon shutdown.
43   *
44   * @author Christian Kohlschütter
45   */
46  public abstract class AFRegistry implements Registry {
47    final RemoteCloseable<?> boundCloser;
48  
49    private final Registry impl;
50    private final Map<String, Remote> bound = new HashMap<>();
51    private final AFNaming naming;
52    private final AtomicBoolean boundCloserExported = new AtomicBoolean(false);
53  
54    private AFRMIService rmiService = null;
55  
56    AFRegistry(AFNaming naming, Registry impl) {
57      this.naming = naming;
58      this.impl = impl;
59      this.boundCloser = new RemoteCloseable<Void>() {
60        @Override
61        public Void get() throws IOException {
62          return null;
63        }
64  
65        @Override
66        public void close() throws IOException {
67          AFRegistry.this.forceUnexportBound();
68        }
69      };
70  
71      if (isLocal()) {
72        ShutdownHookSupport.addWeakShutdownHook(new ShutdownHook() {
73          @Override
74          public void onRuntimeShutdown(Thread thread) {
75            forceUnexportBound();
76          }
77        });
78      }
79    }
80  
81    /**
82     * Returns {@code true} if the wrapped Registry instance is a locally created
83     * {@link RemoteServer}.
84     *
85     * @return {@code true} if wrapped instance is a locally created {@link RemoteServer}.
86     * @see #isLocal()
87     */
88    @Deprecated
89    public boolean isRemoteServer() {
90      return isLocal();
91    }
92  
93    /**
94     * Returns {@code true} if the wrapped Registry instance is locally created.
95     *
96     * @return {@code true} if wrapped instance is locally created.
97     */
98    public final boolean isLocal() {
99      return (impl instanceof RemoteServer);
100   }
101 
102   /**
103    * Returns the {@link AFNaming} instance responsible for this registry.
104    *
105    * @return The {@link AFNaming} instance.
106    */
107   @SuppressFBWarnings("EI_EXPOSE_REP")
108   public AFNaming getNaming() {
109     return naming;
110   }
111 
112   @Override
113   public Remote lookup(String name) throws RemoteException, NotBoundException, AccessException {
114     return impl.lookup(name);
115   }
116 
117   @Override
118   public void bind(String name, Remote obj) throws RemoteException, AlreadyBoundException,
119       AccessException {
120     impl.bind(name, RemoteObject.toStub(obj));
121     synchronized (bound) {
122       bound.put(name, obj);
123     }
124     checkBound();
125   }
126 
127   @Override
128   public void unbind(String name) throws RemoteException, NotBoundException, AccessException {
129     impl.unbind(name);
130     synchronized (bound) {
131       bound.remove(name);
132     }
133     checkBound();
134   }
135 
136   @Override
137   public void rebind(String name, Remote obj) throws RemoteException, AccessException {
138     impl.rebind(name, RemoteObject.toStub(obj));
139     synchronized (bound) {
140       bound.put(name, obj);
141     }
142     checkBound();
143   }
144 
145   @Override
146   public String[] list() throws RemoteException, AccessException {
147     return impl == null ? new String[0] : impl.list();
148   }
149 
150   /**
151    * Returns the remote reference bound to the specified <code>name</code> in this registry. If the
152    * reference has not been bound yet, repeated attempts to resolve it are made until the specified
153    * time elapses.
154    *
155    * @param name the name for the remote reference to look up
156    * @param timeout The timeout value.
157    * @param unit The timeout unit.
158    *
159    * @return a reference to a remote object
160    *
161    * @throws NotBoundException if <code>name</code> is not currently bound and couldn't be resolved
162    *           in the specified time.
163    *
164    * @throws RemoteException if remote communication with the registry failed; if exception is a
165    *           <code>ServerException</code> containing an <code>AccessException</code>, then the
166    *           registry denies the caller access to perform this operation
167    *
168    * @throws AccessException if this registry is local and it denies the caller access to perform
169    *           this operation
170    *
171    * @throws NullPointerException if <code>name</code> is <code>null</code>
172    */
173   public Remote lookup(String name, long timeout, TimeUnit unit) throws NotBoundException,
174       RemoteException {
175     long timeWait = unit.toMillis(timeout);
176 
177     Exception exFirst = null;
178     do {
179       try {
180         return impl.lookup(name);
181       } catch (NotBoundException | ConnectIOException | NoSuchObjectException e) {
182         if (exFirst == null) {
183           exFirst = e;
184         }
185       }
186 
187       try {
188         Thread.sleep(Math.min(timeWait, 50));
189       } catch (InterruptedException e1) {
190         exFirst.addSuppressed(e1);
191         break;
192       }
193       timeWait -= 50;
194     } while (timeWait > 0);
195 
196     if (exFirst instanceof NotBoundException) {
197       throw (NotBoundException) exFirst;
198     } else if (exFirst instanceof RemoteException) {
199       throw (RemoteException) exFirst;
200     } else {
201       throw new RemoteException("Lookup timed out");
202     }
203   }
204 
205   void forceUnexportBound() {
206     final Map<String, Remote> map;
207     synchronized (bound) {
208       map = new HashMap<>(bound);
209       bound.clear();
210     }
211     try {
212       checkBound();
213     } catch (RemoteException e1) {
214       // ignore
215     }
216     for (Map.Entry<String, Remote> en : map.entrySet()) {
217       String name = en.getKey();
218       Remote obj = en.getValue();
219       if (obj == null) {
220         continue;
221       }
222       AFNaming.unexportObject(obj);
223       try {
224         unbind(name);
225       } catch (RemoteException | NotBoundException e) {
226         // ignore
227       }
228 
229     }
230     try {
231       for (String list : list()) {
232         try {
233           unbind(list);
234         } catch (RemoteException | NotBoundException e) {
235           // ignore
236         }
237       }
238     } catch (RemoteException e) {
239       // ignore
240     }
241 
242     AFNaming.unexportObject(this.impl);
243     AFNaming.unexportObject(this);
244   }
245 
246   private void checkBound() throws RemoteException {
247     boolean empty;
248     synchronized (bound) {
249       empty = bound.isEmpty();
250     }
251     if (empty) {
252       if (boundCloserExported.compareAndSet(true, false)) {
253         AFRMIService service;
254         try {
255           service = getRMIService();
256           service.unregisterForShutdown(boundCloser);
257         } catch (NoSuchObjectException | NotBoundException e) {
258           return;
259         } finally {
260           AFNaming.unexportObject(boundCloser);
261         }
262       }
263     } else if (boundCloserExported.compareAndSet(false, true)) {
264       AFNaming.exportObject(boundCloser, naming.getSocketFactory());
265 
266       AFRMIService service;
267       try {
268         service = getRMIService();
269       } catch (NotBoundException e) {
270         return;
271       }
272       service.registerForShutdown(boundCloser);
273     }
274   }
275 
276   private AFRMIService getRMIService() throws RemoteException, NotBoundException {
277     if (rmiService == null) {
278       rmiService = naming.getRMIService(this);
279     }
280     return rmiService;
281   }
282 }