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.session.IoSession;
35  import org.apache.mina.core.session.IoSessionConfig;
36  
37  
38  /**
39   * A base implementation of {@link IoAcceptor}.
40   *
41   * @author The Apache MINA Project (dev@mina.apache.org)
42   * @org.apache.xbean.XBean
43   */
44  public abstract class AbstractIoAcceptor 
45          extends AbstractIoService implements IoAcceptor {
46      
47      private final List<SocketAddress> defaultLocalAddresses =
48          new ArrayList<SocketAddress>();
49      private final List<SocketAddress> unmodifiableDefaultLocalAddresses =
50          Collections.unmodifiableList(defaultLocalAddresses);
51      private final Set<SocketAddress> boundAddresses =
52          new HashSet<SocketAddress>();
53  
54      private boolean disconnectOnUnbind = true;
55  
56      /**
57       * The lock object which is acquired while bind or unbind operation is performed.
58       * Acquire this lock in your property setters which shouldn't be changed while
59       * the service is bound.
60       */
61      protected final Object bindLock = new Object();
62  
63      /**
64       * Constructor for {@link AbstractIoAcceptor}. You need to provide a default
65       * session configuration and an {@link Executor} for handling I/O events. If
66       * null {@link Executor} is provided, a default one will be created using
67       * {@link Executors#newCachedThreadPool()}.
68       *
69       * {@see AbstractIoService#AbstractIoService(IoSessionConfig, Executor)}
70       * 
71       * @param sessionConfig
72       *            the default configuration for the managed {@link IoSession}
73       * @param executor
74       *            the {@link Executor} used for handling execution of I/O
75       *            events. Can be <code>null</code>.
76       */
77      protected AbstractIoAcceptor(IoSessionConfig sessionConfig, Executor executor) {
78          super(sessionConfig, executor);
79          defaultLocalAddresses.add(null);
80      }
81  
82      /**
83       * {@inheritDoc}
84       */
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      public final Set<SocketAddress> getLocalAddresses() {
98          Set<SocketAddress> localAddresses = new HashSet<SocketAddress>();
99          synchronized (bindLock) {
100             localAddresses.addAll(boundAddresses);
101         }
102         return localAddresses;
103     }
104 
105     /**
106      * {@inheritDoc}
107      */
108     public SocketAddress getDefaultLocalAddress() {
109         if (defaultLocalAddresses.isEmpty()) {
110             return null;
111         }
112         return defaultLocalAddresses.iterator().next();
113     }
114 
115     /**
116      * {@inheritDoc}
117      */
118     public final void setDefaultLocalAddress(SocketAddress localAddress) {
119         setDefaultLocalAddresses(localAddress);
120     }
121 
122     /**
123      * {@inheritDoc}
124      */
125     public final List<SocketAddress> getDefaultLocalAddresses() {
126         return unmodifiableDefaultLocalAddresses;
127     }
128 
129     /**
130      * {@inheritDoc}
131      * @org.apache.xbean.Property nestedType="java.net.SocketAddress"
132      */
133     public final void setDefaultLocalAddresses(List<? extends SocketAddress> localAddresses) {
134         if (localAddresses == null) {
135             throw new NullPointerException("localAddresses");
136         }
137         setDefaultLocalAddresses((Iterable<? extends SocketAddress>) localAddresses);
138     }
139 
140     /**
141      * {@inheritDoc}
142      */
143     public final void setDefaultLocalAddresses(Iterable<? extends SocketAddress> localAddresses) {
144         if (localAddresses == null) {
145             throw new NullPointerException("localAddresses");
146         }
147         
148         synchronized (bindLock) {
149             if (!boundAddresses.isEmpty()) {
150                 throw new IllegalStateException(
151                         "localAddress can't be set while the acceptor is bound.");
152             }
153 
154             Collection<SocketAddress> newLocalAddresses = 
155                 new ArrayList<SocketAddress>();
156             for (SocketAddress a: localAddresses) {
157                 checkAddressType(a);
158                 newLocalAddresses.add(a);
159             }
160             
161             if (newLocalAddresses.isEmpty()) {
162                 throw new IllegalArgumentException("empty localAddresses");
163             }
164             
165             this.defaultLocalAddresses.clear();
166             this.defaultLocalAddresses.addAll(newLocalAddresses);
167         }
168     }
169 
170     /**
171      * {@inheritDoc}
172      * @org.apache.xbean.Property nestedType="java.net.SocketAddress"
173      */
174     public final void setDefaultLocalAddresses(SocketAddress firstLocalAddress, SocketAddress... otherLocalAddresses) {
175         if (otherLocalAddresses == null) {
176             otherLocalAddresses = new SocketAddress[0];
177         }
178         
179         Collection<SocketAddress> newLocalAddresses =
180             new ArrayList<SocketAddress>(otherLocalAddresses.length + 1);
181         
182         newLocalAddresses.add(firstLocalAddress);
183         for (SocketAddress a: otherLocalAddresses) {
184             newLocalAddresses.add(a);
185         }
186         
187         setDefaultLocalAddresses(newLocalAddresses);
188     }
189 
190     /**
191      * {@inheritDoc}
192      */
193     public final boolean isCloseOnDeactivation() {
194         return disconnectOnUnbind;
195     }
196 
197     /**
198      * {@inheritDoc}
199      */
200     public final void setCloseOnDeactivation(boolean disconnectClientsOnUnbind) {
201         this.disconnectOnUnbind = disconnectClientsOnUnbind;
202     }
203 
204     /**
205      * {@inheritDoc}
206      */
207     public final void bind() throws IOException {
208         bind(getDefaultLocalAddresses());
209     }
210 
211     /**
212      * {@inheritDoc}
213      */
214     public final void bind(SocketAddress localAddress) throws IOException {
215         if (localAddress == null) {
216             throw new NullPointerException("localAddress");
217         }
218         
219         List<SocketAddress> localAddresses = new ArrayList<SocketAddress>(1);
220         localAddresses.add(localAddress);
221         bind(localAddresses);
222     }
223 
224 
225     /**
226      * {@inheritDoc}
227      */
228     public final void bind(SocketAddress firstLocalAddress, SocketAddress... otherLocalAddresses) throws IOException {
229         if (firstLocalAddress == null) {
230             bind(getDefaultLocalAddresses());
231             return;
232         }
233         
234         List<SocketAddress> localAddresses = new ArrayList<SocketAddress>(2);
235         localAddresses.add(firstLocalAddress);
236 
237         if (otherLocalAddresses != null) {
238             for (SocketAddress address:otherLocalAddresses) {
239                 localAddresses.add(address);
240             }
241         }
242 
243         bind(localAddresses);
244     }
245 
246     /**
247      * {@inheritDoc}
248      */
249     public final void bind(Iterable<? extends SocketAddress> localAddresses) throws IOException {
250         if (isDisposing()) {
251             throw new IllegalStateException("Already disposed.");
252         }
253         
254         if (localAddresses == null) {
255             throw new NullPointerException("localAddresses");
256         }
257         
258         List<SocketAddress> localAddressesCopy = new ArrayList<SocketAddress>();
259         
260         for (SocketAddress a: localAddresses) {
261             checkAddressType(a);
262             localAddressesCopy.add(a);
263         }
264         
265         if (localAddressesCopy.isEmpty()) {
266             throw new IllegalArgumentException("localAddresses is empty.");
267         }
268         
269         boolean activate = false;
270         synchronized (bindLock) {
271             if (boundAddresses.isEmpty()) {
272                 activate = true;
273             }
274 
275             if (getHandler() == null) {
276                 throw new IllegalStateException("handler is not set.");
277             }
278             
279             try {
280                 boundAddresses.addAll(bindInternal(localAddressesCopy));
281             } catch (IOException e) {
282                 throw e;
283             } catch (RuntimeException e) {
284                 throw e;
285             } catch (Throwable e) {
286                 throw new RuntimeIoException(
287                         "Failed to bind to: " + getLocalAddresses(), e);
288             }
289         }
290         
291         if (activate) {
292             getListeners().fireServiceActivated();
293         }
294     }
295 
296     /**
297      * {@inheritDoc}
298      */
299     public final void unbind() {
300         unbind(getLocalAddresses());
301     }
302 
303     /**
304      * {@inheritDoc}
305      */
306     public final void unbind(SocketAddress localAddress) {
307         if (localAddress == null) {
308             throw new NullPointerException("localAddress");
309         }
310         
311         List<SocketAddress> localAddresses = new ArrayList<SocketAddress>(1);
312         localAddresses.add(localAddress);
313         unbind(localAddresses);
314     }
315 
316     /**
317      * {@inheritDoc}
318      */
319     public final void unbind(SocketAddress firstLocalAddress,
320             SocketAddress... otherLocalAddresses) {
321         if (firstLocalAddress == null) {
322             throw new NullPointerException("firstLocalAddress");
323         }
324         if (otherLocalAddresses == null) {
325             throw new NullPointerException("otherLocalAddresses");
326         }
327         
328         List<SocketAddress> localAddresses = new ArrayList<SocketAddress>();
329         localAddresses.add(firstLocalAddress);
330         Collections.addAll(localAddresses, otherLocalAddresses);
331         unbind(localAddresses);
332     }
333 
334     /**
335      * {@inheritDoc}
336      */
337     public final void unbind(Iterable<? extends SocketAddress> localAddresses) {
338         if (localAddresses == null) {
339             throw new NullPointerException("localAddresses");
340         }
341         
342         boolean deactivate = false;
343         synchronized (bindLock) {
344             if (boundAddresses.isEmpty()) {
345                 return;
346             }
347 
348             List<SocketAddress> localAddressesCopy = new ArrayList<SocketAddress>();
349             int specifiedAddressCount = 0;
350             for (SocketAddress a: localAddresses) {
351                 specifiedAddressCount ++;
352                 if (a != null && boundAddresses.contains(a)) {
353                     localAddressesCopy.add(a);
354                 }
355             }
356             if (specifiedAddressCount == 0) {
357                 throw new IllegalArgumentException("localAddresses is empty.");
358             }
359             
360             if (!localAddressesCopy.isEmpty()) {
361                 try {
362                     unbind0(localAddressesCopy);
363                 } catch (RuntimeException e) {
364                     throw e;
365                 } catch (Throwable e) {
366                     throw new RuntimeIoException(
367                             "Failed to unbind from: " + getLocalAddresses(), e);
368                 }
369                 
370                 boundAddresses.removeAll(localAddressesCopy);
371                 if (boundAddresses.isEmpty()) {
372                     deactivate = true;
373                 }
374             }
375         }
376 
377         if (deactivate) {
378             getListeners().fireServiceDeactivated();
379         }
380     }
381 
382     /**
383      * Starts the acceptor, and register the given addresses
384      * @return the {@link Set} of the local addresses which is bound actually
385      */
386     protected abstract Set<SocketAddress> bindInternal(
387             List<? extends SocketAddress> localAddresses) throws Exception;
388 
389     /**
390      * Implement this method to perform the actual unbind operation.
391      */
392     protected abstract void unbind0(
393             List<? extends SocketAddress> localAddresses) throws Exception;
394     
395     @Override
396     public String toString() {
397         TransportMetadata m = getTransportMetadata();
398         return '(' + m.getProviderName() + ' ' + m.getName() + " acceptor: " + 
399                (isActive()?
400                        "localAddress(es): " + getLocalAddresses() +
401                        ", managedSessionCount: " + getManagedSessionCount() :
402                            "not bound") + ')'; 
403     }
404 
405     private void checkAddressType(SocketAddress a) {
406         if (a != null &&
407             !getTransportMetadata().getAddressType().isAssignableFrom(
408                         a.getClass())) {
409             throw new IllegalArgumentException("localAddress type: "
410                     + a.getClass().getSimpleName() + " (expected: "
411                     + getTransportMetadata().getAddressType().getSimpleName() + ")");
412         }
413     }
414     
415     public static class AcceptorOperationFuture extends ServiceOperationFuture {
416         private final List<SocketAddress> localAddresses;
417         
418         public AcceptorOperationFuture(List<? extends SocketAddress> localAddresses) {
419             this.localAddresses = new ArrayList<SocketAddress>(localAddresses);
420         }
421         
422         public final List<SocketAddress> getLocalAddresses() {
423             return Collections.unmodifiableList(localAddresses);
424         }
425         
426         /**
427          * @see Object#toString()
428          */
429         public String toString() {
430             StringBuilder sb = new StringBuilder();
431             
432             sb.append( "Acceptor operation : " );
433             
434             if (localAddresses != null) {
435                 boolean isFirst = true;
436                 
437                 for (SocketAddress address:localAddresses) {
438                     if (isFirst) {
439                         isFirst = false;
440                     } else {
441                         sb.append(", ");
442                     }
443                     
444                     sb.append(address);
445                 }
446             }
447             return sb.toString(); 
448         }
449     }
450 }