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.session;
    21
    22  import java.nio.ByteBuffer;
    23  import java.util.Collections;
    24  import java.util.Set;
    25  import java.util.concurrent.atomic.AtomicInteger;
    26  import java.util.concurrent.locks.Lock;
    27  import java.util.concurrent.locks.ReadWriteLock;
    28  import java.util.concurrent.locks.ReentrantReadWriteLock;
    29
    30  import javax.net.ssl.SSLContext;
    31
    32  import org.apache.mina.api.IdleStatus;
    33  import org.apache.mina.api.IoFilter;
    34  import org.apache.mina.api.IoFuture;
    35  import org.apache.mina.api.IoHandler;
    36  import org.apache.mina.api.IoServer;
    37  import org.apache.mina.api.IoService;
    38  import org.apache.mina.api.IoSession;
    39  import org.apache.mina.api.IoSessionConfig;
    40  import org.apache.mina.filterchain.ReadFilterChainController;
    41  import org.apache.mina.filterchain.WriteFilterChainController;
    42  import org.apache.mina.service.executor.CloseEvent;
    43  import org.apache.mina.service.executor.IdleEvent;
    44  import org.apache.mina.service.executor.IoHandlerExecutor;
    45  import org.apache.mina.service.executor.OpenEvent;
    46  import org.apache.mina.service.executor.ReceiveEvent;
    47  import org.apache.mina.service.executor.SentEvent;
    48  import org.apache.mina.service.idlechecker.IdleChecker;
    49  import org.apache.mina.transport.nio.SelectorLoop;
    50  import org.apache.mina.transport.nio.SslHelper;
    51  import org.slf4j.Logger;
    52  import org.slf4j.LoggerFactory;
    53
    54  /**
    55   * Base implementation of {@link IoSession} shared with all the different transports.
    56   *
    57   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
    58   */
    59  public abstract class AbstractIoSession implements IoSession, ReadFilterChainController, WriteFilterChainController {
    60      /** The logger for this class */
    61      private static final Logger LOG = LoggerFactory.getLogger(AbstractIoSession.class);
    62
    63      // A speedup for logs
    64      private static final boolean IS_DEBUG = LOG.isDebugEnabled();
    65
    66      /** unique identifier generator */
    67      private static final AtomicInteger NEXT_ID = new AtomicInteger(0);
    68
    69      /** The session's unique identifier */
    70      private final long id;
    71
    72      /** The session's creation time */
    73      private final long creationTime;
    74
    75      /** The service this session is associated with */
    76      private final IoService service;
    77
    78      /** attributes map */
    79      private final AttributeContainer attributes = new DefaultAttributeContainer();
    80
    81      /** the {@link IdleChecker} in charge of detecting idle event for this session */
    82      protected final IdleChecker idleChecker;
    83
    84      /** The session config */
    85      protected IoSessionConfig config;
    86
    87      // ------------------------------------------------------------------------
    88      // Basic statistics
    89      // ------------------------------------------------------------------------
    90
    91      /** The number of bytes read since this session has been created */
    92      private volatile long readBytes;
    93
    94      /** The number of bytes written since this session has been created */
    95      private volatile long writtenBytes;
    96
    97      /** Last time something was read for this session */
    98      private volatile long lastReadTime;
    99
    100     /** Last time something was written for this session */
    101     private volatile long lastWriteTime;
    102
    103     // ------------------------------------------------------------------------
    104     // Session state
    105     // ------------------------------------------------------------------------
    106
    107     /** The session's state : one of CREATED, CONNECTED, CLOSING, CLOSED, SECURING, CONNECTED_SECURED */
    108     protected volatile SessionState state;
    109
    110     /** A lock to protect the access to the session's state */
    111     private final ReadWriteLock stateLock = new ReentrantReadWriteLock();
    112
    113     /** A Read lock on the reentrant session's state lock */
    114     private final Lock stateReadLock = stateLock.readLock();
    115
    116     /** A Write lock on the reentrant session's state lock */
    117     private final Lock stateWriteLock = stateLock.writeLock();
    118
    119     /** Tells if the session is secured or not */
    120     protected volatile boolean secured;
    121
    122     // ------------------------------------------------------------------------
    123     // Filter chain
    124     // ------------------------------------------------------------------------
    125
    126     /** The list of {@link IoFilter} implementing this chain. */
    127     private final IoFilter[] chain;
    128
    129     /** the current position in the write chain for this thread */
    130     private int writeChainPosition;
    131
    132     /** the current position in the read chain for this thread */
    133     private int readChainPosition;
    134
    135     /**
    136      * Create an {@link org.apache.mina.api.IoSession} with a unique identifier (
    137      * {@link org.apache.mina.api.IoSession#getId()}) and an associated {@link IoService}
    138      *
    139      * @param service the service this session is associated with
    140      * @param idleChecker the checker for idle session
    141      */
    142     public AbstractIoSession(IoService service, IdleChecker idleChecker) {
    143         // generated a unique id
    144         id = NEXT_ID.getAndIncrement();
    145         creationTime = System.currentTimeMillis();
    146         this.service = service;
    147         this.chain = service.getFilters();
    148         this.idleChecker = idleChecker;
    149         this.config = service.getSessionConfig();
    150
    151         if (IS_DEBUG) {
    152             LOG.debug("Created new session with id : {}", id);
    153         }
    154
    155         this.state = SessionState.CREATED;
    156         service.getManagedSessions().put(id, this);
    157     }
    158
    159     // ------------------------------------------------------------------------
    160     // Session State management
    161     // ------------------------------------------------------------------------
    162     /**
    163      * {@inheritDoc}
    164      */
    165     @Override
    166     public boolean isClosed() {
    167         try {
    168             stateReadLock.lock();
    169
    170             return state == SessionState.CLOSED;
    171         } finally {
    172             stateReadLock.unlock();
    173         }
    174     }
    175
    176     /**
    177      * {@inheritDoc}
    178      */
    179     @Override
    180     public boolean isClosing() {
    181         try {
    182             stateReadLock.lock();
    183
    184             return state == SessionState.CLOSING;
    185         } finally {
    186             stateReadLock.unlock();
    187         }
    188     }
    189
    190     /**
    191      * {@inheritDoc}
    192      */
    193     @Override
    194     public boolean isConnected() {
    195         try {
    196             stateReadLock.lock();
    197
    198             return state == SessionState.CONNECTED;
    199         } finally {
    200             stateReadLock.unlock();
    201         }
    202     }
    203
    204     /**
    205      * {@inheritDoc}
    206      */
    207     @Override
    208     public boolean isCreated() {
    209         try {
    210             stateReadLock.lock();
    211
    212             return state == SessionState.CREATED;
    213         } finally {
    214             stateReadLock.unlock();
    215         }
    216     }
    217
    218     /**
    219      * {@inheritDoc}
    220      */
    221     @Override
    222     public boolean isSecuring() {
    223         try {
    224             stateReadLock.lock();
    225
    226             return state == SessionState.SECURING;
    227         } finally {
    228             stateReadLock.unlock();
    229         }
    230     }
    231
    232     /**
    233      * {@inheritDoc}
    234      */
    235     @Override
    236     public boolean isConnectedSecured() {
    237         try {
    238             stateReadLock.lock();
    239
    240             return state == SessionState.SECURED;
    241         } finally {
    242             stateReadLock.unlock();
    243         }
    244     }
    245
    246     /**
    247      * {@inheritDoc}
    248      */
    249     @Override
    250     public void changeState(SessionState to) {
    251         try {
    252             stateWriteLock.lock();
    253
    254             switch (state) {
    255             case CREATED:
    256                 switch (to) {
    257                 case CONNECTED:
    258                 case SECURING:
    259                 case CLOSING:
    260                     state = to;
    261                     break;
    262
    263                 default:
    264                     throw new IllegalStateException("Cannot transit from " + state + " to " + to);
    265                 }
    266
    267                 break;
    268
    269             case CONNECTED:
    270                 switch (to) {
    271                 case SECURING:
    272                 case CLOSING:
    273                     state = to;
    274                     break;
    275
    276                 default:
    277                     throw new IllegalStateException("Cannot transit from " + state + " to " + to);
    278                 }
    279
    280                 break;
    281
    282             case SECURING:
    283                 switch (to) {
    284                 case SECURED:
    285                 case CLOSING:
    286                     state = to;
    287                     break;
    288
    289                 default:
    290                     throw new IllegalStateException("Cannot transit from " + state + " to " + to);
    291                 }
    292
    293                 break;
    294
    295             case SECURED:
    296                 switch (to) {
    297                 case CONNECTED:
    298                 case SECURING:
    299                 case CLOSING:
    300                     state = to;
    301                     break;
    302
    303                 default:
    304                     throw new IllegalStateException("Cannot transit from " + state + " to " + to);
    305                 }
    306
    307                 break;
    308             case CLOSING:
    309                 if (to != SessionState.CLOSED) {
    310                     throw new IllegalStateException("Cannot transit from " + state + " to " + to);
    311                 }
    312
    313                 state = to;
    314
    315                 break;
    316
    317             case CLOSED:
    318                 throw new IllegalStateException("The session is already closed. cannot switch to " + to);
    319             }
    320         } finally {
    321             stateWriteLock.unlock();
    322         }
    323     }
    324
    325     // ------------------------------------------------------------------------
    326     // SSL/TLS session state management
    327     // ------------------------------------------------------------------------
    328     /**
    329      * {@inheritDoc}
    330      */
    331     @Override
    332     public boolean isSecured() {
    333         return secured;
    334     }
    335
    336     /**
    337      * {@inheritDoc}
    338      */
    339     public void setSecured(boolean secured) {
    340         this.secured = secured;
    341     }
    342
    343     /**
    344      * {@inheritDoc}
    345      */
    346     @Override
    347     public void initSecure(SSLContext sslContext) {
    348         SslHelper sslHelper = new SslHelper(this, sslContext);
    349         sslHelper.init();
    350
    351         attributes.setAttribute(SSL_HELPER, sslHelper);
    352         setSecured(true);
    353     }
    354
    355     /**
    356      * {@inheritDoc}
    357      */
    358     @Override
    359     public long getId() {
    360         return id;
    361     }
    362
    363     /**
    364      * {@inheritDoc}
    365      */
    366     @Override
    367     public long getCreationTime() {
    368         return creationTime;
    369     }
    370
    371     /**
    372      * {@inheritDoc}
    373      */
    374     @Override
    375     public long getReadBytes() {
    376         return readBytes;
    377     }
    378
    379     /**
    380      * To be called by the internal plumber when some bytes are written on the socket
    381      *
    382      * @param bytesCount number of extra bytes written
    383      */
    384     public void incrementWrittenBytes(int bytesCount) {
    385         writtenBytes += bytesCount;
    386     }
    387
    388     /**
    389      * {@inheritDoc}
    390      */
    391     @Override
    392     public long getWrittenBytes() {
    393         return writtenBytes;
    394     }
    395
    396     /**
    397      * {@inheritDoc}
    398      */
    399     @Override
    400     public long getLastReadTime() {
    401         return lastReadTime;
    402     }
    403
    404     /**
    405      * {@inheritDoc}
    406      */
    407     @Override
    408     public long getLastWriteTime() {
    409         return lastWriteTime;
    410     }
    411
    412     /**
    413      * {@inheritDoc}
    414      */
    415     @Override
    416     public final long getLastIoTime() {
    417         return Math.max(lastReadTime, lastWriteTime);
    418     }
    419
    420     /**
    421      * {@inheritDoc}
    422      */
    423     @Override
    424     public IoService getService() {
    425         return service;
    426     }
    427
    428     /**
    429      * {@inheritDoc}
    430      *
    431      * @exception IllegalArgumentException if <code>key==null</code>
    432      * @see #setAttribute(AttributeKey, Object)
    433      */
    434     @Override
    435     public final <T> T getAttribute(AttributeKey<T> key, T defaultValue) {
    436         return attributes.getAttribute(key, defaultValue);
    437     }
    438
    439     /**
    440      * {@inheritDoc}
    441      *
    442      * @exception IllegalArgumentException if <code>key==null</code>
    443      * @see #setAttribute(AttributeKey, Object)
    444      */
    445     @Override
    446     public final <T> T getAttribute(AttributeKey<T> key) {
    447         return attributes.getAttribute(key);
    448     }
    449
    450     /**
    451      * {@inheritDoc}
    452      *
    453      * @exception IllegalArgumentException <ul>
    454      *            <li>
    455      *            if <code>key==null</code></li>
    456      *            <li>
    457      *            if <code>value</code> is not <code>null</code> and not an instance of type that is specified in by the
    458      *            given <code>key</code> (see {@link AttributeKey#getType()})</li>
    459      *            </ul>
    460      *
    461      * @see #getAttribute(AttributeKey)
    462      */
    463     @Override
    464     public final <T> T setAttribute(AttributeKey<? extends T> key, T value) {
    465         return attributes.setAttribute(key, value);
    466     };
    467
    468     /**
    469      * {@inheritDoc}
    470      *
    471      * @see Collections#unmodifiableSet(Set)
    472      */
    473     @Override
    474     public Set<AttributeKey<?>> getAttributeKeys() {
    475         return attributes.getAttributeKeys();
    476     }
    477
    478     /**
    479      * {@inheritDoc}
    480      *
    481      * @exception IllegalArgumentException if <code>key==null</code>
    482      */
    483     @Override
    484     public <T> T removeAttribute(AttributeKey<T> key) {
    485         return attributes.removeAttribute(key);
    486     }
    487
    488     // ----------------------------------------------------
    489     // Write management
    490     // ----------------------------------------------------
    491
    492     /**
    493      * {@inheritDoc}
    494      */
    495     @Override
    496     public void write(Object message) {
    497         doWriteWithFuture(message, null);
    498     }
    499
    500     /**
    501      * {@inheritDoc}
    502      */
    503     @Override
    504     public IoFuture<Void> writeWithFuture(Object message) {
    505         IoFuture<Void> future = new DefaultWriteFuture();
    506         doWriteWithFuture(message, future);
    507
    508         return future;
    509     }
    510
    511     private void doWriteWithFuture(Object message, IoFuture<Void> future) {
    512         if (IS_DEBUG) {
    513             LOG.debug("writing message {} to session {}", message, this);
    514         }
    515
    516         if ((state == SessionState.CLOSED) || (state == SessionState.CLOSING)) {
    517             LOG.error("writing to closed or closing session, the message is discarded");
    518             return;
    519         }
    520
    521         WriteRequest writeRequest = new DefaultWriteRequest(message);
    522
    523         // process the queue
    524         processMessageWriting(writeRequest, future);
    525     }
    526
    527     // ------------------------------------------------------------------------
    528     // Event processing using the filter chain
    529     // ------------------------------------------------------------------------
    530
    531     /** send a caught exception to the {@link IoHandler} (if any) */
    532     protected void processException(Exception t) {
    533         if (IS_DEBUG) {
    534             LOG.debug("caught session exception ", t);
    535         }
    536
    537         IoHandler handler = getService().getIoHandler();
    538
    539         if (handler != null) {
    540             handler.exceptionCaught(this, t);
    541         }
    542     }
    543
    544     /**
    545      * process session open event using the filter chain. To be called by the session {@link SelectorLoop} .
    546      */
    547     public void processSessionOpen() {
    548         if (IS_DEBUG) {
    549             LOG.debug("processing session open event");
    550         }
    551
    552         try {
    553
    554             for (IoFilter filter : chain) {
    555                 filter.sessionOpened(this);
    556             }
    557
    558             IoHandler handler = getService().getIoHandler();
    559
    560             if (handler != null) {
    561                 IoHandlerExecutor executor = getService().getIoHandlerExecutor();
    562
    563                 if (executor != null) {
    564                     // asynchronous event
    565                     executor.execute(new OpenEvent(this));
    566                 } else {
    567                     // synchronous call (in the I/O loop)
    568                     handler.sessionOpened(this);
    569                 }
    570             }
    571         } catch (RuntimeException e) {
    572             processException(e);
    573         }
    574     }
    575
    576     /**
    577      * process session closed event using the filter chain. To be called by the session {@link SelectorLoop} .
    578      */
    579     public void processSessionClosed() {
    580         if (IS_DEBUG) {
    581             LOG.debug("processing session closed event");
    582         }
    583
    584         try {
    585             for (IoFilter filter : chain) {
    586                 filter.sessionClosed(this);
    587             }
    588
    589             IoHandler handler = getService().getIoHandler();
    590
    591             if (handler != null) {
    592                 IoHandlerExecutor executor = getService().getIoHandlerExecutor();
    593                 if (executor != null) {
    594                     // asynchronous event
    595                     executor.execute(new CloseEvent(this));
    596                 } else {
    597                     // synchronous call (in the I/O loop)
    598                     handler.sessionClosed(this);
    599                 }
    600             }
    601         } catch (RuntimeException e) {
    602             LOG.error("Exception while closing the session : ", e);
    603         }
    604         service.getManagedSessions().remove(id);
    605     }
    606
    607     /**
    608      * process session idle event using the filter chain. To be called by the session {@link SelectorLoop} .
    609      */
    610     public void processSessionIdle(IdleStatus status) {
    611         if (IS_DEBUG) {
    612             LOG.debug("processing session idle {} event for session {}", status, this);
    613         }
    614
    615         try {
    616             for (IoFilter filter : chain) {
    617                 filter.sessionIdle(this, status);
    618             }
    619
    620             IoHandler handler = getService().getIoHandler();
    621
    622             if (handler != null) {
    623                 IoHandlerExecutor executor = getService().getIoHandlerExecutor();
    624
    625                 if (executor != null) {
    626                     // asynchronous event
    627                     executor.execute(new IdleEvent(this, status));
    628                 } else {
    629                     // synchronous call (in the I/O loop)
    630                     handler.sessionIdle(this, status);
    631                 }
    632             }
    633         } catch (RuntimeException e) {
    634             processException(e);
    635         }
    636     }
    637
    638     /** for knowing if the message buffer is the selector loop one */
    639     static ThreadLocal<ByteBuffer> tl = new ThreadLocal<ByteBuffer>() {
    640         @Override
    641         protected ByteBuffer initialValue() {
    642             return null;
    643         }
    644     };
    645
    646     /**
    647      * process session message received event using the filter chain. To be called by the session {@link SelectorLoop} .
    648      *
    649      * @param message the received message
    650      */
    651     public void processMessageReceived(ByteBuffer message) {
    652         if (IS_DEBUG) {
    653             LOG.debug("processing message '{}' received event for session {}", message, this);
    654         }
    655
    656         tl.set(message);
    657         try {
    658             // save basic statistics
    659             readBytes += message.remaining();
    660             lastReadTime = System.currentTimeMillis();
    661
    662             if (chain.length < 1) {
    663                 if (IS_DEBUG) {
    664                     LOG.debug("Nothing to do, the chain is empty");
    665                 }
    666
    667                 IoHandler handler = getService().getIoHandler();
    668
    669                 if (handler != null) {
    670                     IoHandlerExecutor executor = getService().getIoHandlerExecutor();
    671
    672                     if (executor != null) {
    673                         // asynchronous event
    674                         // copy the bytebuffer
    675                         if (IS_DEBUG) {
    676                             LOG.debug("copying bytebuffer before pushing to the executor");
    677                         }
    678
    679                         ByteBuffer original = message;
    680                         ByteBuffer clone = ByteBuffer.allocate(original.capacity());
    681                         // copy from the beginning
    682                         original.rewind();
    683                         clone.put(original);
    684                         original.rewind();
    685                         clone.flip();
    686                         executor.execute(new ReceiveEvent(this, clone));
    687                     } else {
    688                         // synchronous call (in the I/O loop)
    689                         handler.messageReceived(this, message);
    690                     }
    691                 }
    692
    693             } else {
    694                 readChainPosition = 0;
    695                 // we call the first filter, it's supposed to call the next ones using the filter chain controller
    696                 chain[readChainPosition].messageReceived(this, message, this);
    697             }
    698         } catch (RuntimeException e) {
    699             processException(e);
    700         }
    701
    702     }
    703
    704     /**
    705      * process session message writing event using the filter chain. To be called by the session {@link SelectorLoop} .
    706      *
    707      * @param message the wrote message, should be transformed into ByteBuffer at the end of the filter chain
    708      */
    709     public void processMessageWriting(WriteRequest writeRequest, IoFuture<Void> future) {
    710         if (IS_DEBUG) {
    711             LOG.debug("processing message '{}' writing event for session {}", writeRequest, this);
    712         }
    713
    714         try {
    715             // lastWriteRequest = null;
    716
    717             if (chain.length < 1) {
    718                 enqueueWriteRequest(writeRequest);
    719             } else {
    720                 writeChainPosition = chain.length - 1;
    721                 // we call the first filter, it's supposed to call the next ones using the filter chain controller
    722                 int position = writeChainPosition;
    723                 IoFilter nextFilter = chain[position];
    724                 nextFilter.messageWriting(this, writeRequest, this);
    725             }
    726
    727             // put the future in the last write request
    728             if (future != null) {
    729                 writeRequest.setFuture(future);
    730             }
    731         } catch (RuntimeException e) {
    732             processException(e);
    733         }
    734
    735     }
    736
    737     public void processMessageSent(Object highLevelMessage) {
    738         if (IS_DEBUG) {
    739             LOG.debug("processing message '{}' sent event for session {}", highLevelMessage, this);
    740         }
    741
    742         try {
    743             int size = chain.length;
    744
    745             for (int i = size - 1; i >= 0; i--) {
    746                 chain[i].messageSent(this, highLevelMessage);
    747             }
    748
    749             IoHandler handler = getService().getIoHandler();
    750
    751             if (handler != null) {
    752                 IoHandlerExecutor executor = getService().getIoHandlerExecutor();
    753
    754                 if (executor != null) {
    755                     // asynchronous event
    756                     executor.execute(new SentEvent(this, highLevelMessage));
    757                 } else {
    758                     // synchronous call (in the I/O loop)
    759                     handler.messageSent(this, highLevelMessage);
    760                 }
    761             }
    762         } catch (RuntimeException e) {
    763             processException(e);
    764         }
    765
    766     }
    767
    768     /**
    769      * process session message received event using the filter chain. To be called by the session {@link SelectorLoop} .
    770      *
    771      * @param message the received message
    772      */
    773     @Override
    774     public void callWriteNextFilter(WriteRequest message) {
    775         if (IS_DEBUG) {
    776             LOG.debug("calling next filter for writing for message '{}' position : {}", message, writeChainPosition);
    777         }
    778
    779         writeChainPosition--;
    780
    781         if (writeChainPosition < 0 || chain.length == 0) {
    782             // end of chain processing
    783             enqueueWriteRequest(message);
    784         } else {
    785             chain[writeChainPosition].messageWriting(this, message, this);
    786         }
    787
    788         writeChainPosition++;
    789     }
    790
    791     /**
    792      * {@inheritDoc}
    793      */
    794     @Override
    795     public void callReadNextFilter(Object message) {
    796         readChainPosition++;
    797
    798         if (readChainPosition >= chain.length) {
    799             // end of chain processing
    800             IoHandler handler = getService().getIoHandler();
    801
    802             if (handler != null) {
    803                 IoHandlerExecutor executor = getService().getIoHandlerExecutor();
    804
    805                 if (executor != null) {
    806                     // asynchronous event
    807                     if (message == tl.get()) {
    808                         // copy the bytebuffer
    809                         if (IS_DEBUG) {
    810                             LOG.debug("copying bytebuffer before pushing to the executor");
    811                         }
    812
    813                         ByteBuffer original = (ByteBuffer) message;
    814                         ByteBuffer clone = ByteBuffer.allocate(original.capacity());
    815                         // copy from the beginning
    816                         original.rewind();
    817                         clone.put(original);
    818                         original.rewind();
    819                         clone.flip();
    820                         executor.execute(new ReceiveEvent(this, clone));
    821                     } else {
    822                         executor.execute(new ReceiveEvent(this, message));
    823                     }
    824                 } else {
    825                     // synchronous call (in the I/O loop)
    826                     handler.messageReceived(this, message);
    827                 }
    828             }
    829         } else {
    830             chain[readChainPosition].messageReceived(this, message, this);
    831         }
    832
    833         readChainPosition--;
    834     }
    835
    836     /**
    837      * {@inheritDoc}
    838      */
    839     @Override
    840     public String toString() {
    841         if (isConnected() || isClosing()) {
    842             String remote = null;
    843             String local = null;
    844
    845             try {
    846                 remote = String.valueOf(getRemoteAddress());
    847             } catch (Exception e) {
    848                 remote = "Cannot get the remote address informations: " + e.getMessage();
    849             }
    850
    851             try {
    852                 local = String.valueOf(getLocalAddress());
    853             } catch (Exception e) {
    854                 local = "Cannot get the local address informations: " + e.getMessage();
    855             }
    856
    857             if (getService() instanceof IoServer) {
    858                 return "(" + getIdAsString() + ": " + getServiceName() + ", server, " + remote + " => " + local + ')';
    859             }
    860
    861             return "(" + getIdAsString() + ": " + getServiceName() + ", client, " + local + " => " + remote + ')';
    862         }
    863
    864         return "(" + getIdAsString() + ") Session disconnected ...";
    865     }
    866
    867     private String getServiceName() {
    868         return getService().getClass().getCanonicalName();
    869     }
    870
    871     /** create string for session id padded with 0 */
    872     private String getIdAsString() {
    873         String id = Long.toHexString(getId()).toUpperCase();
    874
    875         // Somewhat inefficient, but it won't happen that often
    876         // because an ID is often a big integer.
    877         while (id.length() < 8) {
    878             id = '0' + id; // padding
    879         }
    880         id = "0x" + id;
    881
    882         return id;
    883     }
    884 }