Index: CMDIValidator/trunk/cmdi-validator-core/src/main/java/eu/clarin/cmdi/validator/plugins/CheckPidPluginFactory.java
===================================================================
--- CMDIValidator/trunk/cmdi-validator-core/src/main/java/eu/clarin/cmdi/validator/plugins/CheckPidPluginFactory.java	(revision 5337)
+++ CMDIValidator/trunk/cmdi-validator-core/src/main/java/eu/clarin/cmdi/validator/plugins/CheckPidPluginFactory.java	(revision 5338)
@@ -1,19 +1,8 @@
 package eu.clarin.cmdi.validator.plugins;
 
-import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
 
-import org.apache.http.HttpResponse;
 import org.apache.http.HttpStatus;
-import org.apache.http.StatusLine;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpHead;
-import org.apache.http.client.params.ClientPNames;
-import org.apache.http.client.utils.HttpClientUtils;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.params.CoreProtocolPNames;
-import org.apache.http.params.HttpParams;
-import org.apache.http.util.EntityUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -31,5 +20,4 @@
 import eu.clarin.cmdi.validator.CMDIValidatorInitException;
 import eu.clarin.cmdi.validator.CMDIValidatorResultImpl;
-import eu.clarin.cmdi.validator.utils.LRUCache;
 
 
@@ -41,6 +29,8 @@
     private static final String HDL_PROXY_HOST = "hdl.handle.net";
     private static final String URN_SCHEME = "urn";
+    @SuppressWarnings("unused")
     private static final Logger logger =
             LoggerFactory.getLogger(CheckPidPlugin.class);
+    private final HandleResolver resolver = new HandleResolver();
 
 
@@ -52,20 +42,8 @@
 
     public class CheckPidPlugin implements CMDIValidationPlugin {
-        private final HttpClient httpClient;
         private final XPathExecutable xpath;
-        private final LRUCache<URI, Integer> cache =
-                new LRUCache<URI, Integer>(4*4096);
 
         private CheckPidPlugin(final Processor processor)
                 throws CMDIValidatorInitException {
-            this.httpClient = new DefaultHttpClient();
-
-            final HttpParams params = this.httpClient.getParams();
-            params.setParameter(CoreProtocolPNames.USER_AGENT,
-                    CheckPidPlugin.class.getPackage().getName() + "/0.0.1");
-            params.setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, Boolean.TRUE);
-            params.setBooleanParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, true);
-            params.setIntParameter(ClientPNames.MAX_REDIRECTS, 16);
-
             try {
                 final XPathCompiler compiler = processor.newXPathCompiler();
@@ -151,11 +129,5 @@
         private void checkHandle2(URI uri, CMDIValidatorResultImpl result)
                 throws CMDIValidatorException {
-            Integer code = cache.get(uri);
-            if (code == null) {
-                code = checkHandle3(uri, result);
-                // cache result
-                cache.put(uri, code);
-            }
-
+            int code = resolver.resolve(uri);
             switch (code) {
             case HttpStatus.SC_OK:
@@ -181,33 +153,4 @@
             }
         }
-
-
-        private int checkHandle3(URI uri, CMDIValidatorResultImpl result)
-                throws CMDIValidatorException {
-            HttpHead request = null;
-            HttpResponse response = null;
-            try {
-                request = new HttpHead(uri);
-                response = httpClient.execute(request);
-
-                final StatusLine status = response.getStatusLine();
-                logger.debug("checked handle: file={}, uri={}, status={}",
-                        result.getFile(), uri, status.getStatusCode());
-                return status.getStatusCode();
-            } catch (IOException e) {
-                throw new CMDIValidatorException("error performing handle check", e);
-            } finally {
-                if (response != null) {
-                    try {
-                        EntityUtils.consume(response.getEntity());
-                    } catch (IOException ex) {
-                        /* IGNORE */
-                    }
-
-                    /* make sure to release allocated resources */
-                    HttpClientUtils.closeQuietly(response);
-                }
-            }
-        }
     }
 
Index: CMDIValidator/trunk/cmdi-validator-core/src/main/java/eu/clarin/cmdi/validator/plugins/HandleResolver.java
===================================================================
--- CMDIValidator/trunk/cmdi-validator-core/src/main/java/eu/clarin/cmdi/validator/plugins/HandleResolver.java	(revision 5338)
+++ CMDIValidator/trunk/cmdi-validator-core/src/main/java/eu/clarin/cmdi/validator/plugins/HandleResolver.java	(revision 5338)
@@ -0,0 +1,154 @@
+package eu.clarin.cmdi.validator.plugins;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpHead;
+import org.apache.http.client.params.ClientPNames;
+import org.apache.http.client.utils.HttpClientUtils;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.params.CoreProtocolPNames;
+import org.apache.http.params.HttpParams;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import eu.clarin.cmdi.validator.CMDIValidatorException;
+import eu.clarin.cmdi.validator.utils.LRUCache;
+
+public class HandleResolver {
+    private static final Logger logger = LoggerFactory.getLogger(HandleResolver.class);
+    public final int ERROR   = -1;
+    private final LRUCache<URI, Integer> cache =
+            new LRUCache<URI, Integer>(16 * 1024);
+    private final Set<URI> pending;
+    private final Object waiter = new Object();
+    private long cacheHits   = 0;
+    private long cacheMisses = 0;
+
+    public HandleResolver() {
+        this(Runtime.getRuntime().availableProcessors());
+    }
+
+
+    public HandleResolver(final int threads) {
+        this.pending = new HashSet<URI>(threads * 2);
+    }
+
+
+    public long getCacheHits() {
+        return cacheHits;
+    }
+
+
+    public long getCacheMisses() {
+        return cacheMisses;
+    }
+
+
+    public int resolve(final URI handle) throws CMDIValidatorException {
+        if (handle == null) {
+            throw new NullPointerException("handle == null");
+        }
+        logger.debug("resolving '{}'", handle);
+        for (;;) {
+            boolean doResolve = false;
+            synchronized (cache) {
+                final Integer cached = cache.get(handle);
+                if (cached != null) {
+                    logger.trace("got cached result for '{}': {}",
+                            handle, cached);
+                    cacheHits++;
+                    return cached.intValue();
+                }
+
+                synchronized (pending) {
+                    if (!pending.contains(handle)) {
+                        cacheMisses++;
+                        doResolve = true;
+                        pending.add(handle);
+                    }
+                } // synchronized (pending)
+            } // synchronized (cache)
+
+
+            // either resolve in this thread of wait for pending resolve result
+            if (doResolve) {
+                int result = ERROR;
+                try {
+                    final HttpClient httpClient = newHttpClient();
+                    try {
+                        result = doResolve(handle, httpClient);
+                    } finally {
+                        HttpClientUtils.closeQuietly(httpClient);
+                    }
+                } catch (Throwable e) {
+                    throw new CMDIValidatorException(
+                            "error while resolving handle '" + handle + "'", e);
+                } finally {
+                    // cache result and notify other threads
+                    synchronized (cache) {
+                        logger.trace("caching result {} for '{}'",
+                                result, handle);
+                        cache.put(handle, Integer.valueOf(result));
+                        synchronized (pending) {
+                            pending.remove(handle);
+                            synchronized (waiter) {
+                                waiter.notifyAll();
+                            } // synchronized (waiter)
+                        } // synchronized (pending)
+                    } // synchronized (cache)
+                }
+                return result;
+            } else {
+                try {
+                    synchronized (waiter) {
+                        waiter.wait();
+                    } // synchronized (waiter)
+                } catch (InterruptedException e) {
+                    return ERROR;
+                }
+            }
+        } // for
+    }
+
+
+    private int doResolve(final URI handle, final HttpClient httpClient)
+            throws CMDIValidatorException {
+        logger.trace("performing HTTP request for '{}'", handle);
+        HttpHead request = null;
+        HttpResponse response = null;
+        try {
+            request = new HttpHead(handle);
+            response = httpClient.execute(request);
+
+            final StatusLine status = response.getStatusLine();
+            return status.getStatusCode();
+        } catch (IOException e) {
+            if (request != null) {
+                request.abort();
+            }
+            throw new CMDIValidatorException("error resolving handle '" +
+                    handle + "'", e);
+        } finally {
+            /* make sure to release allocated resources */
+            HttpClientUtils.closeQuietly(response);
+        }
+    }
+
+
+    private HttpClient newHttpClient() {
+        final HttpClient client = new DefaultHttpClient();
+        final HttpParams params = client.getParams();
+        params.setParameter(CoreProtocolPNames.USER_AGENT,
+                getClass().getName() + "/1.0");
+        params.setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, Boolean.TRUE);
+        params.setBooleanParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, true);
+        params.setIntParameter(ClientPNames.MAX_REDIRECTS, 16);
+        return client;
+    }
+
+} // class HandleResolver
