/** * This file is part of muCommander, http://www.mucommander.com * Copyright (C) 2002-2016 Maxence Bernard * * muCommander is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * muCommander is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.mucommander.commons.file.connection; import com.mucommander.commons.file.AuthException; import com.mucommander.commons.file.Credentials; import com.mucommander.commons.file.FileURL; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; /** * ConnectionHandler is a an abstract class that provides the basic operations for to interact with a server: establish * the connection, keep it alive and close it. * * @see com.mucommander.commons.file.connection.ConnectionPool * @author Maxence Bernard */ public abstract class ConnectionHandler { private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionHandler.class); /** URL of the server this ConnectionHandler connects to */ protected FileURL realm; /** Credentials that are used to connect to the server */ protected Credentials credentials; /** True if this ConnectionHandler is currently locked */ protected boolean isLocked; /** Time at which the connection managed by this ConnectionHandler was last used */ protected long lastActivityTimestamp; /** Time at which the connection managed by this ConnectionHandler was last kept alive */ protected long lastKeepAliveTimestamp; /** Number of seconds of inactivity after which this ConnectionHandler's connection will be closed by ConnectionPool */ protected long closeOnInactivityPeriod = DEFAULT_CLOSE_ON_INACTIVITY_PERIOD; /** Number of seconds of inactivity after which this ConnectionHandler's connection will be kept alive by ConnectionPool */ protected long keepAlivePeriod = DEFAULT_KEEP_ALIVE_PERIOD; /** Default 'close on inactivity' period */ private final static long DEFAULT_CLOSE_ON_INACTIVITY_PERIOD = 300; /** Default keep alive period (-1, keep alive disabled) */ private final static long DEFAULT_KEEP_ALIVE_PERIOD = -1; /** * Creates a new ConnectionHandler for the given server URL using the Credentials included in the URL (potentially * <code>null</code>). * * @param serverURL URL of the server to connect to */ public ConnectionHandler(FileURL serverURL) { realm = serverURL.getRealm(); this.credentials = serverURL.getCredentials(); } /** * Returns the URL of the server this ConnectionHandler connects to. * * @return the URL of the server this ConnectionHandler connects to */ public FileURL getRealm() { return realm; } /** * Returns the Credentials that are used to connect to the server, <code>null</code> if no credentials are used. * * @return the Credentials that are used to connect to the server, <code>null</code> if no credentials are used */ public Credentials getCredentials() { return credentials; } /** * Checks if the connection is currenty active (as returned by {@link #isConnected()} and if it isn't, starts it * by calling {@link #startConnection()}. Returns true if the connection was properly started, false if the * connection was already active, or throws an IOException if the connection could not be started. * * @return Returns true if the connection was properly started, false if the connection was already active * @throws IOException if the connection could not be started */ public boolean checkConnection() throws IOException { if(!isConnected()) { LOGGER.info("not connected, starting connection, this="+this); startConnection(); return true; } return false; } /** * Tries to lock this ConnectionHandler and returns true if it could be locked, false if it is already locked. * * @return true if it could be locked, false if it is already locked. */ public synchronized boolean acquireLock() { if(isLocked) { LOGGER.info("!!!!! acquireLock() returning false, should not happen !!!!!", new Throwable()); return false; } isLocked = true; return true; } /** * Tries to release the lock on this ConnectionHandler and returns true if it could be locked, false if it * is not locked. * * @return true if it could be locked, false if it is not locked */ public boolean releaseLock() { synchronized(this) { if(!isLocked) { LOGGER.info("!!!!! releaseLock() returning false, should not happen !!!!!", new Throwable()); return false; } isLocked = false; } ConnectionPool.notifyConnectionHandlerLockReleased(); return true; } /** * Returns <code>true</code> if this ConnectionHandler is currently locked. * * @return <code>true</code> if this ConnectionHandler is currently locked */ public synchronized boolean isLocked() { return isLocked; } /** * Updates the time at which the connection managed by this ConnectionHandler was last used to now (current time). */ public void updateLastActivityTimestamp() { lastActivityTimestamp = System.currentTimeMillis(); } /** * Returns the time at which the connection managed by this ConnectionHandler was last used. * * @return the time at which the connection managed by this ConnectionHandler was last used */ public long getLastActivityTimestamp() { return lastActivityTimestamp; } /** * Updates the time at which the connection managed by this ConnectionHandler was last kept alive to now (current time). */ public void updateLastKeepAliveTimestamp() { lastKeepAliveTimestamp = System.currentTimeMillis(); } /** * Returns the time at which the connection managed by this ConnectionHandler was last kept alive. * * @return the time at which the connection managed by this ConnectionHandler was last kept alive */ public long getLastKeepAliveTimestamp() { return lastKeepAliveTimestamp; } /** * Returns the number of seconds of inactivity after which {@link ConnectionPool} will close the connection by * calling {@link #closeConnection()}, <code>-1</code> to indicate that the connection should not be automatically * closed. * * <p>By default, this value is 300 seconds (5 minutes).</p> * * @return the number of seconds of inactivity after which {@link ConnectionPool} will close the connection, * <code>-1</code> to indicate that the connection should not be automatically closed */ public long getCloseOnInactivityPeriod() { return closeOnInactivityPeriod; } /** * Sets the number of seconds of inactivity after which {@link ConnectionPool} will close the connection by calling * {@link #closeConnection()}, <code>-1</code> to prevent the connection from being automatically closed. * * <p>By default, this value is 300 seconds (5 minutes).</p> * * @param nbSeconds the number of seconds of inactivity after which {@link ConnectionPool} will close the connection, * <code>-1</code> to indicate that the connection should not be automatically closed */ public void setCloseOnInactivityPeriod(long nbSeconds) { closeOnInactivityPeriod = nbSeconds; } /** * Returns the number of seconds of inactivity after which {@link ConnectionPool} will keep the connection alive * by calling {@link #keepAlive()}, <code>-1</code> to indicate that this connection should not be kept alive. * * <p>By default, this value is -1 (keep alive disabled).</p> * * @return the number of seconds of inactivity after which {@link ConnectionPool} will keep the connection alive * by calling {@link #keepAlive()}, <code>-1</code> to indicate that this connection should not be kept alive */ public long getKeepAlivePeriod() { return keepAlivePeriod; } /** * Returns the number of seconds of inactivity after which {@link ConnectionPool} will keep the connection alive * by calling {@link #keepAlive()}, <code>-1</code> to indicate that this connection should not be kept alive. * * <p>By default, this value is -1 (keep alive disabled).</p> * * @param nbSeconds the number of seconds of inactivity after which {@link ConnectionPool} will keep the connection * alive by calling {@link #keepAlive()}, <code>-1</code> to indicate that this connection should not be kept alive */ public void setKeepAlivePeriod(long nbSeconds) { keepAlivePeriod = nbSeconds; } /** * Returns <code>true</code> if the given Object is a ConnectionHandler whose realm and credentials are equal to * those of this ConnectionHandler. The credentials comparison is password-sensitive. * * @param o the Object to compare for equality * @see Credentials#equals(Object, boolean) */ public boolean equals(Object o) { if(o==null || !(o instanceof ConnectionHandler)) return false; ConnectionHandler connHandler = (ConnectionHandler)o; return equals(connHandler.realm, connHandler.credentials); } /** * Returns <code>true</code> if both the given realm and credentials are equal to those of this ConnectionHandler. * The credentials comparison is password-sensitive. * * @param realm the FileURL to compare against this ConnectionHandler's * @param credentials the Credentials to compare against this ConnectionHandler's * @return true if both the given realm and credentials are equal to those of this ConnectionHandler * @see Credentials#equals(Object, boolean) */ public boolean equals(FileURL realm, Credentials credentials) { if(!this.realm.equals(realm, false, true)) return false; // Compare credentials. One or both Credentials instances may be null. // Note: Credentials.equals() considers null as equal to empty Credentials (see Credentials#isEmpty()) return (this.credentials==null && credentials==null) || (this.credentials!=null && this.credentials.equals(credentials, true)) || (credentials!=null && credentials.equals(this.credentials, true)); } /** * Throws an {@link AuthException} using this connection handler's realm, credentials and the message passed as * an argument (can be <code>null</code>). The FileURL instance representing the realm that is used to create * the <code>AuthException</code> is a clone of this realm, making it safe for modification. * * @param message the message to pass to AuthException's constructor, can be <code>null</code> * @throws AuthException always throws the created AuthException */ public void throwAuthException(String message) throws AuthException { FileURL clonedRealm = (FileURL)realm.clone(); clonedRealm.setCredentials(credentials); throw new AuthException(clonedRealm, message); } ////////////////////// // Abstract methods // ////////////////////// /** * Starts the connection managed by this ConnectionHandler, and throws an IOException if the connection could not * be established. This method may be called several times during the life of this ConnectionHandler, if the * connection dropped and must be re-established. * * @throws IOException if an error occurred while trying to establish the connection * @throws AuthException if an authentication error occurred (incorrect login or password, insufficient privileges...) */ public abstract void startConnection() throws IOException, AuthException; /** * Returns <code>true</code> if the connection managed by this ConnectionHandler is currently active/established, * in a state that makes it possible to serve client requests. * * <p>Implementation note: This method must not perform any I/O which could block the calling thread.</p> * * @return <code>true</code> if the connection managed by this ConnectionHandler is currently active/established */ public abstract boolean isConnected(); /** * Closes the connection managed by this ConnectionHandler. * * <p>Implementation note: the implementation must guarantee that any calls to {@link #isConnected()} after this * method has been called return false.</p> */ public abstract void closeConnection(); /** * Keeps this connection alive. * * <p>Implementation note: if keep alive is not available in the underlying protocol or * simply unnecessary, this method should be implemented as a no-op (do nothing).</p> */ public abstract void keepAlive(); }