All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
de.codesourcery.versiontracker.common.server.VersionTracker Maven / Gradle / Ivy
/**
* Copyright 2018 Tobias Gierke
*
* Licensed 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.
*/
package de.codesourcery.versiontracker.common.server;
import de.codesourcery.versiontracker.common.Artifact;
import de.codesourcery.versiontracker.common.IVersionProvider;
import de.codesourcery.versiontracker.common.IVersionStorage;
import de.codesourcery.versiontracker.common.VersionInfo;
import org.apache.commons.lang3.Validate;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiPredicate;
/**
* This class is responsible for requesting and periodically refreshing artifact metadata as well
* as providing this metadata to clients using the {@link #getVersionInfo(List,BiPredicate)} method.
*
* This class is thread-safe.
*
* @author [email protected]
*/
public class VersionTracker implements IVersionTracker
{
private static final Logger LOG = LogManager.getLogger(VersionTracker.class);
private final IVersionStorage versionStorage;
private final IVersionProvider versionProvider;
private final Object THREAD_POOL_LOCK = new Object();
// @GuardedBy( THREAD_POOL_LOCK )
private int maxConcurrentThreads = 1;
// @GuardedBy( THREAD_POOL_LOCK )
private ThreadPoolExecutor threadPool;
private final SharedLockCache lockCache;
private final ThreadFactory threadFactory = new ThreadFactory()
{
private final ThreadGroup threadGroup = new ThreadGroup(Thread.currentThread().getThreadGroup(),"versiontracker-request-threads" );
private final AtomicInteger threadId = new AtomicInteger(0);
public Thread newThread(Runnable r)
{
final Thread t = new Thread(threadGroup,r);
t.setDaemon( true );
t.setName("versiontracker-request-thread-"+threadId.incrementAndGet());
return t;
}
};
public VersionTracker(IVersionStorage versionStorage,IVersionProvider versionProvider,SharedLockCache lockCache) {
Validate.notNull(versionProvider,"versionProvider must not be NULL");
Validate.notNull(versionStorage,"versionStorage must not be NULL");
Validate.notNull(lockCache,"lockCache must not be NULL");
this.versionProvider = versionProvider;
this.versionStorage = versionStorage;
this.lockCache = lockCache;
}
/**
* Try to retrieve version information for a given artifact.
*
* @param artifacts
* @param requiresUpdate
* @return
* @throws InterruptedException
*/
@Override
public Map getVersionInfo(List artifacts, BiPredicate requiresUpdate) throws InterruptedException
{
final Map resultMap = new HashMap<>();
final ZonedDateTime now = ZonedDateTime.now();
final DynamicLatch stopLatch = new DynamicLatch();
for ( Artifact artifact : artifacts )
{
try
{
lockCache.doWhileLocked( artifact, () ->
{
if ( LOG.isDebugEnabled() )
{
LOG.debug( "getVersionInfo(): Looking for " + artifact + " in version storage" );
}
final Optional result = versionStorage.getVersionInfo( artifact );
if ( result.isEmpty() || requiresUpdate.test( result.get(), artifact ) )
{
LOG.debug( "getVersionInfo(): Got " + (result.isPresent() ? "outdated" : "no") + " metadata for " + artifact + " yet,fetching it" );
updateArtifactFromServer( artifact, result.orElse( null ), resultMap, stopLatch, now );
}
else
{
LOG.debug( "getVersionInfo(): [from storage] " + result.get() );
synchronized( resultMap )
{
resultMap.put( artifact, result.get().copy() );
}
versionStorage.updateLastRequestDate( artifact, now );
}
} );
} catch(InterruptedException e) {
LOG.error("getVersionInfo(): Caught unexpected exception "+e.getMessage()+" while handling "+artifact,e);
throw e;
} catch (Exception e) {
LOG.error("getVersionInfo(): Caught unexpected exception "+e.getMessage()+" while handling "+artifact,e);
throw new RuntimeException("Uncaught exception "+e.getMessage()+" while handling "+artifact,e);
}
}
stopLatch.await();
return resultMap;
}
@Override
public VersionInfo forceUpdate(String groupId, String artifactId) throws InterruptedException
{
Validate.notBlank( groupId, "groupId must not be null or blank");
Validate.notBlank( artifactId, "artifactId must not be null or blank");
final Artifact a = new Artifact();
a.groupId = groupId;
a.artifactId = artifactId;
LOG.info( "forceUpdate(): Forced update of " + a );
final DynamicLatch updateFinished = new DynamicLatch();
final Map map = new HashMap<>();
updateArtifactFromServer( a, null, map, updateFinished, ZonedDateTime.now() );
updateFinished.await();
return map.get( a );
}
private void updateArtifactFromServer(Artifact artifact,
VersionInfo existing,
Map resultMap,
DynamicLatch stopLatch,
ZonedDateTime now)
{
stopLatch.inc();
boolean submitted = false;
try
{
if ( LOG.isDebugEnabled() ) {
LOG.debug("updateArtifact(): About to submit task for "+artifact);
}
submit( () ->
{
try
{
if ( LOG.isDebugEnabled() ) {
LOG.debug("updateArtifact(): Waiting to lock "+artifact);
}
lockCache.doWhileLocked(artifact,() ->
{
if ( LOG.isDebugEnabled() ) {
LOG.debug("updateArtifact(): Got lock for "+artifact);
}
final VersionInfo newInfo;
if ( existing != null)
{
if ( LOG.isDebugEnabled() ) {
LOG.debug("updateArtifact(): [outdated] Trying to update metadata for "+artifact);
}
newInfo = existing;
}
else
{
if ( LOG.isDebugEnabled() ) {
LOG.debug("updateArtifact(): [missing] Trying to update metadata for "+artifact);
}
newInfo = new VersionInfo();
newInfo.creationDate = now;
newInfo.artifact = artifact;
}
newInfo.lastRequestDate = now;
synchronized(resultMap) {
resultMap.put(artifact,newInfo);
}
try
{
versionProvider.update( newInfo, true );
}
catch (Exception e)
{
if ( LOG.isDebugEnabled() ) {
LOG.error("updateArtifact(): Caught "+e.getMessage()+" while updating "+artifact,e);
} else {
LOG.error("updateArtifact(): Caught "+e.getMessage()+" while updating "+artifact+": "+e.getMessage());
}
}
finally {
versionStorage.saveOrUpdate( newInfo );
}
});
}
catch (Throwable e)
{
LOG.error("updateArtifact(): Caught "+e.getMessage()+" while updating "+artifact,e);
if ( e instanceof Error t) {
throw t;
}
}
finally
{
stopLatch.dec();
}
});
if ( LOG.isDebugEnabled() ) {
LOG.debug("updateArtifact(): Submitted task for "+artifact);
}
submitted = true;
}
finally
{
if ( ! submitted ) {
stopLatch.dec();
}
}
}
protected static final class DynamicLatch
{
private int count = 0;
public void inc() {
synchronized(this) {
count++;
}
}
public void dec()
{
synchronized(this)
{
if ( count == 0 ) {
LOG.error("dec(): Internal error, count < 0");
throw new IllegalStateException("count < 0 ?");
}
count--;
notifyAll();
}
}
public void await() throws InterruptedException
{
synchronized(this)
{
while ( count > 0 )
{
if ( LOG.isDebugEnabled() ) {
LOG.debug("await(): Waiting for "+count+" threads to finish");
}
wait();
}
}
}
}
private void submit(Runnable r)
{
synchronized( THREAD_POOL_LOCK )
{
if ( threadPool == null )
{
LOG.info("setMaxConcurrentThreads(): Using "+maxConcurrentThreads+" threads to retrieve artifact metadata.");
final BlockingQueue workingQueue = new ArrayBlockingQueue<>(200);
threadPool = new ThreadPoolExecutor( maxConcurrentThreads, maxConcurrentThreads, 60 , TimeUnit.SECONDS,
workingQueue,threadFactory, new ThreadPoolExecutor.CallerRunsPolicy() );
}
threadPool.submit( r );
}
}
/**
* Sets the maximum number of threads to use when retrieving artifact metadata.
*
* Note that the concurrency on an HTTP request level is determined by the
* {@link IVersionProvider}, not by this setting.
*
* @param maxConcurrentThreads
*/
public void setMaxConcurrentThreads(int maxConcurrentThreads)
{
if ( maxConcurrentThreads < 1 )
{
throw new IllegalArgumentException("maxConcurrentThreads needs to be >= 1");
}
synchronized( THREAD_POOL_LOCK)
{
final boolean poolChanged = this.maxConcurrentThreads != maxConcurrentThreads;
this.maxConcurrentThreads = maxConcurrentThreads;
if ( poolChanged && threadPool != null )
{
threadPool.shutdown();
threadPool = null;
}
}
}
@Override
public int getMaxConcurrentThreads()
{
synchronized( THREAD_POOL_LOCK )
{
return maxConcurrentThreads;
}
}
@Override
public void close()
{
synchronized( THREAD_POOL_LOCK )
{
if ( threadPool != null )
{
LOG.debug("close(): Shutting down thread pool");
threadPool.shutdown();
threadPool = null;
}
}
}
@Override
public IVersionStorage getStorage() {
return versionStorage;
}
@Override
public IVersionProvider getVersionProvider() {
return versionProvider;
}
}