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.session;
021
022import java.util.Iterator;
023import java.util.Set;
024
025import org.apache.mina.core.future.CloseFuture;
026import org.apache.mina.core.future.IoFuture;
027import org.apache.mina.core.future.IoFutureListener;
028import org.apache.mina.core.service.IoService;
029import org.apache.mina.util.ConcurrentHashSet;
030
031/**
032 * Detects idle sessions and fires <tt>sessionIdle</tt> events to them.
033 * To be used for service unable to trigger idle events alone, like VmPipe
034 * or SerialTransport. Polling base transport are advised to trigger idle 
035 * events alone, using the poll/select timeout. 
036 *
037 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
038 */
039public class IdleStatusChecker {
040
041    // the list of session to check
042    private final Set<AbstractIoSession> sessions = new ConcurrentHashSet<AbstractIoSession>();
043
044    /* create a task you can execute in the transport code,
045     * if the transport is like NIO or APR you don't need to call it,
046     * you just need to call the needed static sessions on select()/poll() 
047     * timeout.
048     */
049    private final NotifyingTask notifyingTask = new NotifyingTask();
050
051    private final IoFutureListener<IoFuture> sessionCloseListener = new SessionCloseListener();
052
053    public IdleStatusChecker() {
054        // Do nothing
055    }
056
057    /**
058     * Add the session for being checked for idle. 
059     * @param session the session to check
060     */
061    public void addSession(AbstractIoSession session) {
062        sessions.add(session);
063        CloseFuture closeFuture = session.getCloseFuture();
064
065        // isn't service reponsability to remove the session nicely ?
066        closeFuture.addListener(sessionCloseListener);
067    }
068
069    /**
070     * remove a session from the list of session being checked.
071     * @param session
072     */
073    private void removeSession(AbstractIoSession session) {
074        sessions.remove(session);
075    }
076
077    /**
078     * get a runnable task able to be scheduled in the {@link IoService} executor.
079     * @return the associated runnable task
080     */
081    public NotifyingTask getNotifyingTask() {
082        return notifyingTask;
083    }
084
085    /**
086     * The class to place in the transport executor for checking the sessions idle 
087     */
088    public class NotifyingTask implements Runnable {
089        private volatile boolean cancelled;
090
091        private volatile Thread thread;
092
093        // we forbid instantiation of this class outside
094        /** No qualifier */
095        NotifyingTask() {
096            // Do nothing
097        }
098
099        public void run() {
100            thread = Thread.currentThread();
101            try {
102                while (!cancelled) {
103                    // Check idleness with fixed delay (1 second).
104                    long currentTime = System.currentTimeMillis();
105
106                    notifySessions(currentTime);
107
108                    try {
109                        Thread.sleep(1000);
110                    } catch (InterruptedException e) {
111                        // will exit the loop if interrupted from interrupt()
112                    }
113                }
114            } finally {
115                thread = null;
116            }
117        }
118
119        /**
120         * stop execution of the task
121         */
122        public void cancel() {
123            cancelled = true;
124            Thread thread = this.thread;
125            if (thread != null) {
126                thread.interrupt();
127            }
128        }
129
130        private void notifySessions(long currentTime) {
131            Iterator<AbstractIoSession> it = sessions.iterator();
132            while (it.hasNext()) {
133                AbstractIoSession session = it.next();
134                if (session.isConnected()) {
135                    AbstractIoSession.notifyIdleSession(session, currentTime);
136                }
137            }
138        }
139    }
140
141    private class SessionCloseListener implements IoFutureListener<IoFuture> {
142        /**
143         * Default constructor
144         */
145        public SessionCloseListener() {
146            super();
147        }
148
149        public void operationComplete(IoFuture future) {
150            removeSession((AbstractIoSession) future.getSession());
151        }
152    }
153}