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.util.AbstractSet;
    23  import java.util.Iterator;
    24  import java.util.List;
    25  import java.util.Map;
    26  import java.util.Set;
    27  import java.util.concurrent.Executor;
    28  import java.util.concurrent.ExecutorService;
    29  import java.util.concurrent.Executors;
    30  import java.util.concurrent.TimeUnit;
    31  import java.util.concurrent.atomic.AtomicInteger;
    32
    33  import org.apache.mina.core.IoUtil;
    34  import org.apache.mina.core.filterchain.DefaultIoFilterChain;
    35  import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
    36  import org.apache.mina.core.filterchain.IoFilterChainBuilder;
    37  import org.apache.mina.core.future.ConnectFuture;
    38  import org.apache.mina.core.future.DefaultIoFuture;
    39  import org.apache.mina.core.future.IoFuture;
    40  import org.apache.mina.core.future.WriteFuture;
    41  import org.apache.mina.core.session.AbstractIoSession;
    42  import org.apache.mina.core.session.DefaultIoSessionDataStructureFactory;
    43  import org.apache.mina.core.session.IdleStatus;
    44  import org.apache.mina.core.session.IoSession;
    45  import org.apache.mina.core.session.IoSessionConfig;
    46  import org.apache.mina.core.session.IoSessionDataStructureFactory;
    47  import org.apache.mina.core.session.IoSessionInitializationException;
    48  import org.apache.mina.core.session.IoSessionInitializer;
    49  import org.apache.mina.util.ExceptionMonitor;
    50  import org.apache.mina.util.NamePreservingRunnable;
    51  import org.slf4j.Logger;
    52  import org.slf4j.LoggerFactory;
    53
    54  /**
    55   * Base implementation of {@link IoService}s.
    56   *
    57   * An instance of IoService contains an Executor which will handle the incoming
    58   * events.
    59   *
    60   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
    61   */
    62  public abstract class AbstractIoService implements IoService {
    63
    64      private static final Logger LOGGER = LoggerFactory.getLogger(AbstractIoService.class);
    65
    66      /**
    67       * The unique number identifying the Service. It's incremented
    68       * for each new IoService created.
    69       */
    70      private static final AtomicInteger id = new AtomicInteger();
    71
    72      /**
    73       * The thread name built from the IoService inherited
    74       * instance class name and the IoService Id
    75       **/
    76      private final String threadName;
    77
    78      /**
    79       * The associated executor, responsible for handling execution of I/O events.
    80       */
    81      private final Executor executor;
    82
    83      /**
    84       * A flag used to indicate that the local executor has been created
    85       * inside this instance, and not passed by a caller.
    86       *
    87       * If the executor is locally created, then it will be an instance
    88       * of the ThreadPoolExecutor class.
    89       */
    90      private final boolean createdExecutor;
    91
    92      /**
    93       * The IoHandler in charge of managing all the I/O Events. It is
    94       */
    95      private IoHandler handler;
    96
    97      /**
    98       * The default {@link IoSessionConfig} which will be used to configure new sessions.
    99       */
    100     private final IoSessionConfig sessionConfig;
    101
    102     private final IoServiceListener serviceActivationListener = new IoServiceListener() {
    103         public void serviceActivated(IoService service) {
    104             // Update lastIoTime.
    105             AbstractIoService s = (AbstractIoService) service;
    106             IoServiceStatistics _stats = s.getStatistics();
    107             _stats.setLastReadTime(s.getActivationTime());
    108             _stats.setLastWriteTime(s.getActivationTime());
    109             _stats.setLastThroughputCalculationTime(s.getActivationTime());
    110
    111         }
    112
    113         public void serviceDeactivated(IoService service) {
    114             // Empty handler
    115         }
    116
    117         public void serviceIdle(IoService service, IdleStatus idleStatus) {
    118             // Empty handler
    119         }
    120
    121         public void sessionCreated(IoSession session) {
    122             // Empty handler
    123         }
    124
    125         public void sessionDestroyed(IoSession session) {
    126             // Empty handler
    127         }
    128     };
    129
    130     /**
    131      * Current filter chain builder.
    132      */
    133     private IoFilterChainBuilder filterChainBuilder = new DefaultIoFilterChainBuilder();
    134
    135     private IoSessionDataStructureFactory sessionDataStructureFactory = new DefaultIoSessionDataStructureFactory();
    136
    137     /**
    138      * Maintains the {@link IoServiceListener}s of this service.
    139      */
    140     private final IoServiceListenerSupport listeners;
    141
    142     /**
    143      * A lock object which must be acquired when related resources are
    144      * destroyed.
    145      */
    146     protected final Object disposalLock = new Object();
    147
    148     private volatile boolean disposing;
    149
    150     private volatile boolean disposed;
    151
    152     /**
    153      * {@inheritDoc}
    154      */
    155     private IoServiceStatistics stats = new IoServiceStatistics(this);
    156
    157     /**
    158      * Constructor for {@link AbstractIoService}. You need to provide a default
    159      * session configuration and an {@link Executor} for handling I/O events. If
    160      * a null {@link Executor} is provided, a default one will be created using
    161      * {@link Executors#newCachedThreadPool()}.
    162      *
    163      * @param sessionConfig
    164      *            the default configuration for the managed {@link IoSession}
    165      * @param executor
    166      *            the {@link Executor} used for handling execution of I/O
    167      *            events. Can be <code>null</code>.
    168      */
    169     protected AbstractIoService(IoSessionConfig sessionConfig, Executor executor) {
    170         if (sessionConfig == null) {
    171             throw new IllegalArgumentException("sessionConfig");
    172         }
    173
    174         if (getTransportMetadata() == null) {
    175             throw new IllegalArgumentException("TransportMetadata");
    176         }
    177
    178         if (!getTransportMetadata().getSessionConfigType().isAssignableFrom(sessionConfig.getClass())) {
    179             throw new IllegalArgumentException("sessionConfig type: " + sessionConfig.getClass() + " (expected: "
    180                     + getTransportMetadata().getSessionConfigType() + ")");
    181         }
    182
    183         // Create the listeners, and add a first listener : a activation listener
    184         // for this service, which will give information on the service state.
    185         listeners = new IoServiceListenerSupport(this);
    186         listeners.add(serviceActivationListener);
    187
    188         // Stores the given session configuration
    189         this.sessionConfig = sessionConfig;
    190
    191         // Make JVM load the exception monitor before some transports
    192         // change the thread context class loader.
    193         ExceptionMonitor.getInstance();
    194
    195         if (executor == null) {
    196             this.executor = Executors.newCachedThreadPool();
    197             createdExecutor = true;
    198         } else {
    199             this.executor = executor;
    200             createdExecutor = false;
    201         }
    202
    203         threadName = getClass().getSimpleName() + '-' + id.incrementAndGet();
    204     }
    205
    206     /**
    207      * {@inheritDoc}
    208      */
    209     public final IoFilterChainBuilder getFilterChainBuilder() {
    210         return filterChainBuilder;
    211     }
    212
    213     /**
    214      * {@inheritDoc}
    215      */
    216     public final void setFilterChainBuilder(IoFilterChainBuilder builder) {
    217         if (builder == null) {
    218             builder = new DefaultIoFilterChainBuilder();
    219         }
    220         filterChainBuilder = builder;
    221     }
    222
    223     /**
    224      * {@inheritDoc}
    225      */
    226     public final DefaultIoFilterChainBuilder getFilterChain() {
    227         if (filterChainBuilder instanceof DefaultIoFilterChainBuilder) {
    228             return (DefaultIoFilterChainBuilder) filterChainBuilder;
    229         }
    230
    231         throw new IllegalStateException("Current filter chain builder is not a DefaultIoFilterChainBuilder.");
    232     }
    233
    234     /**
    235      * {@inheritDoc}
    236      */
    237     public final void addListener(IoServiceListener listener) {
    238         listeners.add(listener);
    239     }
    240
    241     /**
    242      * {@inheritDoc}
    243      */
    244     public final void removeListener(IoServiceListener listener) {
    245         listeners.remove(listener);
    246     }
    247
    248     /**
    249      * {@inheritDoc}
    250      */
    251     public final boolean isActive() {
    252         return listeners.isActive();
    253     }
    254
    255     /**
    256      * {@inheritDoc}
    257      */
    258     public final boolean isDisposing() {
    259         return disposing;
    260     }
    261
    262     /**
    263      * {@inheritDoc}
    264      */
    265     public final boolean isDisposed() {
    266         return disposed;
    267     }
    268
    269     /**
    270      * {@inheritDoc}
    271      */
    272     public final void dispose() {
    273         dispose(false);
    274     }
    275
    276     /**
    277      * {@inheritDoc}
    278      */
    279     public final void dispose(boolean awaitTermination) {
    280         if (disposed) {
    281             return;
    282         }
    283
    284         synchronized (disposalLock) {
    285             if (!disposing) {
    286                 disposing = true;
    287
    288                 try {
    289                     dispose0();
    290                 } catch (Exception e) {
    291                     ExceptionMonitor.getInstance().exceptionCaught(e);
    292                 }
    293             }
    294         }
    295
    296         if (createdExecutor) {
    297             ExecutorService e = (ExecutorService) executor;
    298             e.shutdownNow();
    299             if (awaitTermination) {
    300
    301                 //Thread.currentThread().setName();
    302
    303                 try {
    304                     LOGGER.debug("awaitTermination on {} called by thread=[{}]", this, Thread.currentThread().getName());
    305                     e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
    306                     LOGGER.debug("awaitTermination on {} finished", this);
    307                 } catch (InterruptedException e1) {
    308                     LOGGER.warn("awaitTermination on [{}] was interrupted", this);
    309                     // Restore the interrupted status
    310                     Thread.currentThread().interrupt();
    311                 }
    312             }
    313         }
    314         disposed = true;
    315     }
    316
    317     /**
    318      * Implement this method to release any acquired resources.  This method
    319      * is invoked only once by {@link #dispose()}.
    320      */
    321     protected abstract void dispose0() throws Exception;
    322
    323     /**
    324      * {@inheritDoc}
    325      */
    326     public final Map<Long, IoSession> getManagedSessions() {
    327         return listeners.getManagedSessions();
    328     }
    329
    330     /**
    331      * {@inheritDoc}
    332      */
    333     public final int getManagedSessionCount() {
    334         return listeners.getManagedSessionCount();
    335     }
    336
    337     /**
    338      * {@inheritDoc}
    339      */
    340     public final IoHandler getHandler() {
    341         return handler;
    342     }
    343
    344     /**
    345      * {@inheritDoc}
    346      */
    347     public final void setHandler(IoHandler handler) {
    348         if (handler == null) {
    349             throw new IllegalArgumentException("handler cannot be null");
    350         }
    351
    352         if (isActive()) {
    353             throw new IllegalStateException("handler cannot be set while the service is active.");
    354         }
    355
    356         this.handler = handler;
    357     }
    358
    359     /**
    360      * {@inheritDoc}
    361      */
    362     public IoSessionConfig getSessionConfig() {
    363         return sessionConfig;
    364     }
    365
    366     /**
    367      * {@inheritDoc}
    368      */
    369     public final IoSessionDataStructureFactory getSessionDataStructureFactory() {
    370         return sessionDataStructureFactory;
    371     }
    372
    373     /**
    374      * {@inheritDoc}
    375      */
    376     public final void setSessionDataStructureFactory(IoSessionDataStructureFactory sessionDataStructureFactory) {
    377         if (sessionDataStructureFactory == null) {
    378             throw new IllegalArgumentException("sessionDataStructureFactory");
    379         }
    380
    381         if (isActive()) {
    382             throw new IllegalStateException("sessionDataStructureFactory cannot be set while the service is active.");
    383         }
    384
    385         this.sessionDataStructureFactory = sessionDataStructureFactory;
    386     }
    387
    388     /**
    389      * {@inheritDoc}
    390      */
    391     public IoServiceStatistics getStatistics() {
    392         return stats;
    393     }
    394
    395     /**
    396      * {@inheritDoc}
    397      */
    398     public final long getActivationTime() {
    399         return listeners.getActivationTime();
    400     }
    401
    402     /**
    403      * {@inheritDoc}
    404      */
    405     public final Set<WriteFuture> broadcast(Object message) {
    406         // Convert to Set.  We do not return a List here because only the
    407         // direct caller of MessageBroadcaster knows the order of write
    408         // operations.
    409         final List<WriteFuture> futures = IoUtil.broadcast(message, getManagedSessions().values());
    410         return new AbstractSet<WriteFuture>() {
    411             @Override
    412             public Iterator<WriteFuture> iterator() {
    413                 return futures.iterator();
    414             }
    415
    416             @Override
    417             public int size() {
    418                 return futures.size();
    419             }
    420         };
    421     }
    422
    423     public final IoServiceListenerSupport getListeners() {
    424         return listeners;
    425     }
    426
    427     protected final void executeWorker(Runnable worker) {
    428         executeWorker(worker, null);
    429     }
    430
    431     protected final void executeWorker(Runnable worker, String suffix) {
    432         String actualThreadName = threadName;
    433         if (suffix != null) {
    434             actualThreadName = actualThreadName + '-' + suffix;
    435         }
    436         executor.execute(new NamePreservingRunnable(worker, actualThreadName));
    437     }
    438
    439     // TODO Figure out make it work without causing a compiler error / warning.
    440     @SuppressWarnings("unchecked")
    441     protected final void initSession(IoSession session, IoFuture future, IoSessionInitializer sessionInitializer) {
    442         // Update lastIoTime if needed.
    443         if (stats.getLastReadTime() == 0) {
    444             stats.setLastReadTime(getActivationTime());
    445         }
    446
    447         if (stats.getLastWriteTime() == 0) {
    448             stats.setLastWriteTime(getActivationTime());
    449         }
    450
    451         // Every property but attributeMap should be set now.
    452         // Now initialize the attributeMap.  The reason why we initialize
    453         // the attributeMap at last is to make sure all session properties
    454         // such as remoteAddress are provided to IoSessionDataStructureFactory.
    455         try {
    456             ((AbstractIoSession) session).setAttributeMap(session.getService().getSessionDataStructureFactory()
    457                     .getAttributeMap(session));
    458         } catch (IoSessionInitializationException e) {
    459             throw e;
    460         } catch (Exception e) {
    461             throw new IoSessionInitializationException("Failed to initialize an attributeMap.", e);
    462         }
    463
    464         try {
    465             ((AbstractIoSession) session).setWriteRequestQueue(session.getService().getSessionDataStructureFactory()
    466                     .getWriteRequestQueue(session));
    467         } catch (IoSessionInitializationException e) {
    468             throw e;
    469         } catch (Exception e) {
    470             throw new IoSessionInitializationException("Failed to initialize a writeRequestQueue.", e);
    471         }
    472
    473         if ((future != null) && (future instanceof ConnectFuture)) {
    474             // DefaultIoFilterChain will notify the future. (We support ConnectFuture only for now).
    475             session.setAttribute(DefaultIoFilterChain.SESSION_CREATED_FUTURE, future);
    476         }
    477
    478         if (sessionInitializer != null) {
    479             sessionInitializer.initializeSession(session, future);
    480         }
    481
    482         finishSessionInitialization0(session, future);
    483     }
    484
    485     /**
    486      * Implement this method to perform additional tasks required for session
    487      * initialization. Do not call this method directly;
    488      * {@link #initSession(IoSession, IoFuture, IoSessionInitializer)} will call
    489      * this method instead.
    490      */
    491     protected void finishSessionInitialization0(IoSession session, IoFuture future) {
    492         // Do nothing. Extended class might add some specific code
    493     }
    494
    495     protected static class ServiceOperationFuture extends DefaultIoFuture {
    496         public ServiceOperationFuture() {
    497             super(null);
    498         }
    499
    500         public final boolean isDone() {
    501             return getValue() == Boolean.TRUE;
    502         }
    503
    504         public final void setDone() {
    505             setValue(Boolean.TRUE);
    506         }
    507
    508         public final Exception getException() {
    509             if (getValue() instanceof Exception) {
    510                 return (Exception) getValue();
    511             }
    512
    513             return null;
    514         }
    515
    516         public final void setException(Exception exception) {
    517             if (exception == null) {
    518                 throw new IllegalArgumentException("exception");
    519             }
    520             setValue(exception);
    521         }
    522     }
    523
    524     /**
    525      * {@inheritDoc}
    526      */
    527     public int getScheduledWriteBytes() {
    528         return stats.getScheduledWriteBytes();
    529     }
    530
    531     /**
    532      * {@inheritDoc}
    533      */
    534     public int getScheduledWriteMessages() {
    535         return stats.getScheduledWriteMessages();
    536     }
    537
    538 }