View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  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,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.core.service;
21  
22  import java.io.IOException;
23  import java.net.SocketAddress;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Set;
30  import java.util.concurrent.Executor;
31  import java.util.concurrent.Executors;
32  
33  import org.apache.mina.core.RuntimeIoException;
34  import org.apache.mina.core.future.IoFuture;
35  import org.apache.mina.core.session.IoSession;
36  import org.apache.mina.core.session.IoSessionConfig;
37  
38  /**
39   * A base implementation of {@link IoAcceptor}.
40   *
41   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
42   * @org.apache.xbean.XBean
43   */
44  public abstract class AbstractIoAcceptor extends AbstractIoService implements IoAcceptor {
45  
46      private final List<SocketAddress> defaultLocalAddresses = new ArrayList<>();
47  
48      private final List<SocketAddress> unmodifiableDefaultLocalAddresses = Collections
49              .unmodifiableList(defaultLocalAddresses);
50  
51      private final Set<SocketAddress> boundAddresses = new HashSet<>();
52  
53      private boolean disconnectOnUnbind = true;
54  
55      /**
56       * The lock object which is acquired while bind or unbind operation is performed.
57       * Acquire this lock in your property setters which shouldn't be changed while
58       * the service is bound.
59       */
60      protected final Object bindLock = new Object();
61  
62      /**
63       * Constructor for {@link AbstractIoAcceptor}. You need to provide a default
64       * session configuration and an {@link Executor} for handling I/O events. If
65       * null {@link Executor} is provided, a default one will be created using
66       * {@link Executors#newCachedThreadPool()}.
67       * 
68       * @see AbstractIoService#AbstractIoService(IoSessionConfig, Executor)
69       * 
70       * @param sessionConfig
71       *            the default configuration for the managed {@link IoSession}
72       * @param executor
73       *            the {@link Executor} used for handling execution of I/O
74       *            events. Can be <code>null</code>.
75       */
76      protected AbstractIoAcceptor(IoSessionConfig sessionConfig, Executor executor) {
77          super(sessionConfig, executor);
78          defaultLocalAddresses.add(null);
79      }
80  
81      /**
82       * {@inheritDoc}
83       */
84      @Override
85      public SocketAddress getLocalAddress() {
86          Set<SocketAddress> localAddresses = getLocalAddresses();
87          if (localAddresses.isEmpty()) {
88              return null;
89          }
90  
91          return localAddresses.iterator().next();
92      }
93  
94      /**
95       * {@inheritDoc}
96       */
97      @Override
98      public final Set<SocketAddress> getLocalAddresses() {
99          Set<SocketAddress> localAddresses = new HashSet<>();
100 
101         synchronized (boundAddresses) {
102             localAddresses.addAll(boundAddresses);
103         }
104 
105         return localAddresses;
106     }
107 
108     /**
109      * {@inheritDoc}
110      */
111     @Override
112     public SocketAddress getDefaultLocalAddress() {
113         if (defaultLocalAddresses.isEmpty()) {
114             return null;
115         }
116         return defaultLocalAddresses.iterator().next();
117     }
118 
119     /**
120      * {@inheritDoc}
121      */
122     @Override
123     public final void setDefaultLocalAddress(SocketAddress localAddress) {
124         setDefaultLocalAddresses(localAddress);
125     }
126 
127     /**
128      * {@inheritDoc}
129      */
130     @Override
131     public final List<SocketAddress> getDefaultLocalAddresses() {
132         return unmodifiableDefaultLocalAddresses;
133     }
134 
135     /**
136      * {@inheritDoc}
137      * @org.apache.xbean.Property nestedType="java.net.SocketAddress"
138      */
139     @Override
140     public final void setDefaultLocalAddresses(List<? extends SocketAddress> localAddresses) {
141         if (localAddresses == null) {
142             throw new IllegalArgumentException("localAddresses");
143         }
144         setDefaultLocalAddresses((Iterable<? extends SocketAddress>) localAddresses);
145     }
146 
147     /**
148      * {@inheritDoc}
149      */
150     @Override
151     public final void setDefaultLocalAddresses(Iterable<? extends SocketAddress> localAddresses) {
152         if (localAddresses == null) {
153             throw new IllegalArgumentException("localAddresses");
154         }
155 
156         synchronized (bindLock) {
157             synchronized (boundAddresses) {
158                 if (!boundAddresses.isEmpty()) {
159                     throw new IllegalStateException("localAddress can't be set while the acceptor is bound.");
160                 }
161 
162                 Collection<SocketAddress> newLocalAddresses = new ArrayList<>();
163 
164                 for (SocketAddress a : localAddresses) {
165                     checkAddressType(a);
166                     newLocalAddresses.add(a);
167                 }
168 
169                 if (newLocalAddresses.isEmpty()) {
170                     throw new IllegalArgumentException("empty localAddresses");
171                 }
172 
173                 this.defaultLocalAddresses.clear();
174                 this.defaultLocalAddresses.addAll(newLocalAddresses);
175             }
176         }
177     }
178 
179     /**
180      * {@inheritDoc}
181      * @org.apache.xbean.Property nestedType="java.net.SocketAddress"
182      */
183     @Override
184     public final void setDefaultLocalAddresses(SocketAddress firstLocalAddress, SocketAddress... otherLocalAddresses) {
185         if (otherLocalAddresses == null) {
186             otherLocalAddresses = new SocketAddress[0];
187         }
188 
189         Collection<SocketAddress> newLocalAddresses = new ArrayList<>(otherLocalAddresses.length + 1);
190 
191         newLocalAddresses.add(firstLocalAddress);
192         
193         for (SocketAddress a : otherLocalAddresses) {
194             newLocalAddresses.add(a);
195         }
196 
197         setDefaultLocalAddresses(newLocalAddresses);
198     }
199 
200     /**
201      * {@inheritDoc}
202      */
203     @Override
204     public final boolean isCloseOnDeactivation() {
205         return disconnectOnUnbind;
206     }
207 
208     /**
209      * {@inheritDoc}
210      */
211     @Override
212     public final void setCloseOnDeactivation(boolean disconnectClientsOnUnbind) {
213         this.disconnectOnUnbind = disconnectClientsOnUnbind;
214     }
215 
216     /**
217      * {@inheritDoc}
218      */
219     @Override
220     public final void bind() throws IOException {
221         bind(getDefaultLocalAddresses());
222     }
223 
224     /**
225      * {@inheritDoc}
226      */
227     @Override
228     public final void bind(SocketAddress localAddress) throws IOException {
229         if (localAddress == null) {
230             throw new IllegalArgumentException("localAddress");
231         }
232 
233         List<SocketAddress> localAddresses = new ArrayList<>(1);
234         localAddresses.add(localAddress);
235         bind(localAddresses);
236     }
237 
238     /**
239      * {@inheritDoc}
240      */
241     @Override
242     public final void bind(SocketAddress... addresses) throws IOException {
243         if ((addresses == null) || (addresses.length == 0)) {
244             bind(getDefaultLocalAddresses());
245             return;
246         }
247 
248         List<SocketAddress> localAddresses = new ArrayList<>(2);
249 
250         for (SocketAddress address : addresses) {
251             localAddresses.add(address);
252         }
253 
254         bind(localAddresses);
255     }
256 
257     /**
258      * {@inheritDoc}
259      */
260     @Override
261     public final void bind(SocketAddress firstLocalAddress, SocketAddress... addresses) throws IOException {
262         if (firstLocalAddress == null) {
263             bind(getDefaultLocalAddresses());
264         }
265 
266         if ((addresses == null) || (addresses.length == 0)) {
267             bind(getDefaultLocalAddresses());
268             return;
269         }
270 
271         List<SocketAddress> localAddresses = new ArrayList<>(2);
272         localAddresses.add(firstLocalAddress);
273 
274         for (SocketAddress address : addresses) {
275             localAddresses.add(address);
276         }
277 
278         bind(localAddresses);
279     }
280 
281     /**
282      * {@inheritDoc}
283      */
284     @Override
285 public final void bind(Iterable<? extends SocketAddress> localAddresses) throws IOException {
286         if (isDisposing()) {
287             throw new IllegalStateException("The Accpetor disposed is being disposed.");
288         }
289 
290         if (localAddresses == null) {
291             throw new IllegalArgumentException("localAddresses");
292         }
293 
294         List<SocketAddress> localAddressesCopy = new ArrayList<>();
295 
296         for (SocketAddress a : localAddresses) {
297             checkAddressType(a);
298             localAddressesCopy.add(a);
299         }
300 
301         if (localAddressesCopy.isEmpty()) {
302             throw new IllegalArgumentException("localAddresses is empty.");
303         }
304 
305         boolean activate = false;
306         synchronized (bindLock) {
307             synchronized (boundAddresses) {
308                 if (boundAddresses.isEmpty()) {
309                     activate = true;
310                 }
311             }
312 
313             if (getHandler() == null) {
314                 throw new IllegalStateException("handler is not set.");
315             }
316 
317             try {
318                 Set<SocketAddress> addresses = bindInternal(localAddressesCopy);
319 
320                 synchronized (boundAddresses) {
321                     boundAddresses.addAll(addresses);
322                 }
323             } catch (IOException | RuntimeException e) {
324                 throw e;
325             } catch (Exception e) {
326                 throw new RuntimeIoException("Failed to bind to: " + getLocalAddresses(), e);
327             }
328         }
329 
330         if (activate) {
331             getListeners().fireServiceActivated();
332         }
333     }
334 
335     /**
336      * {@inheritDoc}
337      */
338     @Override
339     public final void unbind() {
340         unbind(getLocalAddresses());
341     }
342 
343     /**
344      * {@inheritDoc}
345      */
346     @Override
347     public final void unbind(SocketAddress localAddress) {
348         if (localAddress == null) {
349             throw new IllegalArgumentException("localAddress");
350         }
351 
352         List<SocketAddress> localAddresses = new ArrayList<>(1);
353         localAddresses.add(localAddress);
354         unbind(localAddresses);
355     }
356 
357     /**
358      * {@inheritDoc}
359      */
360     @Override
361     public final void unbind(SocketAddress firstLocalAddress, SocketAddress... otherLocalAddresses) {
362         if (firstLocalAddress == null) {
363             throw new IllegalArgumentException("firstLocalAddress");
364         }
365         if (otherLocalAddresses == null) {
366             throw new IllegalArgumentException("otherLocalAddresses");
367         }
368 
369         List<SocketAddress> localAddresses = new ArrayList<>();
370         localAddresses.add(firstLocalAddress);
371         Collections.addAll(localAddresses, otherLocalAddresses);
372         unbind(localAddresses);
373     }
374 
375     /**
376      * {@inheritDoc}
377      */
378     @Override
379     public final void unbind(Iterable<? extends SocketAddress> localAddresses) {
380         if (localAddresses == null) {
381             throw new IllegalArgumentException("localAddresses");
382         }
383 
384         boolean deactivate = false;
385         synchronized (bindLock) {
386             synchronized (boundAddresses) {
387                 if (boundAddresses.isEmpty()) {
388                     return;
389                 }
390 
391                 List<SocketAddress> localAddressesCopy = new ArrayList<>();
392                 int specifiedAddressCount = 0;
393 
394                 for (SocketAddress a : localAddresses) {
395                     specifiedAddressCount++;
396 
397                     if ((a != null) && boundAddresses.contains(a)) {
398                         localAddressesCopy.add(a);
399                     }
400                 }
401 
402                 if (specifiedAddressCount == 0) {
403                     throw new IllegalArgumentException("localAddresses is empty.");
404                 }
405 
406                 if (!localAddressesCopy.isEmpty()) {
407                     try {
408                         unbind0(localAddressesCopy);
409                     } catch (RuntimeException e) {
410                         throw e;
411                     } catch (Exception e) {
412                         throw new RuntimeIoException("Failed to unbind from: " + getLocalAddresses(), e);
413                     }
414 
415                     boundAddresses.removeAll(localAddressesCopy);
416 
417                     if (boundAddresses.isEmpty()) {
418                         deactivate = true;
419                     }
420                 }
421             }
422         }
423 
424         if (deactivate) {
425             getListeners().fireServiceDeactivated();
426         }
427     }
428 
429     /**
430      * Starts the acceptor, and register the given addresses
431      * 
432      * @param localAddresses The address to bind to
433      * @return the {@link Set} of the local addresses which is bound actually
434      * @throws Exception If the bind failed
435      */
436     protected abstract Set<SocketAddress> bindInternal(List<? extends SocketAddress> localAddresses) throws Exception;
437 
438     /**
439      * Implement this method to perform the actual unbind operation.
440      * 
441      * @param localAddresses The address to unbind from
442      * @throws Exception If the unbind failed
443      */
444     protected abstract void unbind0(List<? extends SocketAddress> localAddresses) throws Exception;
445 
446     @Override
447     public String toString() {
448         TransportMetadata m = getTransportMetadata();
449         return '('
450                 + m.getProviderName()
451                 + ' '
452                 + m.getName()
453                 + " acceptor: "
454                 + (isActive() ? "localAddress(es): " + getLocalAddresses() + ", managedSessionCount: "
455                         + getManagedSessionCount() : "not bound") + ')';
456     }
457 
458     private void checkAddressType(SocketAddress a) {
459         if (a != null && !getTransportMetadata().getAddressType().isAssignableFrom(a.getClass())) {
460             throw new IllegalArgumentException("localAddress type: " + a.getClass().getSimpleName() + " (expected: "
461                     + getTransportMetadata().getAddressType().getSimpleName() + ")");
462         }
463     }
464 
465     /**
466      * A {@link IoFuture} 
467      */
468     public static class AcceptorOperationFuture extends ServiceOperationFuture {
469         private final List<SocketAddress> localAddresses;
470 
471         /**
472          * Creates a new AcceptorOperationFuture instance
473          * 
474          * @param localAddresses The list of local addresses to listen to
475          */
476         public AcceptorOperationFuture(List<? extends SocketAddress> localAddresses) {
477             this.localAddresses = new ArrayList<>(localAddresses);
478         }
479 
480         /**
481          * @return The list of local addresses we listen to
482          */
483         public final List<SocketAddress> getLocalAddresses() {
484             return Collections.unmodifiableList(localAddresses);
485         }
486 
487         /**
488          * @see Object#toString()
489          */
490         @Override
491         public String toString() {
492             StringBuilder sb = new StringBuilder();
493 
494             sb.append("Acceptor operation : ");
495 
496             if (localAddresses != null) {
497                 boolean isFirst = true;
498 
499                 for (SocketAddress address : localAddresses) {
500                     if (isFirst) {
501                         isFirst = false;
502                     } else {
503                         sb.append(", ");
504                     }
505 
506                     sb.append(address);
507                 }
508             }
509             return sb.toString();
510         }
511     }
512 }