001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.mina.core.service;
021
022import java.io.IOException;
023import java.net.SocketAddress;
024import java.util.ArrayList;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.HashSet;
028import java.util.List;
029import java.util.Set;
030import java.util.concurrent.Executor;
031import java.util.concurrent.Executors;
032
033import org.apache.mina.core.RuntimeIoException;
034import org.apache.mina.core.session.IoSession;
035import org.apache.mina.core.session.IoSessionConfig;
036
037/**
038 * A base implementation of {@link IoAcceptor}.
039 *
040 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
041 * @org.apache.xbean.XBean
042 */
043public abstract class AbstractIoAcceptor extends AbstractIoService implements IoAcceptor {
044
045    private final List<SocketAddress> defaultLocalAddresses = new ArrayList<>();
046
047    private final List<SocketAddress> unmodifiableDefaultLocalAddresses = Collections
048            .unmodifiableList(defaultLocalAddresses);
049
050    private final Set<SocketAddress> boundAddresses = new HashSet<>();
051
052    private boolean disconnectOnUnbind = true;
053
054    /**
055     * The lock object which is acquired while bind or unbind operation is performed.
056     * Acquire this lock in your property setters which shouldn't be changed while
057     * the service is bound.
058     */
059    protected final Object bindLock = new Object();
060
061    /**
062     * Constructor for {@link AbstractIoAcceptor}. You need to provide a default
063     * session configuration and an {@link Executor} for handling I/O events. If
064     * null {@link Executor} is provided, a default one will be created using
065     * {@link Executors#newCachedThreadPool()}.
066     * 
067     * @see AbstractIoService#AbstractIoService(IoSessionConfig, Executor)
068     * 
069     * @param sessionConfig
070     *            the default configuration for the managed {@link IoSession}
071     * @param executor
072     *            the {@link Executor} used for handling execution of I/O
073     *            events. Can be <code>null</code>.
074     */
075    protected AbstractIoAcceptor(IoSessionConfig sessionConfig, Executor executor) {
076        super(sessionConfig, executor);
077        defaultLocalAddresses.add(null);
078    }
079
080    /**
081     * {@inheritDoc}
082     */
083    @Override
084    public SocketAddress getLocalAddress() {
085        Set<SocketAddress> localAddresses = getLocalAddresses();
086        if (localAddresses.isEmpty()) {
087            return null;
088        }
089
090        return localAddresses.iterator().next();
091    }
092
093    /**
094     * {@inheritDoc}
095     */
096    @Override
097    public final Set<SocketAddress> getLocalAddresses() {
098        Set<SocketAddress> localAddresses = new HashSet<>();
099
100        synchronized (boundAddresses) {
101            localAddresses.addAll(boundAddresses);
102        }
103
104        return localAddresses;
105    }
106
107    /**
108     * {@inheritDoc}
109     */
110    @Override
111    public SocketAddress getDefaultLocalAddress() {
112        if (defaultLocalAddresses.isEmpty()) {
113            return null;
114        }
115        return defaultLocalAddresses.iterator().next();
116    }
117
118    /**
119     * {@inheritDoc}
120     */
121    @Override
122    public final void setDefaultLocalAddress(SocketAddress localAddress) {
123        setDefaultLocalAddresses(localAddress);
124    }
125
126    /**
127     * {@inheritDoc}
128     */
129    @Override
130    public final List<SocketAddress> getDefaultLocalAddresses() {
131        return unmodifiableDefaultLocalAddresses;
132    }
133
134    /**
135     * {@inheritDoc}
136     * @org.apache.xbean.Property nestedType="java.net.SocketAddress"
137     */
138    @Override
139    public final void setDefaultLocalAddresses(List<? extends SocketAddress> localAddresses) {
140        if (localAddresses == null) {
141            throw new IllegalArgumentException("localAddresses");
142        }
143        setDefaultLocalAddresses((Iterable<? extends SocketAddress>) localAddresses);
144    }
145
146    /**
147     * {@inheritDoc}
148     */
149    @Override
150    public final void setDefaultLocalAddresses(Iterable<? extends SocketAddress> localAddresses) {
151        if (localAddresses == null) {
152            throw new IllegalArgumentException("localAddresses");
153        }
154
155        synchronized (bindLock) {
156            synchronized (boundAddresses) {
157                if (!boundAddresses.isEmpty()) {
158                    throw new IllegalStateException("localAddress can't be set while the acceptor is bound.");
159                }
160
161                Collection<SocketAddress> newLocalAddresses = new ArrayList<>();
162
163                for (SocketAddress a : localAddresses) {
164                    checkAddressType(a);
165                    newLocalAddresses.add(a);
166                }
167
168                if (newLocalAddresses.isEmpty()) {
169                    throw new IllegalArgumentException("empty localAddresses");
170                }
171
172                this.defaultLocalAddresses.clear();
173                this.defaultLocalAddresses.addAll(newLocalAddresses);
174            }
175        }
176    }
177
178    /**
179     * {@inheritDoc}
180     * @org.apache.xbean.Property nestedType="java.net.SocketAddress"
181     */
182    @Override
183    public final void setDefaultLocalAddresses(SocketAddress firstLocalAddress, SocketAddress... otherLocalAddresses) {
184        if (otherLocalAddresses == null) {
185            otherLocalAddresses = new SocketAddress[0];
186        }
187
188        Collection<SocketAddress> newLocalAddresses = new ArrayList<>(otherLocalAddresses.length + 1);
189
190        newLocalAddresses.add(firstLocalAddress);
191        for (SocketAddress a : otherLocalAddresses) {
192            newLocalAddresses.add(a);
193        }
194
195        setDefaultLocalAddresses(newLocalAddresses);
196    }
197
198    /**
199     * {@inheritDoc}
200     */
201    @Override
202    public final boolean isCloseOnDeactivation() {
203        return disconnectOnUnbind;
204    }
205
206    /**
207     * {@inheritDoc}
208     */
209    @Override
210    public final void setCloseOnDeactivation(boolean disconnectClientsOnUnbind) {
211        this.disconnectOnUnbind = disconnectClientsOnUnbind;
212    }
213
214    /**
215     * {@inheritDoc}
216     */
217    @Override
218    public final void bind() throws IOException {
219        bind(getDefaultLocalAddresses());
220    }
221
222    /**
223     * {@inheritDoc}
224     */
225    @Override
226    public final void bind(SocketAddress localAddress) throws IOException {
227        if (localAddress == null) {
228            throw new IllegalArgumentException("localAddress");
229        }
230
231        List<SocketAddress> localAddresses = new ArrayList<>(1);
232        localAddresses.add(localAddress);
233        bind(localAddresses);
234    }
235
236    /**
237     * {@inheritDoc}
238     */
239    @Override
240    public final void bind(SocketAddress... addresses) throws IOException {
241        if ((addresses == null) || (addresses.length == 0)) {
242            bind(getDefaultLocalAddresses());
243            return;
244        }
245
246        List<SocketAddress> localAddresses = new ArrayList<>(2);
247
248        for (SocketAddress address : addresses) {
249            localAddresses.add(address);
250        }
251
252        bind(localAddresses);
253    }
254
255    /**
256     * {@inheritDoc}
257     */
258    @Override
259    public final void bind(SocketAddress firstLocalAddress, SocketAddress... addresses) throws IOException {
260        if (firstLocalAddress == null) {
261            bind(getDefaultLocalAddresses());
262        }
263
264        if ((addresses == null) || (addresses.length == 0)) {
265            bind(getDefaultLocalAddresses());
266            return;
267        }
268
269        List<SocketAddress> localAddresses = new ArrayList<>(2);
270        localAddresses.add(firstLocalAddress);
271
272        for (SocketAddress address : addresses) {
273            localAddresses.add(address);
274        }
275
276        bind(localAddresses);
277    }
278
279    /**
280     * {@inheritDoc}
281     */
282    @Override
283public final void bind(Iterable<? extends SocketAddress> localAddresses) throws IOException {
284        if (isDisposing()) {
285            throw new IllegalStateException("The Accpetor disposed is being disposed.");
286        }
287
288        if (localAddresses == null) {
289            throw new IllegalArgumentException("localAddresses");
290        }
291
292        List<SocketAddress> localAddressesCopy = new ArrayList<>();
293
294        for (SocketAddress a : localAddresses) {
295            checkAddressType(a);
296            localAddressesCopy.add(a);
297        }
298
299        if (localAddressesCopy.isEmpty()) {
300            throw new IllegalArgumentException("localAddresses is empty.");
301        }
302
303        boolean activate = false;
304        synchronized (bindLock) {
305            synchronized (boundAddresses) {
306                if (boundAddresses.isEmpty()) {
307                    activate = true;
308                }
309            }
310
311            if (getHandler() == null) {
312                throw new IllegalStateException("handler is not set.");
313            }
314
315            try {
316                Set<SocketAddress> addresses = bindInternal(localAddressesCopy);
317
318                synchronized (boundAddresses) {
319                    boundAddresses.addAll(addresses);
320                }
321            } catch (IOException e) {
322                throw e;
323            } catch (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    public static class AcceptorOperationFuture extends ServiceOperationFuture {
466        private final List<SocketAddress> localAddresses;
467
468        public AcceptorOperationFuture(List<? extends SocketAddress> localAddresses) {
469            this.localAddresses = new ArrayList<>(localAddresses);
470        }
471
472        public final List<SocketAddress> getLocalAddresses() {
473            return Collections.unmodifiableList(localAddresses);
474        }
475
476        /**
477         * @see Object#toString()
478         */
479        public String toString() {
480            StringBuilder sb = new StringBuilder();
481
482            sb.append("Acceptor operation : ");
483
484            if (localAddresses != null) {
485                boolean isFirst = true;
486
487                for (SocketAddress address : localAddresses) {
488                    if (isFirst) {
489                        isFirst = false;
490                    } else {
491                        sb.append(", ");
492                    }
493
494                    sb.append(address);
495                }
496            }
497            return sb.toString();
498        }
499    }
500}