/*
* ====================================================================
* Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://svnkit.com/license.html
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
* ====================================================================
*/
package org.tmatesoft.svn.core.wc;
import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import org.tmatesoft.svn.core.ISVNCanceller;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepository;
import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions;
import org.tmatesoft.svn.core.io.ISVNConnectionListener;
import org.tmatesoft.svn.core.io.ISVNSession;
import org.tmatesoft.svn.core.io.ISVNTunnelProvider;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.util.ISVNDebugLog;
import org.tmatesoft.svn.util.SVNDebugLog;
import org.tmatesoft.svn.util.SVNLogType;
/**
* The <b>DefaultSVNRepositoryPool</b> class is a default implementation of
* the <b>ISVNRepositoryPool</b> interface.
*
* <p>
* It creates <b>SVNRepository</b> objects that may be stored in a common
* pool and reused later. The objects common pool may be shared by different
* threads, but each thread can retrieve only those objects, that have been
* created within that thread. So, <b>DefaultSVNRepositoryPool</b> is thread-safe.
* An objects pool may be global during runtime, or it may be private - one separate
* pool per one <b>DefaultSVNRepositoryPool</b> object. Also there's a possibility to
* have a <b>DefaultSVNRepositoryPool</b> object with the pool feature
* disabled (<b>SVNRepository</b> objects instantiated by such a creator are never
* cached).
*
* <p>
* <b>DefaultSVNRepositoryPool</b> caches one <b>SVNRepository</b> object per one url
* protocol (per one thread), that is the number of protocols used equals to
* the number of objects cached per one thread (if all objects are created as reusable).
*
* <p>
* Also <b>DefaultSVNRepositoryPool</b> is able to create <b>SVNRepository</b> objects
* that use a single socket connection (i.e. don't close a connection after every repository
* access operation but reuse a single one).
*
* @version 1.3
* @author TMate Software Ltd.
* @since 1.2
*/
public class DefaultSVNRepositoryPool implements ISVNRepositoryPool, ISVNSession, ISVNConnectionListener {
/**
* Defines a common shared objects pool. All objects that will be
* created by different threads will be stored in this common pool.
*
* @deprecated
*/
public static final int RUNTIME_POOL = 1;
/**
* Defines a private pool. All objects that will be created by
* different threads will be stored only within this pool object.
* This allows to have more than one separate pools.
*
* @deprecated
*/
public static final int INSTANCE_POOL = 2;
/**
* Defines a without-pool configuration. Objects that are created
* by this <b>DefaultSVNRepositoryPool</b> object are not cached,
* the pool feature is disabled.
*
* @deprecated
*/
public static final int NO_POOL = 4;
private static final long DEFAULT_IDLE_TIMEOUT = 60*1000;
private static volatile ScheduledExecutorService ourTimer;
private static volatile int ourInstanceCount;
private ISVNAuthenticationManager myAuthManager;
private ISVNTunnelProvider myTunnelProvider;
private ISVNDebugLog myDebugLog;
private ISVNCanceller myCanceller;
private Map<String, SVNRepository> myPool;
private long myTimeout;
private Map<SVNRepository, Long> myInactiveRepositories = new HashMap<SVNRepository, Long>();
private ScheduledExecutorService myTimer;
private boolean myIsKeepConnection;
private ScheduledFuture<?> myScheduledTimeoutTask;
private File mySpoolLocation;
/**
* Constructs a <b>DefaultSVNRepositoryPool</b> instance
* that represents {@link #RUNTIME_POOL} objects pool.
* <b>SVNRepository</b> objects created by this instance will
* use a single socket connection.
*
* <p/>
* This constructor is identical to
* <code>DefaultSVNRepositoryPool(authManager, tunnelProvider, DEFAULT_IDLE_TIMEOUT, true)</code>.
*
* @param authManager an authentication driver
* @param tunnelProvider a tunnel provider
*/
public DefaultSVNRepositoryPool(ISVNAuthenticationManager authManager, ISVNTunnelProvider tunnelProvider) {
this(authManager, tunnelProvider, DEFAULT_IDLE_TIMEOUT, true);
}
/**
* Constructs a <b>DefaultSVNRepositoryPool</b> instance
* that represents {@link #RUNTIME_POOL} objects pool.
* <b>SVNRepository</b> objects created by this instance will
* use a single socket connection.
*
* @param authManager an authentication driver
* @param tunnelProvider a tunnel provider
* @param timeout inactivity timeout after which open connections should be closed
* @param keepConnection whether to keep connection open
*/
public DefaultSVNRepositoryPool(ISVNAuthenticationManager authManager, ISVNTunnelProvider tunnelProvider,
long timeout, boolean keepConnection) {
myAuthManager = authManager;
myTunnelProvider = tunnelProvider;
myDebugLog = SVNDebugLog.getDefaultLog();
myTimeout = timeout > 0 ? timeout : DEFAULT_IDLE_TIMEOUT;
myIsKeepConnection = keepConnection;
myTimeout = timeout;
synchronized (DefaultSVNRepositoryPool.class) {
if (ourTimer == null) {
ourTimer = createExecutor();
}
if (myIsKeepConnection) {
myTimer = ourTimer;
try {
myScheduledTimeoutTask = myTimer.scheduleWithFixedDelay(new TimeoutTask(), 10, 10, TimeUnit.SECONDS);
} catch (IllegalStateException e) {
// Timer already cancelled error.
SVNDebugLog.getDefaultLog().logError(SVNLogType.DEFAULT, e);
ourTimer = createExecutor();
myTimer = ourTimer;
myScheduledTimeoutTask = myTimer.scheduleWithFixedDelay(new TimeoutTask(), 10, 10, TimeUnit.SECONDS);
}
}
ourInstanceCount++;
}
}
private ScheduledExecutorService createExecutor() {
return Executors.newSingleThreadScheduledExecutor(new DaemonThreadFactory());
}
private static final class DaemonThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
final Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
}
/**
* Constructs a <b>DefaultSVNRepositoryPool</b> instance.
*
* @param authManager an authentication driver
* @param tunnelProvider a tunnel provider
* @param keepConnections if <span class="javakeyword">true</span>
* then <b>SVNRepository</b> objects will keep
* a single connection for accessing a repository,
* if <span class="javakeyword">false</span> - open
* a new connection per each repository access operation
* @param poolMode a mode of this object represented by
* one of the constant fields of <b>DefaultSVNRepositoryPool</b>
* @deprecated
*/
public DefaultSVNRepositoryPool(ISVNAuthenticationManager authManager, ISVNTunnelProvider tunnelProvider, boolean keepConnections, int poolMode) {
this(authManager, tunnelProvider);
}
/**
* Creates a new <b>SVNRepository</b> driver object.
* if <code>mayReuse</code> is <span class="javakeyword">true</span>
* and the mode of this <b>DefaultSVNRepositoryPool</b> object is not
* {@link #NO_POOL} then first tries to find the <b>SVNRepository</b>
* object in the pool for the given protocol. If the object is not found,
* creates a new one for that protocol, caches it in the pool and returns
* back.
*
* <p>
* <b>NOTE:</b> be careful when simultaneously using several <b>SVNRepository</b>
* drivers for the same protocol - since there can be only one driver object in
* the pool per a protocol, creating two objects for the same protocol
* with <code>mayReuse</code> set to <span class="javakeyword">true</span>,
* actually returns the same single object stored in the thread pool.
*
* @param url a repository location for which a driver
* is to be created
* @param mayReuse if <span class="javakeyword">true</span> then
* <b>SVNRepository</b> object is reusable
* @return a new <b>SVNRepository</b> driver object
* @throws SVNException
* @see org.tmatesoft.svn.core.io.SVNRepository
*
*/
public synchronized SVNRepository createRepository(SVNURL url, boolean mayReuse) throws SVNException {
synchronized (DefaultSVNRepositoryPool.class) {
if (myIsKeepConnection && myTimer == null && ourTimer != null) {
myTimer = ourTimer;
myScheduledTimeoutTask = myTimer.scheduleWithFixedDelay(new TimeoutTask(), 10, 10, TimeUnit.SECONDS);
}
}
SVNRepository repos = null;
Map<String, SVNRepository> pool = getPool();
if (!mayReuse || pool == null) {
repos = SVNRepositoryFactory.create(url, this);
repos.setAuthenticationManager(myAuthManager);
repos.setTunnelProvider(myTunnelProvider);
repos.setDebugLog(myDebugLog);
repos.setCanceller(myCanceller);
setOptionalSpoolLocation(repos, myTunnelProvider);
return repos;
}
repos = (SVNRepository) pool.get(url.getProtocol());
if (repos != null) {
repos.setLocation(url, false);
} else {
repos = SVNRepositoryFactory.create(url, this);
// add listener.
if (myIsKeepConnection) {
repos.addConnectionListener(this);
}
pool.put(url.getProtocol(), repos);
}
repos.setAuthenticationManager(myAuthManager);
repos.setTunnelProvider(myTunnelProvider);
repos.setDebugLog(myDebugLog);
repos.setCanceller(myCanceller);
setOptionalSpoolLocation(repos, myTunnelProvider);
return repos;
}
public void setSpoolLocation(File location) {
mySpoolLocation = location;
}
public File getSpoolLocation() {
return mySpoolLocation;
}
private void setOptionalSpoolLocation(SVNRepository repos, ISVNTunnelProvider options) {
if (!(repos instanceof DAVRepository)) {
return;
}
if (!(options instanceof DefaultSVNOptions)) {
return;
}
final File poolSpoolLocation = getSpoolLocation();
final File configSpoolLocation = ((DefaultSVNOptions) options).getHttpSpoolDirectory();
final File spoolLocation = poolSpoolLocation != null ? poolSpoolLocation : configSpoolLocation;
if (spoolLocation != null) {
((DAVRepository) repos).setSpoolLocation(spoolLocation);
}
}
/**
* Sets the given authentication instance to this pool and to all {@link SVNRepository} objects
* stored in this pool.
*
* @param authManager authentication manager instance
*/
public void setAuthenticationManager(ISVNAuthenticationManager authManager) {
myAuthManager = authManager;
Map<String, SVNRepository> pool = getPool();
for (Iterator<String> protocols = pool.keySet().iterator(); protocols.hasNext();) {
String key = protocols.next();
SVNRepository repository = (SVNRepository) pool.get(key);
repository.setAuthenticationManager(myAuthManager);
}
}
/**
* Says if the given <b>SVNRepository</b> driver object should
* keep a connection opened. If this object was created with
* <code>keepConnections</code> set to <span class="javakeyword">true</span>
* and if <code>repository</code> is not created for the
* <span class="javastring">"svn+ssh"</span> protocol (since for this protocol there's
* no extra need to keep a connection opened - it remains opened), this
* method returns <span class="javakeyword">true</span>.
*
* @param repository an <b>SVNRepository</b> driver
* @return <span class="javakeyword">true</span> if
* the driver should keep a connection
*/
public boolean keepConnection(SVNRepository repository) {
return myIsKeepConnection;
}
/**
* Closes connections of cached <b>SVNRepository</b> objects.
*
* <p>
* Actually, calls the {@link #dispose()} routine.
*
* @param shutdownAll if <span class="javakeyword">true</span> - closes
* connections of all the cached objects, otherwise only
* connections of those cached objects which owner threads
* have already disposed
* @see SVNRepository
*/
public synchronized void shutdownConnections(boolean shutdownAll) {
dispose();
}
/**
* Disposes this pool. Clears all inactive {@link SVNRepository} objects from this pool.
*
* @since 1.2.0
*/
public void dispose() {
synchronized (myInactiveRepositories) {
myTimer = null;
}
shutdownInactiveRepositories(Long.MAX_VALUE);
Map<String, SVNRepository> pool = getPool();
for (Iterator<String> protocols = pool.keySet().iterator(); protocols.hasNext();) {
String key = protocols.next();
SVNRepository repository = pool.get(key);
repository.closeSession();
}
myPool = null;
synchronized (DefaultSVNRepositoryPool.class) {
if (myScheduledTimeoutTask != null) {
myScheduledTimeoutTask.cancel(false);
myScheduledTimeoutTask = null;
}
ourInstanceCount--;
if (ourInstanceCount <= 0) {
ourInstanceCount = 0;
shutdownTimer();
}
}
}
/**
* Stops the daemon thread that checks whether there are any <code>SVNRepository</code> objects
* expired.
*
* @see #connectionClosed(SVNRepository)
* @since 1.1.5
*/
public static void shutdownTimer() {
synchronized (DefaultSVNRepositoryPool.class) {
if (ourTimer != null) {
try {
ourTimer.shutdownNow();
} catch (SecurityException se) {
}
ourTimer = null;
}
}
}
// no caching in this class
/**
* Does nothing.
*
* @param repository an <b>SVNRepository</b> driver (to distinguish
* that repository for which this message is actual)
* @param revision a revision number
* @param message the commit message for <code>revision</code>
*/
public void saveCommitMessage(SVNRepository repository, long revision, String message) {
}
/**
* Returns <span class="javakeyword">null</span>.
*
* @param repository an <b>SVNRepository</b> driver (to distinguish
* that repository for which a commit message is requested)
* @param revision a revision number
* @return the commit message for <code>revision</code>
*/
public String getCommitMessage(SVNRepository repository, long revision) {
return null;
}
/**
* Returns <span class="javakeyword">false</span>.
*
* @param repository an <b>SVNRepository</b> driver (to distinguish
* that repository for which a commit message is requested)
* @param revision a revision number
* @return <span class="javakeyword">true</span> if the cache
* has got a message for the given repository and revision,
* <span class="javakeyword">false</span> otherwise
*/
public boolean hasCommitMessage(SVNRepository repository, long revision) {
return false;
}
/**
* Places the specified <code>repository</code> into the pool of inactive <code>SVNRepository</code>
* objects.
*
* <p/>
* If this pool keeps connections open (refer to the <code>keepConnection</code> parameter of the
* {@link #DefaultSVNRepositoryPool(ISVNAuthenticationManager, ISVNTunnelProvider, long, boolean) constructor}),
* then each <code>SVNRepository</code> object which is passed to this method (what means it finished
* the operation), must be reused in a period of time not greater than the timeout value. The timeout value
* is either equal to the value passed to the {@link #DefaultSVNRepositoryPool(ISVNAuthenticationManager, ISVNTunnelProvider, long, boolean) constructor},
* or it defaults to 60 seconds if no valid timeout value was provided. Otherwise the repository object will
* be {@link SVNRepository#closeSession() closed}. Timeout checking occurs one time in 10 seconds. This
* behavior - closing repository objects after timeout - can be changed by switching off the timer thread
* via {@link #shutdownTimer()}.
*
* @param repository repository access object
* @since 1.1.4
*/
public void connectionClosed(final SVNRepository repository) {
// start inactivity timer.
synchronized (myInactiveRepositories) {
myInactiveRepositories.put(repository, new Long(System.currentTimeMillis()));
// schedule timeout cleanup.
}
}
/**
* Removes the specified <code>repository</code> object from the pool of inactive <code>SVNRepository</code>
* objects held by this object. This method is synchronized.
*
* @param repository repository access object to remove from the pool
* @since 1.1.4
*/
public void connectionOpened(SVNRepository repository) {
synchronized (myInactiveRepositories) {
myInactiveRepositories.remove(repository);
}
}
/**
* Sets a canceller to be used in all {@link SVNRepository} objects produced by this
* pool.
*
* @param canceller caller's canceller
* @since 1.1.4
*/
public void setCanceller(ISVNCanceller canceller) {
myCanceller = canceller;
Map<String, SVNRepository> pool = getPool();
for (Iterator<String> protocols = pool.keySet().iterator(); protocols.hasNext();) {
String key = (String) protocols.next();
SVNRepository repository = (SVNRepository) pool.get(key);
repository.setCanceller(canceller);
}
}
/**
* Sets a debug logger to be used in all {@link SVNRepository} objects produced by this
* pool.
*
* @param log debug logger
* @since 1.1.4
*/
public void setDebugLog(ISVNDebugLog log) {
myDebugLog = log == null ? SVNDebugLog.getDefaultLog() : log;
Map<String, SVNRepository> pool = getPool();
for (Iterator<String> protocols = pool.keySet().iterator(); protocols.hasNext();) {
String key = (String) protocols.next();
SVNRepository repository = (SVNRepository) pool.get(key);
repository.setDebugLog(myDebugLog);
}
}
private long getTimeout() {
return myTimeout;
}
private Map<String, SVNRepository> getPool() {
if (myPool == null) {
myPool = new HashMap<String, SVNRepository>();
}
return myPool;
}
private void shutdownInactiveRepositories(long currentTime) {
synchronized (myInactiveRepositories) {
for (Iterator<SVNRepository> repositories = myInactiveRepositories.keySet().iterator(); repositories.hasNext();) {
SVNRepository repos = repositories.next();
long time = ((Long) myInactiveRepositories.get(repos)).longValue();
if (currentTime - time >= getTimeout()) {
repositories.remove();
repos.closeSession();
}
}
}
}
private class TimeoutTask implements Runnable {
public void run() {
try {
shutdownInactiveRepositories(System.currentTimeMillis());
} catch (Throwable th) {
SVNDebugLog.getDefaultLog().logSevere(SVNLogType.WC, th);
}
}
}
}