blob: d46b0e53c39db267b234553e829aa3a2f48670a4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/***
* Java TelnetD library (embeddable telnet daemon)
* Copyright (c) 2000-2005 Dieter Wimberger
* All rights reserved.
* <p/>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* <p/>
* Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* <p/>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS
* IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
***/
package org.apache.felix.gogo.jline.telnet;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Class that takes care for active and queued connection.
* Housekeeping is done also for connections that were just broken
* off, or exceeded their timeout.
*
* @author Dieter Wimberger
* @version 2.0 (16/07/2006)
*/
public abstract class ConnectionManager implements Runnable {
private static Logger LOG = Logger.getLogger(ConnectionManager.class.getName());
private final List<Connection> openConnections;
private Thread thread;
private ThreadGroup threadGroup; //ThreadGroup all connections run in
private Stack<Connection> closedConnections;
private ConnectionFilter connectionFilter; //reference to the connection filter
private int maxConnections; //maximum allowed connections stored from the properties
private int warningTimeout; //time to idle warning
private int disconnectTimeout; //time to idle diconnection
private int housekeepingInterval; //interval for managing cleanups
private String loginShell;
private boolean lineMode = false;
private boolean stopping = false;
public ConnectionManager() {
threadGroup = new ThreadGroup(toString() + "Connections");
closedConnections = new Stack<>();
openConnections = Collections.synchronizedList(new ArrayList<>(100));
}
public ConnectionManager(int con, int timew, int timedis, int hoke, ConnectionFilter filter, String lsh, boolean lm) {
this();
connectionFilter = filter;
loginShell = lsh;
lineMode = lm;
maxConnections = con;
warningTimeout = timew;
disconnectTimeout = timedis;
housekeepingInterval = hoke;
}//constructor
/**
* Gets the active ConnectionFilter instance or
* returns null if no filter is set.
*
* @return the managers ConnectionFilter.
*/
public ConnectionFilter getConnectionFilter() {
return connectionFilter;
}//getConnectionFilter
/**
* Set a connection filter for this
* ConnectionManager instance. The filter is used to handle
* IP level allow/deny of incoming connections.
*
* @param filter ConnectionFilter instance.
*/
public void setConnectionFilter(ConnectionFilter filter) {
connectionFilter = filter;
}//setConnectionFilter
/**
* Returns the number of open connections.
* @return the number of open connections as <tt>int</tt>.
*/
public int openConnectionCount() {
return openConnections.size();
}//openConnectionCount
/**
* Returns the {@link Connection} at the given index.
* @param idx
* @return
*/
public Connection getConnection(int idx) {
synchronized (openConnections) {
return openConnections.get(idx);
}
}//getConnection
/**
* Get all {@link Connection} instances with the given
* <tt>InetAddress</tt>.
*
* @return all {@link Connection} instances with the given
* <tt>InetAddress</tt>.
*/
public Connection[] getConnectionsByAdddress(InetAddress addr) {
ArrayList<Connection> l = new ArrayList<>();
synchronized (openConnections) {
for (Connection connection : openConnections) {
if (connection.getConnectionData().getInetAddress().equals(addr)) {
l.add(connection);
}
}
}
Connection[] conns = new Connection[l.size()];
return l.toArray(conns);
}//getConnectionsByAddress
/**
* Starts this <tt>ConnectionManager</tt>.
*/
public void start() {
thread = new Thread(this);
thread.start();
}//start
/**
* Stops this <tt>ConnectionManager</tt>.
*/
public void stop() {
LOG.log(Level.FINE, "stop()::" + this.toString());
stopping = true;
//wait for thread to die
try {
if (thread != null) {
thread.join();
}
} catch (InterruptedException iex) {
LOG.log(Level.SEVERE, "stop()", iex);
}
synchronized (openConnections) {
for (Connection tc : openConnections) {
try {
//maybe write a disgrace to the socket?
tc.close();
} catch (Exception exc) {
LOG.log(Level.SEVERE, "stop()", exc);
}
}
openConnections.clear();
}
LOG.log(Level.FINE, "stop():: Stopped " + this.toString());
}//stop
/**
* Method that that tries to connect an incoming request.
* Properly queueing.
*
* @param insock Socket thats representing the incoming connection.
*/
public void makeConnection(Socket insock) {
LOG.log(Level.FINE, "makeConnection()::" + insock.toString());
if (connectionFilter == null || connectionFilter.isAllowed(insock.getInetAddress())) {
//we create the connection data object at this point to
//store certain information there.
ConnectionData newCD = new ConnectionData(insock, this);
newCD.setLoginShell(loginShell);
newCD.setLineMode(lineMode);
if (openConnections.size() < maxConnections) {
//create a new Connection instance
Connection con = createConnection(threadGroup, newCD);
//log the newly created connection
Object[] args = {openConnections.size() + 1};
LOG.info(MessageFormat.format("connection #{0,number,integer} made.", args));
//register it for being managed
synchronized (openConnections) {
openConnections.add(con);
}
//start it
con.start();
}
} else {
LOG.info("makeConnection():: Active Filter blocked incoming connection.");
try {
insock.close();
} catch (IOException ex) {
//do nothing or log.
}
}
}//makeConnection
protected abstract Connection createConnection(ThreadGroup threadGroup, ConnectionData newCD);
/**
* Periodically does following work:
* <ul>
* <li> cleaning up died connections.
* <li> checking managed connections if they are working properly.
* <li> checking the open connections.
* </ul>
*/
public void run() {
//housekeep connections
try {
do {
//clean up and close all broken connections
//cleanupBroken();
//clean up closed connections
cleanupClosed();
//check all active connections
checkOpenConnections();
//sleep interval
Thread.sleep(housekeepingInterval);
} while (!stopping);
} catch (Exception e) {
LOG.log(Level.SEVERE, "run()", e);
}
LOG.log(Level.FINE, "run():: Ran out " + this.toString());
}//run
/*
private void cleanupBroken() {
//cleanup loop
while (!m_BrokenConnections.isEmpty()) {
Connection nextOne = (Connection) m_BrokenConnections.pop();
log.info("cleanupBroken():: Closing broken connection " + nextOne.toString());
//fire logoff event for shell site cleanup , beware could hog the daemon thread
nextOne.processConnectionEvent(new ConnectionEvent(nextOne, ConnectionEvent.CONNECTION_BROKEN));
//close the connection, will be automatically registered as closed
nextOne.close();
}
}//cleanupBroken
*/
private void cleanupClosed() {
if (stopping) {
return;
}
//cleanup loop
while (!closedConnections.isEmpty()) {
Connection nextOne = closedConnections.pop();
LOG.info("cleanupClosed():: Removing closed connection " + nextOne.toString());
synchronized (openConnections) {
openConnections.remove(nextOne);
}
}
}//cleanupBroken
private void checkOpenConnections() {
if (stopping) {
return;
}
//do routine checks on active connections
synchronized (openConnections) {
for (Connection conn : openConnections) {
ConnectionData cd = conn.getConnectionData();
//check if it is dead and remove it.
if (!conn.isActive()) {
registerClosedConnection(conn);
continue;
}
/* Timeouts check */
//first we caculate the inactivity time
long inactivity = System.currentTimeMillis() - cd.getLastActivity();
//now we check for warning and disconnection
if (inactivity > warningTimeout) {
//..and for disconnect
if (inactivity > (disconnectTimeout + warningTimeout)) {
//this connection needs to be disconnected :)
LOG.log(Level.FINE, "checkOpenConnections():" + conn.toString() + " exceeded total timeout.");
//fire logoff event for shell site cleanup , beware could hog the daemon thread
conn.processConnectionEvent(new ConnectionEvent(conn, ConnectionEvent.Type.CONNECTION_TIMEDOUT));
//conn.close();
} else {
//this connection needs to be warned :)
if (!cd.isWarned()) {
LOG.log(Level.FINE, "checkOpenConnections():" + conn.toString() + " exceeded warning timeout.");
cd.setWarned(true);
//warning event is fired but beware this could hog the daemon thread!!
conn.processConnectionEvent(new ConnectionEvent(conn, ConnectionEvent.Type.CONNECTION_IDLE));
}
}
}
}
/* end Timeouts check */
}
}//checkConnections
public void registerClosedConnection(Connection con) {
if (stopping) {
return;
}
if (!closedConnections.contains(con)) {
LOG.log(Level.FINE, "registerClosedConnection()::" + con.toString());
closedConnections.push(con);
}
}//unregister
public int getDisconnectTimeout() {
return disconnectTimeout;
}
public void setDisconnectTimeout(int disconnectTimeout) {
this.disconnectTimeout = disconnectTimeout;
}
public int getHousekeepingInterval() {
return housekeepingInterval;
}
public void setHousekeepingInterval(int housekeepingInterval) {
this.housekeepingInterval = housekeepingInterval;
}
public boolean isLineMode() {
return lineMode;
}
public void setLineMode(boolean lineMode) {
this.lineMode = lineMode;
}
public String getLoginShell() {
return loginShell;
}
public void setLoginShell(String loginShell) {
this.loginShell = loginShell;
}
public int getMaxConnections() {
return maxConnections;
}
public void setMaxConnections(int maxConnections) {
this.maxConnections = maxConnections;
}
public int getWarningTimeout() {
return warningTimeout;
}
public void setWarningTimeout(int warningTimeout) {
this.warningTimeout = warningTimeout;
}
}//class ConnectionManager