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<SocketAddress>();
046
047    private final List<SocketAddress> unmodifiableDefaultLocalAddresses = Collections
048            .unmodifiableList(defaultLocalAddresses);
049
050    private final Set<SocketAddress> boundAddresses = new HashSet<SocketAddress>();
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    public SocketAddress getLocalAddress() {
084        Set<SocketAddress> localAddresses = getLocalAddresses();
085        if (localAddresses.isEmpty()) {
086            return null;
087        }
088
089        return localAddresses.iterator().next();
090    }
091
092    /**
093     * {@inheritDoc}
094     */
095    public final Set<SocketAddress> getLocalAddresses() {
096        Set<SocketAddress> localAddresses = new HashSet<SocketAddress>();
097
098        synchronized (boundAddresses) {
099            localAddresses.addAll(boundAddresses);
100        }
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 IllegalArgumentException("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 IllegalArgumentException("localAddresses");
146        }
147
148        synchronized (bindLock) {
149            synchronized (boundAddresses) {
150                if (!boundAddresses.isEmpty()) {
151                    throw new IllegalStateException("localAddress can't be set while the acceptor is bound.");
152                }
153
154                Collection<SocketAddress> newLocalAddresses = new ArrayList<SocketAddress>();
155
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    /**
172     * {@inheritDoc}
173     * @org.apache.xbean.Property nestedType="java.net.SocketAddress"
174     */
175    public final void setDefaultLocalAddresses(SocketAddress firstLocalAddress, SocketAddress... otherLocalAddresses) {
176        if (otherLocalAddresses == null) {
177            otherLocalAddresses = new SocketAddress[0];
178        }
179
180        Collection<SocketAddress> newLocalAddresses = 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 IllegalArgumentException("localAddress");
217        }
218
219        List<SocketAddress> localAddresses = new ArrayList<SocketAddress>(1);
220        localAddresses.add(localAddress);
221        bind(localAddresses);
222    }
223
224    /**
225     * {@inheritDoc}
226     */
227    public final void bind(SocketAddress... addresses) throws IOException {
228        if ((addresses == null) || (addresses.length == 0)) {
229            bind(getDefaultLocalAddresses());
230            return;
231        }
232
233        List<SocketAddress> localAddresses = new ArrayList<SocketAddress>(2);
234
235        for (SocketAddress address : addresses) {
236            localAddresses.add(address);
237        }
238
239        bind(localAddresses);
240    }
241
242    /**
243     * {@inheritDoc}
244     */
245    public final void bind(SocketAddress firstLocalAddress, SocketAddress... addresses) throws IOException {
246        if (firstLocalAddress == null) {
247            bind(getDefaultLocalAddresses());
248        }
249
250        if ((addresses == null) || (addresses.length == 0)) {
251            bind(getDefaultLocalAddresses());
252            return;
253        }
254
255        List<SocketAddress> localAddresses = new ArrayList<SocketAddress>(2);
256        localAddresses.add(firstLocalAddress);
257
258        for (SocketAddress address : addresses) {
259            localAddresses.add(address);
260        }
261
262        bind(localAddresses);
263    }
264
265    /**
266     * {@inheritDoc}
267     */
268    public final void bind(Iterable<? extends SocketAddress> localAddresses) throws IOException {
269        if (isDisposing()) {
270            throw new IllegalStateException("The Accpetor disposed is being disposed.");
271        }
272
273        if (localAddresses == null) {
274            throw new IllegalArgumentException("localAddresses");
275        }
276
277        List<SocketAddress> localAddressesCopy = new ArrayList<SocketAddress>();
278
279        for (SocketAddress a : localAddresses) {
280            checkAddressType(a);
281            localAddressesCopy.add(a);
282        }
283
284        if (localAddressesCopy.isEmpty()) {
285            throw new IllegalArgumentException("localAddresses is empty.");
286        }
287
288        boolean activate = false;
289        synchronized (bindLock) {
290            synchronized (boundAddresses) {
291                if (boundAddresses.isEmpty()) {
292                    activate = true;
293                }
294            }
295
296            if (getHandler() == null) {
297                throw new IllegalStateException("handler is not set.");
298            }
299
300            try {
301                Set<SocketAddress> addresses = bindInternal(localAddressesCopy);
302
303                synchronized (boundAddresses) {
304                    boundAddresses.addAll(addresses);
305                }
306            } catch (IOException e) {
307                throw e;
308            } catch (RuntimeException e) {
309                throw e;
310            } catch (Exception e) {
311                throw new RuntimeIoException("Failed to bind to: " + getLocalAddresses(), e);
312            }
313        }
314
315        if (activate) {
316            getListeners().fireServiceActivated();
317        }
318    }
319
320    /**
321     * {@inheritDoc}
322     */
323    public final void unbind() {
324        unbind(getLocalAddresses());
325    }
326
327    /**
328     * {@inheritDoc}
329     */
330    public final void unbind(SocketAddress localAddress) {
331        if (localAddress == null) {
332            throw new IllegalArgumentException("localAddress");
333        }
334
335        List<SocketAddress> localAddresses = new ArrayList<SocketAddress>(1);
336        localAddresses.add(localAddress);
337        unbind(localAddresses);
338    }
339
340    /**
341     * {@inheritDoc}
342     */
343    public final void unbind(SocketAddress firstLocalAddress, SocketAddress... otherLocalAddresses) {
344        if (firstLocalAddress == null) {
345            throw new IllegalArgumentException("firstLocalAddress");
346        }
347        if (otherLocalAddresses == null) {
348            throw new IllegalArgumentException("otherLocalAddresses");
349        }
350
351        List<SocketAddress> localAddresses = new ArrayList<SocketAddress>();
352        localAddresses.add(firstLocalAddress);
353        Collections.addAll(localAddresses, otherLocalAddresses);
354        unbind(localAddresses);
355    }
356
357    /**
358     * {@inheritDoc}
359     */
360    public final void unbind(Iterable<? extends SocketAddress> localAddresses) {
361        if (localAddresses == null) {
362            throw new IllegalArgumentException("localAddresses");
363        }
364
365        boolean deactivate = false;
366        synchronized (bindLock) {
367            synchronized (boundAddresses) {
368                if (boundAddresses.isEmpty()) {
369                    return;
370                }
371
372                List<SocketAddress> localAddressesCopy = new ArrayList<SocketAddress>();
373                int specifiedAddressCount = 0;
374
375                for (SocketAddress a : localAddresses) {
376                    specifiedAddressCount++;
377
378                    if ((a != null) && boundAddresses.contains(a)) {
379                        localAddressesCopy.add(a);
380                    }
381                }
382
383                if (specifiedAddressCount == 0) {
384                    throw new IllegalArgumentException("localAddresses is empty.");
385                }
386
387                if (!localAddressesCopy.isEmpty()) {
388                    try {
389                        unbind0(localAddressesCopy);
390                    } catch (RuntimeException e) {
391                        throw e;
392                    } catch (Exception e) {
393                        throw new RuntimeIoException("Failed to unbind from: " + getLocalAddresses(), e);
394                    }
395
396                    boundAddresses.removeAll(localAddressesCopy);
397
398                    if (boundAddresses.isEmpty()) {
399                        deactivate = true;
400                    }
401                }
402            }
403        }
404
405        if (deactivate) {
406            getListeners().fireServiceDeactivated();
407        }
408    }
409
410    /**
411     * Starts the acceptor, and register the given addresses
412     * 
413     * @param localAddresses The address to bind to
414     * @return the {@link Set} of the local addresses which is bound actually
415     * @throws Exception If the bind failed
416     */
417    protected abstract Set<SocketAddress> bindInternal(List<? extends SocketAddress> localAddresses) throws Exception;
418
419    /**
420     * Implement this method to perform the actual unbind operation.
421     * 
422     * @param localAddresses The address to unbind from
423     * @throws Exception If the unbind failed
424     */
425    protected abstract void unbind0(List<? extends SocketAddress> localAddresses) throws Exception;
426
427    @Override
428    public String toString() {
429        TransportMetadata m = getTransportMetadata();
430        return '('
431                + m.getProviderName()
432                + ' '
433                + m.getName()
434                + " acceptor: "
435                + (isActive() ? "localAddress(es): " + getLocalAddresses() + ", managedSessionCount: "
436                        + getManagedSessionCount() : "not bound") + ')';
437    }
438
439    private void checkAddressType(SocketAddress a) {
440        if (a != null && !getTransportMetadata().getAddressType().isAssignableFrom(a.getClass())) {
441            throw new IllegalArgumentException("localAddress type: " + a.getClass().getSimpleName() + " (expected: "
442                    + getTransportMetadata().getAddressType().getSimpleName() + ")");
443        }
444    }
445
446    public static class AcceptorOperationFuture extends ServiceOperationFuture {
447        private final List<SocketAddress> localAddresses;
448
449        public AcceptorOperationFuture(List<? extends SocketAddress> localAddresses) {
450            this.localAddresses = new ArrayList<SocketAddress>(localAddresses);
451        }
452
453        public final List<SocketAddress> getLocalAddresses() {
454            return Collections.unmodifiableList(localAddresses);
455        }
456
457        /**
458         * @see Object#toString()
459         */
460        public String toString() {
461            StringBuilder sb = new StringBuilder();
462
463            sb.append("Acceptor operation : ");
464
465            if (localAddresses != null) {
466                boolean isFirst = true;
467
468                for (SocketAddress address : localAddresses) {
469                    if (isFirst) {
470                        isFirst = false;
471                    } else {
472                        sb.append(", ");
473                    }
474
475                    sb.append(address);
476                }
477            }
478            return sb.toString();
479        }
480    }
481}