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 }