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.File;
21  import java.io.IOException;
22  import java.io.ObjectInput;
23  import java.io.ObjectOutput;
24  import java.rmi.server.RMIClientSocketFactory;
25  import java.rmi.server.RMIServerSocketFactory;
26  import java.rmi.server.RMISocketFactory;
27  import java.util.HashMap;
28  import java.util.Map;
29  import java.util.Objects;
30  
31  import org.newsclub.net.unix.AFSocket;
32  import org.newsclub.net.unix.AFSocketAddress;
33  import org.newsclub.net.unix.AFUNIXSocket;
34  import org.newsclub.net.unix.AFUNIXSocketAddress;
35  import org.newsclub.net.unix.AFUNIXSocketCredentials;
36  import org.newsclub.net.unix.HostAndPort;
37  
38  import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
39  
40  /**
41   * An {@link RMISocketFactory} that supports {@link AFUNIXSocket}s.
42   *
43   * @author Christian Kohlschütter
44   */
45  public class AFUNIXRMISocketFactory extends AFRMISocketFactory {
46    private static final long serialVersionUID = 1L;
47  
48    static final String DEFAULT_SOCKET_FILE_PREFIX = "";
49    static final String DEFAULT_SOCKET_FILE_SUFFIX = ".rmi";
50  
51    private File socketDir;
52    private String socketPrefix;
53    private String socketSuffix;
54  
55    private final transient Map<HostAndPort, AFUNIXSocketCredentials> credentials = new HashMap<>();
56  
57    /**
58     * Constructor required per definition.
59     *
60     * @see RMISocketFactory
61     */
62    public AFUNIXRMISocketFactory() {
63      super();
64    }
65  
66    /**
67     * Creates a new socket factory.
68     *
69     * @param naming The {@link AFNaming} instance to use.
70     * @param socketDir The directory to store the sockets in.
71     * @param defaultClientFactory The default {@link RMIClientSocketFactory}.
72     * @param defaultServerFactory The default {@link RMIServerSocketFactory}.
73     * @param socketPrefix A string that will be inserted at the beginning of each socket filename, or
74     *          {@code null}.
75     * @param socketSuffix A string that will be added to the end of each socket filename, or
76     *          {@code null}.
77     */
78    @SuppressFBWarnings("EI_EXPOSE_REP2")
79    public AFUNIXRMISocketFactory(final AFNaming naming, final File socketDir,
80        final RMIClientSocketFactory defaultClientFactory,
81        final RMIServerSocketFactory defaultServerFactory, final String socketPrefix,
82        final String socketSuffix) {
83      super(naming, defaultClientFactory, defaultServerFactory);
84      Objects.requireNonNull(socketDir);
85      this.socketDir = socketDir;
86      this.socketPrefix = socketPrefix == null ? DEFAULT_SOCKET_FILE_PREFIX : socketPrefix;
87      this.socketSuffix = socketSuffix == null ? DEFAULT_SOCKET_FILE_SUFFIX : socketSuffix;
88    }
89  
90    /**
91     * Creates a new socket factory.
92     *
93     * @param naming The {@link AFNaming} instance to use.
94     * @param socketDir The directory to store the sockets in.
95     * @param defaultClientFactory The default {@link RMIClientSocketFactory}.
96     * @param defaultServerFactory The default {@link RMIServerSocketFactory}.
97     */
98    public AFUNIXRMISocketFactory(AFNaming naming, File socketDir,
99        RMIClientSocketFactory defaultClientFactory, RMIServerSocketFactory defaultServerFactory) {
100     this(naming, socketDir, defaultClientFactory, defaultServerFactory, null, null);
101   }
102 
103   /**
104    * Creates a new socket factory.
105    *
106    * @param naming The {@link AFNaming} instance to use.
107    * @param socketDir The directory to store the sockets in.
108    */
109   public AFUNIXRMISocketFactory(AFNaming naming, File socketDir) {
110     this(naming, socketDir, DefaultRMIClientSocketFactory.getInstance(),
111         DefaultRMIServerSocketFactory.getInstance());
112   }
113 
114   @Override
115   protected AFNaming readNamingInstance(ObjectInput in) throws IOException {
116     socketDir = new File(in.readUTF());
117     int port = in.readInt();
118     return AFUNIXNaming.getInstance(socketDir, port);
119   }
120 
121   @Override
122   protected void writeNamingInstance(ObjectOutput out, AFNaming naming) throws IOException {
123     out.writeUTF(socketDir.getAbsolutePath());
124     out.writeInt(naming.getRegistryPort());
125   }
126 
127   @Override
128   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
129     super.readExternal(in);
130 
131     socketPrefix = in.readUTF();
132     socketSuffix = in.readUTF();
133   }
134 
135   @Override
136   public void writeExternal(ObjectOutput out) throws IOException {
137     super.writeExternal(out);
138 
139     out.writeUTF(socketPrefix);
140     out.writeUTF(socketSuffix);
141   }
142 
143   @Override
144   public int hashCode() {
145     return socketDir == null ? System.identityHashCode(this) : socketDir.hashCode();
146   }
147 
148   @Override
149   public boolean equals(Object other) {
150     if (!(other instanceof AFUNIXRMISocketFactory)) {
151       return false;
152     }
153     AFUNIXRMISocketFactory sf = (AFUNIXRMISocketFactory) other;
154     if (socketDir == null) {
155       return sf == this;
156     } else {
157       return socketDir.equals(sf.socketDir);
158     }
159   }
160 
161   /**
162    * The directory in which socket files are stored.
163    *
164    * @return The directory.
165    */
166   public File getSocketDir() {
167     return socketDir;
168   }
169 
170   File getFile(int port) {
171     if (isPlainFileSocket()) {
172       return getSocketDir();
173     } else {
174       Objects.requireNonNull(socketDir);
175       return new File(socketDir, socketPrefix + port + socketSuffix);
176     }
177   }
178 
179   boolean hasSocketFile(int port) {
180     return getFile(port).exists();
181   }
182 
183   private boolean isPlainFileSocket() {
184     return (getNaming().getRegistryPort() == RMIPorts.PLAIN_FILE_SOCKET);
185   }
186 
187   @Override
188   protected AFUNIXSocketAddress newSocketAddress(int port) throws IOException {
189     return AFUNIXSocketAddress.of(getFile(port), port);
190   }
191 
192   @Override
193   protected final AFSocket<?> newConnectedSocket(AFSocketAddress addr) throws IOException {
194     final AFUNIXSocket socket = ((AFUNIXSocketAddress) addr).newConnectedSocket();
195     AFUNIXSocketCredentials creds = socket.getPeerCredentials();
196 
197     final HostAndPort hap = new HostAndPort(addr.getHostString(), addr.getPort());
198     synchronized (credentials) {
199       if (credentials.put(hap, creds) != null) {
200         // unexpected
201       }
202     }
203     socket.addCloseable(() -> {
204       synchronized (credentials) {
205         credentials.remove(hap);
206       }
207     });
208     return socket;
209   }
210 
211   @Override
212   public String toString() {
213     return super.toString() + //
214         "[path=" + socketDir + //
215         (isPlainFileSocket() ? "" : //
216             ";prefix=" + socketPrefix + ";suffix=" + socketSuffix) + "]";
217   }
218 
219   @Override
220   public void close() throws IOException {
221     synchronized (credentials) {
222       credentials.clear();
223     }
224     super.close();
225   }
226 
227   AFUNIXSocketCredentials peerCredentialsFor(RemotePeerInfo data) {
228     synchronized (credentials) {
229       return credentials.get(new HostAndPort(data.host, data.port));
230     }
231   }
232 
233   @Override
234   boolean hasRegisteredPort(int port) {
235     return hasSocketFile(port);
236   }
237 }