Index: SRUAggregator/trunk/aggregator_development.yml
===================================================================
--- SRUAggregator/trunk/aggregator_development.yml	(revision 5954)
+++ SRUAggregator/trunk/aggregator_development.yml	(revision 5957)
@@ -14,6 +14,6 @@
 
   ENDPOINTS_SCAN_TIMEOUT_MS: 60000
-  ENDPOINTS_SEARCH_TIMEOUT_MS: 10000
-  EXECUTOR_SHUTDOWN_TIMEOUT_MS: 100
+  ENDPOINTS_SEARCH_TIMEOUT_MS: 30000
+  EXECUTOR_SHUTDOWN_TIMEOUT_MS: 1000
 
 # use the simple server factory, run on a single port
@@ -32,5 +32,5 @@
   loggers:
     eu.clarin.sru.fcs.aggregator: INFO
-    eu.clarin.sru.client: INFO
+    eu.clarin.sru.client: ERROR
 
   appenders:
Index: SRUAggregator/trunk/pom.xml
===================================================================
--- SRUAggregator/trunk/pom.xml	(revision 5954)
+++ SRUAggregator/trunk/pom.xml	(revision 5957)
@@ -8,5 +8,5 @@
 	<groupId>eu.clarin.sru.fcs</groupId>
 	<artifactId>Aggregator2</artifactId>
-	<version>2.0.0-alpha-14</version>
+	<version>2.0.0-alpha-15</version>
 	<name>FCS Aggregator</name>
 
Index: SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/app/Aggregator.java
===================================================================
--- SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/app/Aggregator.java	(revision 5954)
+++ SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/app/Aggregator.java	(revision 5957)
@@ -13,4 +13,5 @@
 import eu.clarin.sru.client.SRUVersion;
 import eu.clarin.sru.client.fcs.ClarinFCSClientBuilder;
+import eu.clarin.sru.client.fcs.ClarinFCSEndpointDescriptionParser;
 import eu.clarin.sru.fcs.aggregator.client.ThrottledClient;
 import eu.clarin.sru.fcs.aggregator.scan.Corpus;
@@ -25,4 +26,5 @@
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -86,6 +88,5 @@
  * @author edima
  *
- * TODO: support new spec-compatible centres, see Oliver's mail
- * (use SRUClient's extraResponseData POJOs)
+ * DONE: support new spec-compatible centre
  *
  * TODO: version page: credits, open source, see vcr/version page
@@ -96,6 +97,4 @@
  *
  * TODO: disable popups easily
- *
- * TODO: Fix activeSearch memory leak (gc searches older than...)
  *
  * TODO: Use weblicht with results
@@ -209,8 +208,9 @@
 				.setSocketTimeout(params.ENDPOINTS_SCAN_TIMEOUT_MS)
 				.addDefaultDataViewParsers()
+				.registerExtraResponseDataParser(
+						new ClarinFCSEndpointDescriptionParser())
 				.enableLegacySupport()
 				.buildThreadedClient(),
-				params.SCAN_MAX_CONCURRENT_REQUESTS_PER_ENDPOINT,
-				params.SEARCH_MAX_CONCURRENT_REQUESTS_PER_ENDPOINT
+				params.SCAN_MAX_CONCURRENT_REQUESTS_PER_ENDPOINT
 		);
 		sruSearchClient = new ThrottledClient(
@@ -221,5 +221,4 @@
 				.enableLegacySupport()
 				.buildThreadedClient(),
-				params.SCAN_MAX_CONCURRENT_REQUESTS_PER_ENDPOINT,
 				params.SEARCH_MAX_CONCURRENT_REQUESTS_PER_ENDPOINT
 		);
@@ -269,4 +268,16 @@
 			Search sr = new Search(sruSearchClient, version, searchStatsAtom.get(),
 					corpora, searchString, searchLang, 1, maxRecords);
+			if ((activeSearches.size() % 100) == 0) {
+				List<Long> toBeRemoved = new ArrayList<Long>();
+				long t0 = System.currentTimeMillis();
+				for (Map.Entry<Long, Search> e : activeSearches.entrySet()) {
+					if (t0 - e.getValue().getCreatedAt() > 1800 * 1000) {
+						toBeRemoved.add(e.getKey());
+					}
+				}
+				for (Long l : toBeRemoved) {
+					activeSearches.remove(l);
+				}
+			}
 			activeSearches.put(sr.getId(), sr);
 			return sr;
Index: SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/client/ExplainOperation.java
===================================================================
--- SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/client/ExplainOperation.java	(revision 5957)
+++ SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/client/ExplainOperation.java	(revision 5957)
@@ -0,0 +1,63 @@
+package eu.clarin.sru.fcs.aggregator.client;
+
+import eu.clarin.sru.client.SRUCallback;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUExplainRequest;
+import eu.clarin.sru.client.SRUExplainResponse;
+import eu.clarin.sru.client.SRUThreadedClient;
+
+/**
+ *
+ * @author edima
+ */
+class ExplainOperation implements Operation<SRUExplainRequest, SRUExplainResponse>, SRUCallback<SRUExplainRequest, SRUExplainResponse> {
+	SRUExplainRequest request;
+	ThrottledClient.ExplainCallback callback;
+	GenericClient client;
+	OpStats stats;
+
+	public ExplainOperation(SRUExplainRequest request, ThrottledClient.ExplainCallback callback) {
+		this.request = request;
+		this.callback = callback;
+		this.stats = new OpStats();
+	}
+
+	@Override
+	public void setClient(GenericClient client) {
+		this.client = client;
+	}
+
+	@Override
+	public void execute(SRUThreadedClient sruClient) {
+		try {
+			sruClient.explain(request, this);
+		} catch (SRUClientException xc) {
+			onError(request, xc);
+		}
+	}
+
+	@Override
+	public void onSuccess(SRUExplainResponse response) {
+		try {
+			stats.finishedTime = System.currentTimeMillis();
+			callback.onSuccess(response, stats);
+		} finally {
+			client.executeNextOperationOfEndpoint(request.getBaseURI());
+		}
+	}
+
+	@Override
+	public void onError(SRUExplainRequest request, SRUClientException error) {
+		try {
+			stats.finishedTime = System.currentTimeMillis();
+			callback.onError(request, error, stats);
+		} finally {
+			client.executeNextOperationOfEndpoint(request.getBaseURI());
+		}
+	}
+
+	@Override
+	public OpStats stats() {
+		return stats;
+	}
+}
Index: SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/client/GenericClient.java
===================================================================
--- SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/client/GenericClient.java	(revision 5954)
+++ SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/client/GenericClient.java	(revision 5957)
@@ -13,8 +13,9 @@
  */
 class GenericClient {
+
 	private final SRUThreadedClient sruClient;
 	public final int maxConcurrentRequests;
+
 	// queue of operations waiting for execution
-
 	static class ExecQueue {
 
@@ -61,3 +62,11 @@
 		}
 	}
+
+	public void shutdown() {
+		sruClient.shutdown();
+	}
+
+	public void shutdownNow() {
+		sruClient.shutdownNow();
+	}
 }
Index: SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/client/ThrottledClient.java
===================================================================
--- SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/client/ThrottledClient.java	(revision 5954)
+++ SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/client/ThrottledClient.java	(revision 5957)
@@ -2,4 +2,6 @@
 
 import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUExplainRequest;
+import eu.clarin.sru.client.SRUExplainResponse;
 import eu.clarin.sru.client.SRUScanRequest;
 import eu.clarin.sru.client.SRUScanResponse;
@@ -20,4 +22,11 @@
 	}
 
+	public interface ExplainCallback {
+
+		void onSuccess(SRUExplainResponse response, Stats stats);
+
+		void onError(SRUExplainRequest request, SRUClientException error, Stats stats);
+	}
+
 	public interface ScanCallback {
 
@@ -34,28 +43,28 @@
 	}
 
-	SRUThreadedClient sruClient;
-	GenericClient scanClient;
-	GenericClient searchClient;
+	GenericClient client;
 
-	public ThrottledClient(SRUThreadedClient sruClient, int maxConcurrentScanRequests, int maxConcurrentSearchRequests) {
-		this.sruClient = sruClient;
-		this.scanClient = new GenericClient(sruClient, maxConcurrentScanRequests);
-		this.searchClient = new GenericClient(sruClient, maxConcurrentSearchRequests);
+	public ThrottledClient(SRUThreadedClient sruClient, int maxConcurrentRequests) {
+		this.client = new GenericClient(sruClient, maxConcurrentRequests);
+	}
+
+	public void explain(SRUExplainRequest request, ExplainCallback callback) {
+		client.execute(request.getBaseURI(), new ExplainOperation(request, callback));
 	}
 
 	public void scan(SRUScanRequest request, ScanCallback callback) {
-		scanClient.execute(request.getBaseURI(), new ScanOperation(request, callback));
+		client.execute(request.getBaseURI(), new ScanOperation(request, callback));
 	}
 
 	public void searchRetrieve(SRUSearchRetrieveRequest request, SearchCallback callback) {
-		searchClient.execute(request.getBaseURI(), new SearchOperation(request, callback));
+		client.execute(request.getBaseURI(), new SearchOperation(request, callback));
 	}
 
 	public void shutdown() {
-		sruClient.shutdown();
+		client.shutdown();
 	}
 
 	public void shutdownNow() {
-		sruClient.shutdownNow();
+		client.shutdownNow();
 	}
 }
Index: SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/CenterRegistryForTesting.java
===================================================================
--- SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/CenterRegistryForTesting.java	(revision 5954)
+++ SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/CenterRegistryForTesting.java	(revision 5957)
@@ -3,5 +3,4 @@
 import java.util.ArrayList;
 import java.util.List;
-import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -19,38 +18,23 @@
     private static final Logger logger = Logger.getLogger(CenterRegistryForTesting.class.getName());
     private boolean hasChildrenLoaded = false;
-    private List<Institution> centers = new ArrayList<Institution>();
-    private static final String[] INSTITUTION_URLS = new String[]{
-		"http://130.183.206.32/restxml/3",
-		"http://130.183.206.32/restxml/5"
-    };
-    private static final String[] INSTITUTION_NAMES = new String[]{
-		"IDS",
-		"MPI"
-    };
-    private static final String[][] INSTITUTION_ENDPOINTS = new String[][]{
-        new String[]{ // MPI endpoints, contact person Olha 
-			"https://clarin.phonetik.uni-muenchen.de/BASSRU/",
-			"http://lux17.mpi.nl/cqltest"
-        }
-    };
+	private List<Institution> centers = new ArrayList<Institution>() {
+		{
+			Institution inst = new Institution("test_IDS", null);
+			inst.addEndpoint("https://clarin.ids-mannheim.de/digibibsru-new");
+			add(inst);
+		}
+	};
 
     @Override
     public boolean hasCQLInstitutionsLoaded() {
-        return hasChildrenLoaded;
+		return true;
     }
 
     @Override
     public void loadCQLInstitutions() {
-        if (hasChildrenLoaded) {
-            return;
-        }
-        hasChildrenLoaded = true;
-        loadCQLInstitutionsForTesting();
-        logger.log(Level.FINE, "Number of Centers: {0}", centers.size());
     }
 
     @Override
     public List<Institution> getCQLInstitutions() {
-        loadCQLInstitutions();
         return centers;
     }
@@ -58,5 +42,4 @@
     @Override
     public Institution getCQLInstitution(int index) {
-        loadCQLInstitutions();
         if (index >= centers.size()) {
             return null;
@@ -64,15 +47,3 @@
         return centers.get(index);
     }
-
-    private void loadCQLInstitutionsForTesting() {
-        for (int i = 0; i < INSTITUTION_ENDPOINTS.length; i++) {
-            Institution institution = new Institution(INSTITUTION_NAMES[i], INSTITUTION_URLS[i]);
-            for (int j = 0; j < INSTITUTION_ENDPOINTS.length; j++) {
-				institution.addEndpoint(INSTITUTION_ENDPOINTS[i][j]);
-            } 
-			if (!institution.getEndpoints().isEmpty()) {
-				centers.add(institution);
-			}
-        }
-    }
 }
Index: SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/Corpora.java
===================================================================
--- SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/Corpora.java	(revision 5954)
+++ SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/Corpora.java	(revision 5957)
@@ -78,5 +78,5 @@
 			@Override
 			public void call(Corpus c) {
-				if (c.getEndpointUrl().equals(endpointUrl)) {
+				if (c.getEndpoint().getUrl().equals(endpointUrl)) {
 					found.add(c);
 				}
Index: SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/Corpus.java
===================================================================
--- SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/Corpus.java	(revision 5954)
+++ SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/Corpus.java	(revision 5957)
@@ -18,4 +18,5 @@
  */
 public class Corpus {
+
 	private static final org.slf4j.Logger log = LoggerFactory.getLogger(Corpus.class);
 
@@ -24,8 +25,7 @@
 
 	private Institution institution;
-	private String endpointUrl;
+	private Endpoint endpoint;
 	private String handle;
 	private Integer numberOfRecords;
-	private String displayName;
 	private Set<String> languages = new HashSet<String>();
 	private String landingPage;
@@ -37,11 +37,11 @@
 	}
 
-	public Corpus(Institution institution, String endpointUrl) {
+	public Corpus(Institution institution, Endpoint endpoint) {
 		this.institution = institution;
-		this.endpointUrl = endpointUrl;
+		this.endpoint = endpoint;
 	}
 
 	public String getId() {
-		return endpointUrl + "#" + handle;
+		return endpoint.getUrl() + "#" + handle;
 	}
 
@@ -61,5 +61,4 @@
 	}
 
-
 	public String getHandle() {
 		return handle;
@@ -78,18 +77,10 @@
 	}
 
-	public String getDisplayName() {
-		return displayName;
+	public Endpoint getEndpoint() {
+		return endpoint;
 	}
 
-	public void setDisplayName(String displayName) {
-		this.displayName = displayName;
-	}
-
-	public String getEndpointUrl() {
-		return endpointUrl;
-	}
-
-	public void setEndpointUrl(String endpointUrl) {
-		this.endpointUrl = endpointUrl;
+	public void setEndpoint(Endpoint endpoint) {
+		this.endpoint = endpoint;
 	}
 
@@ -146,5 +137,5 @@
 	public int hashCode() {
 		int hash = 3;
-		hash = 29 * hash + (this.endpointUrl != null ? this.endpointUrl.hashCode() : 0);
+		hash = 29 * hash + (this.endpoint != null ? this.endpoint.hashCode() : 0);
 		hash = 29 * hash + (this.handle != null ? this.handle.hashCode() : 0);
 		return hash;
@@ -160,5 +151,5 @@
 		}
 		final Corpus other = (Corpus) obj;
-		if ((this.endpointUrl == null) ? (other.endpointUrl != null) : !this.endpointUrl.equals(other.endpointUrl)) {
+		if ((this.endpoint == null) ? (other.endpoint != null) : !this.endpoint.equals(other.endpoint)) {
 			return false;
 		}
@@ -171,5 +162,5 @@
 	@Override
 	public String toString() {
-		return "Corpus{" + "endpointUrl=" + endpointUrl + ", handle=" + handle + '}';
+		return "Corpus{" + "endpoint=" + endpoint + ", handle=" + handle + '}';
 	}
 
Index: SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/Endpoint.java
===================================================================
--- SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/Endpoint.java	(revision 5957)
+++ SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/Endpoint.java	(revision 5957)
@@ -0,0 +1,55 @@
+package eu.clarin.sru.fcs.aggregator.scan;
+
+/**
+ * @author edima
+ */
+public class Endpoint {
+
+	private String url;
+	private FCSProtocolVersion protocol;
+
+	// for JSON deserialization
+	public Endpoint() {
+	}
+
+	public Endpoint(String url, FCSProtocolVersion protocol) {
+		this.url = url;
+		this.protocol = protocol;
+	}
+
+	public String getUrl() {
+		return url;
+	}
+
+	public FCSProtocolVersion getProtocol() {
+		return protocol;
+	}
+
+	public void setUrl(String url) {
+		this.url = url;
+	}
+
+	public void setProtocol(FCSProtocolVersion protocol) {
+		this.protocol = protocol;
+	}
+
+	@Override
+	public String toString() {
+		return url;
+	}
+
+	@Override
+	public int hashCode() {
+		https://primes.utm.edu/lists/small/1000.txt
+		return url.hashCode() * 1049;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (!(obj instanceof Endpoint)) {
+			return false;
+		}
+		Endpoint e = (Endpoint) obj;
+		return e.url.equals(url) && e.protocol.equals(protocol);
+	}
+}
Index: SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/FCSProtocolVersion.java
===================================================================
--- SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/FCSProtocolVersion.java	(revision 5957)
+++ SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/FCSProtocolVersion.java	(revision 5957)
@@ -0,0 +1,10 @@
+package eu.clarin.sru.fcs.aggregator.scan;
+
+/**
+ *
+ * @author edima
+ */
+public enum FCSProtocolVersion {
+	LEGACY, // https://trac.clarin.eu/wiki/FCS-specification
+	VERSION_1, // https://trac.clarin.eu/wiki/FCS/Specification
+}
Index: SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/Institution.java
===================================================================
--- SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/Institution.java	(revision 5954)
+++ SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/Institution.java	(revision 5957)
@@ -13,5 +13,5 @@
 	private String name;
 	private String link;
-	private Set<String> endpoints;
+	private Set<Endpoint> endpoints;
 
 	// for JSON deserialization
@@ -22,9 +22,9 @@
 		this.name = name;
 		this.link = link;
-		this.endpoints = new LinkedHashSet<String>();
+		this.endpoints = new LinkedHashSet<Endpoint>();
 	}
 
 	public String addEndpoint(String endpointUrl) {
-		endpoints.add(endpointUrl);
+		endpoints.add(new Endpoint(endpointUrl, FCSProtocolVersion.LEGACY));
 		return endpointUrl;
 	}
@@ -38,5 +38,5 @@
 	}
 
-	public Set<String> getEndpoints() {
+	public Set<Endpoint> getEndpoints() {
 		return this.endpoints;
 	}
Index: SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/ScanCrawlTask.java
===================================================================
--- SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/ScanCrawlTask.java	(revision 5954)
+++ SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/ScanCrawlTask.java	(revision 5957)
@@ -45,4 +45,11 @@
 			log.info("ScanCrawlTask: Initiating crawl");
 			CenterRegistry centerRegistry = new CenterRegistryLive(centerRegistryUrl, filter);
+			centerRegistry.getCQLInstitutions().add(0,
+					new Institution("test_IDS", null) {
+						{
+							addEndpoint("https://clarin.ids-mannheim.de/digibibsru-new");
+						}
+					});
+//			CenterRegistry centerRegistry = new CenterRegistryForTesting();
 			ScanCrawler scanCrawler = new ScanCrawler(centerRegistry, sruClient, cacheMaxDepth);
 
Index: SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/ScanCrawler.java
===================================================================
--- SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/ScanCrawler.java	(revision 5954)
+++ SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/ScanCrawler.java	(revision 5957)
@@ -4,11 +4,19 @@
 import eu.clarin.sru.client.SRUClientException;
 import eu.clarin.sru.client.SRUDiagnostic;
+import eu.clarin.sru.client.SRUExplainRequest;
+import eu.clarin.sru.client.SRUExplainResponse;
+import eu.clarin.sru.client.SRUExtraResponseData;
 import eu.clarin.sru.client.SRUScanRequest;
 import eu.clarin.sru.client.SRUScanResponse;
 import eu.clarin.sru.client.SRUTerm;
+import eu.clarin.sru.client.fcs.ClarinFCSEndpointDescription;
+import eu.clarin.sru.client.fcs.ClarinFCSEndpointDescription.ResourceInfo;
 import eu.clarin.sru.fcs.aggregator.client.ThrottledClient;
 import eu.clarin.sru.fcs.aggregator.util.SRUCQL;
 import eu.clarin.sru.fcs.aggregator.util.Throw;
 import java.net.SocketTimeoutException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
 import org.slf4j.LoggerFactory;
 import org.w3c.dom.DocumentFragment;
@@ -45,8 +53,7 @@
 		for (Institution institution : centerRegistry.getCQLInstitutions()) {
 			cache.addInstitution(institution);
-			Iterable<String> endpoints = institution.getEndpoints();
-			for (String endp : endpoints) {
-				ScanTask st = new ScanTask(institution, endp, null, cache, 0);
-				scanForCorpora(st);
+			Iterable<Endpoint> endpoints = institution.getEndpoints();
+			for (Endpoint endp : endpoints) {
+				new ExplainTask(institution, endp, cache).start();
 			}
 		}
@@ -60,34 +67,133 @@
 
 		log.info("done crawling");
+
+		for (Institution institution : centerRegistry.getCQLInstitutions()) {
+			Iterable<Endpoint> endpoints = institution.getEndpoints();
+			for (Endpoint endp : endpoints) {
+				log.info("inst/endpoint type: {} / {}", institution.getName(), endp.getProtocol());
+			}
+		}
+
 		return cache;
 	}
 
-	public Statistics getStatistics() {
-		return statistics;
-	}
-
-	private void scanForCorpora(ScanTask st) {
-		if (st.depth > maxDepth) {
+	class ExplainTask implements ThrottledClient.ExplainCallback {
+
+		final Institution institution;
+		final Endpoint endpoint;
+		final Corpora corpora;
+
+		ExplainTask(final Institution institution, final Endpoint endpoint, final Corpora corpora) {
+			this.institution = institution;
+			this.endpoint = endpoint;
+			this.corpora = corpora;
+		}
+
+		void start() {
+			SRUExplainRequest explainRequest = null;
+			try {
+				explainRequest = new SRUExplainRequest(endpoint.getUrl());
+				explainRequest.setExtraRequestData(SRUCQL.EXPLAIN_ASK_FOR_RESOURCES_PARAM, "true");
+				explainRequest.setParseRecordDataEnabled(true);
+			} catch (Throwable ex) {
+				log.error("Exception creating explain request for {}: {}", endpoint.getUrl(), ex.getMessage());
+				log.error("--> ", ex);
+			}
+			if (explainRequest == null) {
+				return;
+			}
+
+			log.info("{} Start explain: {}", latch.get(), endpoint.getUrl());
+			latch.increment();
+			sruClient.explain(explainRequest, this);
+		}
+
+		@Override
+		public void onSuccess(SRUExplainResponse response, ThrottledClient.Stats stats) {
+			try {
+				statistics.addEndpointDatapoint(institution, endpoint.getUrl(), stats.getQueueTime(), stats.getExecutionTime());
+				if (response != null && response.hasExtraResponseData()) {
+					for (SRUExtraResponseData data : response.getExtraResponseData()) {
+						if (data instanceof ClarinFCSEndpointDescription) {
+							endpoint.setProtocol(FCSProtocolVersion.VERSION_1);
+							ClarinFCSEndpointDescription desc = (ClarinFCSEndpointDescription) data;
+							addCorpora(corpora, institution, endpoint, desc.getResources(), null);
+						}
+					}
+				}
+
+				if (response != null && response.hasDiagnostics()) {
+					for (SRUDiagnostic d : response.getDiagnostics()) {
+						SRUExplainRequest request = response.getRequest();
+						Diagnostic diag = new Diagnostic(request.getBaseURI().toString(), null,
+								d.getURI(), d.getMessage(), d.getDetails());
+						statistics.addEndpointDiagnostic(institution, endpoint.getUrl(), diag);
+						log.info("Diagnostic: {} {}: {} {} {}", diag.getReqEndpointUrl(), diag.getReqContext(),
+								diag.getDgnUri(), diag.getDgnMessage(), diag.getDgnDiagnostic());
+					}
+				}
+
+				log.info("{} Finished explain, endpoint version is {}: {}", latch.get(), endpoint.getProtocol(), endpoint.getUrl());
+			} catch (Exception xc) {
+				log.error("{} Exception in explain callback {}", latch.get(), endpoint.getUrl());
+				log.error("--> ", xc);
+			} finally {
+				if (endpoint.getProtocol().equals(FCSProtocolVersion.LEGACY)) {
+					new ScanTask(institution, endpoint, null, corpora, 0).start();
+				}
+
+				latch.decrement();
+			}
+		}
+
+		@Override
+		public void onError(SRUExplainRequest request, SRUClientException error, ThrottledClient.Stats stats) {
+			try {
+				log.error("{} Error while explaining {}: {}", latch.get(), endpoint.getUrl(), error.getMessage());
+				statistics.addEndpointDatapoint(institution, endpoint.getUrl(), stats.getQueueTime(), stats.getExecutionTime());
+				statistics.addErrorDatapoint(institution, endpoint.getUrl(), error);
+				if (Throw.isCausedBy(error, SocketTimeoutException.class)) {
+					return;
+				}
+				log.error("--> " + request.getBaseURI() + " --> ", error);
+			} finally {
+				latch.decrement();
+			}
+		}
+	}
+
+	private static void addCorpora(Corpora corpora, Institution institution, Endpoint endpoint,
+			List<ResourceInfo> resources, Corpus parentCorpus) {
+		if (resources == null) {
 			return;
 		}
-
-		SRUScanRequest scanRequest = null;
-		try {
-			scanRequest = new SRUScanRequest(st.endpointUrl);
-			scanRequest.setScanClause(SRUCQL.SCAN_RESOURCE_PARAMETER
-					+ "=" + normalizeHandle(st.parentCorpus));
-			scanRequest.setExtraRequestData(SRUCQL.SCAN_RESOURCE_INFO_PARAMETER,
-					SRUCQL.SCAN_RESOURCE_INFO_PARAMETER_DEFAULT_VALUE);
-		} catch (Throwable ex) {
-			log.error("Exception creating scan request for {}: {}", st.endpointUrl, ex.getMessage());
-			log.error("--> ", ex);
-		}
-		if (scanRequest == null) {
-			return;
-		}
-
-		log.info("{} Start scan: {}#{}", latch.get(), st.endpointUrl, normalizeHandle(st.parentCorpus));
-		latch.increment();
-		sruClient.scan(scanRequest, st);
+		for (ResourceInfo ri : resources) {
+			Corpus c = new Corpus(institution, endpoint);
+			c.setHandle(ri.getPid());
+			c.setTitle(getBestValueFrom(ri.getTitle()));
+			c.setDescription(getBestValueFrom(ri.getTitle()));
+			c.setLanguages(new HashSet<String>(ri.getLanguages()));
+			c.setLandingPage(ri.getLandingPageURI());
+
+			if (corpora.addCorpus(c, parentCorpus)) {
+				addCorpora(corpora, institution, endpoint, ri.getSubResources(), c);
+			}
+		}
+	}
+
+	private static String getBestValueFrom(Map<String, String> map) {
+		String ret = map.get("en");
+		if (ret == null || ret.trim().isEmpty()) {
+			ret = map.get(null);
+		}
+		if (ret == null || ret.trim().isEmpty()) {
+			ret = map.get("de");
+		}
+		if (ret == null || ret.trim().isEmpty()) {
+			ret = map.size() > 0
+					? map.values().iterator().next()
+					: null;
+		}
+		return ret;
 	}
 
@@ -95,13 +201,13 @@
 
 		final Institution institution;
-		final String endpointUrl;
+		final Endpoint endpoint;
 		final Corpus parentCorpus;
 		final Corpora corpora;
 		final int depth;
 
-		ScanTask(final Institution institution, final String endpointUrl,
+		ScanTask(final Institution institution, final Endpoint endpoint,
 				final Corpus parentCorpus, final Corpora corpora, final int depth) {
 			this.institution = institution;
-			this.endpointUrl = endpointUrl;
+			this.endpoint = endpoint;
 			this.parentCorpus = parentCorpus;
 			this.corpora = corpora;
@@ -109,25 +215,48 @@
 		}
 
+		private void start() {
+			if (depth > maxDepth) {
+				return;
+			}
+
+			SRUScanRequest scanRequest = null;
+			try {
+				scanRequest = new SRUScanRequest(endpoint.getUrl());
+				scanRequest.setScanClause(SRUCQL.SCAN_RESOURCE_PARAMETER
+						+ "=" + normalizeHandle(parentCorpus));
+				scanRequest.setExtraRequestData(SRUCQL.SCAN_RESOURCE_INFO_PARAMETER,
+						SRUCQL.SCAN_RESOURCE_INFO_PARAMETER_DEFAULT_VALUE);
+			} catch (Throwable ex) {
+				log.error("Exception creating scan request for {}: {}", endpoint.getUrl(), ex.getMessage());
+				log.error("--> ", ex);
+			}
+			if (scanRequest == null) {
+				return;
+			}
+
+			log.info("{} Start scan: {}#{}", latch.get(), endpoint.getUrl(), normalizeHandle(parentCorpus));
+			latch.increment();
+			sruClient.scan(scanRequest, this);
+		}
+
 		@Override
 		public void onSuccess(SRUScanResponse response, ThrottledClient.Stats stats) {
 			try {
-				statistics.addEndpointDatapoint(institution, endpointUrl, stats.getQueueTime(), stats.getExecutionTime());
+				statistics.addEndpointDatapoint(institution, endpoint.getUrl(), stats.getQueueTime(), stats.getExecutionTime());
 				if (response != null && response.hasTerms()) {
 					for (SRUTerm term : response.getTerms()) {
 						if (term == null) {
-							log.warn("null term for scan at endpoint {}", endpointUrl);
+							log.warn("null term for scan at endpoint {}", endpoint.getUrl());
 						} else {
-							Corpus c = createCorpus(institution, endpointUrl, term);
+							Corpus c = createCorpus(institution, endpoint, term);
 							if (corpora.addCorpus(c, parentCorpus)) {
-								ScanTask st = new ScanTask(institution, endpointUrl, c, corpora, depth + 1);
-								scanForCorpora(st);
+								new ScanTask(institution, endpoint, c, corpora, depth + 1).start();
 							}
 						}
 					}
 				} else if (parentCorpus == null) {
-					Corpus c = createCorpus(institution, endpointUrl, null);
+					Corpus c = createCorpus(institution, endpoint, null);
 					if (corpora.addCorpus(c, parentCorpus)) {
-						ScanTask st = new ScanTask(institution, endpointUrl, c, corpora, depth + 1);
-						scanForCorpora(st);
+						new ScanTask(institution, endpoint, c, corpora, depth + 1).start();
 					}
 				}
@@ -140,5 +269,5 @@
 						Diagnostic diag = new Diagnostic(request.getBaseURI().toString(), handle,
 								d.getURI(), d.getMessage(), d.getDetails());
-						statistics.addEndpointDiagnostic(institution, endpointUrl, diag);
+						statistics.addEndpointDiagnostic(institution, endpoint.getUrl(), diag);
 						log.info("Diagnostic: {} {}: {} {} {}", diag.getReqEndpointUrl(), diag.getReqContext(),
 								diag.getDgnUri(), diag.getDgnMessage(), diag.getDgnDiagnostic());
@@ -146,7 +275,7 @@
 				}
 
-				log.info("{} Finished scan: {}#{}", latch.get(), endpointUrl, normalizeHandle(parentCorpus));
+				log.info("{} Finished scan: {}#{}", latch.get(), endpoint.getUrl(), normalizeHandle(parentCorpus));
 			} catch (Exception xc) {
-				log.error("{} Exception in callback {}#{}", latch.get(), endpointUrl, normalizeHandle(parentCorpus));
+				log.error("{} Exception in scan callback {}#{}", latch.get(), endpoint.getUrl(), normalizeHandle(parentCorpus));
 				log.error("--> ", xc);
 			} finally {
@@ -158,7 +287,7 @@
 		public void onError(SRUScanRequest request, SRUClientException error, ThrottledClient.Stats stats) {
 			try {
-				log.error("{} Error while scanning {}#{}: {}", latch.get(), endpointUrl, normalizeHandle(parentCorpus), error.getMessage());
-				statistics.addEndpointDatapoint(institution, endpointUrl, stats.getQueueTime(), stats.getExecutionTime());
-				statistics.addErrorDatapoint(institution, endpointUrl, error);
+				log.error("{} Error while scanning {}#{}: {}", latch.get(), endpoint.getUrl(), normalizeHandle(parentCorpus), error.getMessage());
+				statistics.addEndpointDatapoint(institution, endpoint.getUrl(), stats.getQueueTime(), stats.getExecutionTime());
+				statistics.addErrorDatapoint(institution, endpoint.getUrl(), error);
 				if (Throw.isCausedBy(error, SocketTimeoutException.class)) {
 					return;
@@ -171,4 +300,8 @@
 	}
 
+	public Statistics getStatistics() {
+		return statistics;
+	}
+
 	private static String normalizeHandle(Corpus corpus) {
 		if (corpus == null) {
@@ -182,10 +315,10 @@
 	}
 
-	private static Corpus createCorpus(Institution institution, String endpointUrl, SRUTerm term) {
-		Corpus c = new Corpus(institution, endpointUrl);
-		c.setDisplayName(term.getDisplayTerm());
+	private static Corpus createCorpus(Institution institution, Endpoint endpoint, SRUTerm term) {
+		Corpus c = new Corpus(institution, endpoint);
+		c.setTitle(term.getDisplayTerm());
 		String handle = term.getValue();
 		if (handle == null) {
-			log.error("null handle for corpus: {} : {}", endpointUrl, term.getDisplayTerm());
+			log.error("null handle for corpus: {} : {}", endpoint, term.getDisplayTerm());
 			handle = "";
 		}
Index: SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Exports.java
===================================================================
--- SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Exports.java	(revision 5954)
+++ SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Exports.java	(revision 5957)
@@ -286,5 +286,5 @@
             tc.createMatchesLayer("FCS", resultsProcessed.get(0).getSearchString());
             for (Result result : resultsProcessed) {
-                MatchedCorpus mCorpus = tc.getMatchesLayer().addCorpus(result.getCorpus().getDisplayName(), result.getCorpus().getHandle());
+				MatchedCorpus mCorpus = tc.getMatchesLayer().addCorpus(result.getCorpus().getTitle(), result.getCorpus().getHandle());
                 for (Kwic kwic : result.getKwics()) {
                     List<Token> tokens = new ArrayList<Token>();
Index: SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Request.java
===================================================================
--- SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Request.java	(revision 5954)
+++ SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Request.java	(revision 5957)
@@ -38,7 +38,3 @@
 		return searchString;
 	}
-
-	public boolean hasCorpusHandle() {
-		return corpus != null && corpus.getHandle() != null;
-	}
 }
Index: SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Search.java
===================================================================
--- SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Search.java	(revision 5954)
+++ SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Search.java	(revision 5957)
@@ -9,4 +9,5 @@
 import eu.clarin.sru.fcs.aggregator.client.ThrottledClient;
 import eu.clarin.sru.fcs.aggregator.scan.Corpus;
+import eu.clarin.sru.fcs.aggregator.scan.FCSProtocolVersion;
 import eu.clarin.sru.fcs.aggregator.scan.Statistics;
 import eu.clarin.sru.fcs.aggregator.util.SRUCQL;
@@ -35,4 +36,5 @@
 
 	private final Long id;
+	private final long createdAt = System.currentTimeMillis();
 	private final String searchLanguage;
 	private final List<Request> requests = Collections.synchronizedList(new ArrayList<Request>());
@@ -56,11 +58,14 @@
 		log.info("Executing search in '{}' query='{}' maxRecords='{}'", corpus, searchString, maxRecords);
 
-		SRUSearchRetrieveRequest searchRequest = new SRUSearchRetrieveRequest(corpus.getEndpointUrl());
+		SRUSearchRetrieveRequest searchRequest = new SRUSearchRetrieveRequest(corpus.getEndpoint().getUrl());
 		searchRequest.setVersion(version);
 		searchRequest.setMaximumRecords(maxRecords);
-		searchRequest.setRecordSchema(ClarinFCSRecordData.RECORD_SCHEMA);
+//		searchRequest.setRecordSchema(
+//				corpus.getEndpoint().getProtocol().equals(FCSProtocolVersion.LEGACY)
+//						? ClarinFCSRecordData.LEGACY_RECORD_SCHEMA
+//						: ClarinFCSRecordData.RECORD_SCHEMA);
 		searchRequest.setQuery("\"" + searchString + "\"");
 		searchRequest.setStartRecord(startRecord);
-		if (request.hasCorpusHandle()) {
+		if (corpus.getHandle() != null) {
 			searchRequest.setExtraRequestData(SRUCQL.SEARCH_CORPUS_HANDLE_PARAMETER, corpus.getHandle());
 		}
@@ -71,5 +76,5 @@
 				@Override
 				public void onSuccess(SRUSearchRetrieveResponse response, ThrottledClient.Stats stats) {
-					statistics.addEndpointDatapoint(corpus.getInstitution(), corpus.getEndpointUrl(), stats.getQueueTime(), stats.getExecutionTime());
+					statistics.addEndpointDatapoint(corpus.getInstitution(), corpus.getEndpoint().getUrl(), stats.getQueueTime(), stats.getExecutionTime());
 					results.add(new Result(request, response, null));
 					requests.remove(request);
@@ -78,6 +83,6 @@
 				@Override
 				public void onError(SRUSearchRetrieveRequest srureq, SRUClientException xc, ThrottledClient.Stats stats) {
-					statistics.addEndpointDatapoint(corpus.getInstitution(), corpus.getEndpointUrl(), stats.getQueueTime(), stats.getExecutionTime());
-					statistics.addErrorDatapoint(corpus.getInstitution(), corpus.getEndpointUrl(), xc);
+					statistics.addEndpointDatapoint(corpus.getInstitution(), corpus.getEndpoint().getUrl(), stats.getQueueTime(), stats.getExecutionTime());
+					statistics.addErrorDatapoint(corpus.getInstitution(), corpus.getEndpoint().getUrl(), xc);
 					results.add(new Result(request, null, xc));
 					requests.remove(request);
@@ -85,5 +90,5 @@
 			});
 		} catch (Throwable xc) {
-			log.error("SearchRetrieve error for " + corpus.getEndpointUrl(), xc);
+			log.error("SearchRetrieve error for " + corpus.getEndpoint().getUrl(), xc);
 		}
 		return request;
@@ -204,3 +209,7 @@
 		// nothing to do 
 	}
+
+	public long getCreatedAt() {
+		return createdAt;
+	}
 }
Index: SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/util/SRUCQL.java
===================================================================
--- SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/util/SRUCQL.java	(revision 5954)
+++ SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/util/SRUCQL.java	(revision 5957)
@@ -9,4 +9,6 @@
 
 	public static final String VERSION = "version";
+
+	public static final String EXPLAIN_ASK_FOR_RESOURCES_PARAM = "x-fcs-endpoint-description";
 
 	public static final String SEARCH_RETRIEVE = "searchRetrieve";
Index: SRUAggregator/trunk/src/main/resources/assets/index.html
===================================================================
--- SRUAggregator/trunk/src/main/resources/assets/index.html	(revision 5954)
+++ SRUAggregator/trunk/src/main/resources/assets/index.html	(revision 5957)
@@ -2,16 +2,17 @@
 <html>
 <head>
-	<title>Aggregator2</title>
+	<title>Aggregator - Federated Content Search</title>
 	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 	<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
 	<link href="lib/bootstrap.min.css" rel="stylesheet">
 	<link href="lib/font-awesome.min.css" rel="stylesheet">
 	<link href="CLARIN.css" rel="stylesheet">
 	<link href="base.css" rel="stylesheet">
-</head>
-<body>
-	<noscript>
-		<div>Federated Content Search Aggregator requires JavaScript to be enabled!</div>
-	</noscript>
+
+	<!--[if lt IE 9]>
+		<script src="http://cdnjs.cloudflare.com/ajax/libs/es5-shim/3.4.0/es5-shim.js"></script>
+		<script src="http://cdnjs.cloudflare.com/ajax/libs/es5-shim/3.4.0/es5-sham.js"></script>
+	<![endif]-->   
 
 	<style>
@@ -22,5 +23,5 @@
 			height:100%;
 		}
-		#container {
+		#main-container {
 			min-height:100%;
 			position:relative;
@@ -36,36 +37,15 @@
 		}
 	</style>
+</head>
 
-	<div id="container">
-		<div id="body">
-			<div id="reactMain">
-		</div>
-		<div id="footer">
-			<div class="container">
-				<div id="CLARIN_footer_left">
-						<a title="about" id="aboutlink" href="about"> 
-						<span class="glyphicon glyphicon-info-sign"></span>
-						<span>VERSION 2.0.0.Î±14</span>
-					</a>
-				</div>
-				<div id="CLARIN_footer_middle">
-					<a title="CLARIN ERIC" href="https://www.clarin.eu/">
-					<img src="img/clarindLogo.png" alt="CLARIN ERIC logo" style="height:80px"/>
-					</a>
-				</div>
-				<div id="CLARIN_footer_right">
-					<a title="contact" href="mailto:fcs@clarin.eu">
-						<span class="glyphicon glyphicon-envelope"></span>
-						<span> CONTACT</span>
-					</a>
-				</div>
-			</div>
-		</div>
+<body>
+	<noscript>
+		<div>Federated Content Search Aggregator requires JavaScript to be enabled!</div>
+	</noscript>
+	<div id="main-container">
+		<div id="body"></div>
+		<div id="footer"></div>
 	</div>
 
-	<!--[if lt IE 9]>
-		<script src="http://cdnjs.cloudflare.com/ajax/libs/es5-shim/3.4.0/es5-shim.js"></script>
-		<script src="http://cdnjs.cloudflare.com/ajax/libs/es5-shim/3.4.0/es5-sham.js"></script>
-	<![endif]-->   
 	<script src="lib/jquery.min.js"></script>
 	<script src="lib/react-with-addons.js"></script>
Index: SRUAggregator/trunk/src/main/resources/assets/js/corpora.js
===================================================================
--- SRUAggregator/trunk/src/main/resources/assets/js/corpora.js	(revision 5954)
+++ SRUAggregator/trunk/src/main/resources/assets/js/corpora.js	(revision 5957)
@@ -96,5 +96,5 @@
 		var querytokens = query.split(" ").filter(function(x){ return x.length > 0; });
 		this.props.corpora.recurse(function(corpus){
-			var title = corpus.title ? corpus.title : corpus.displayName;
+			var title = corpus.title;
 			querytokens.forEach(function(qtoken){
 				if (title && title.toLowerCase().indexOf(qtoken) >= 0) {
@@ -217,6 +217,5 @@
 		var expansive = corpus.expanded ? {overflow:'hidden'} 
 			: {whiteSpace:'nowrap', overflow:'hidden', textOverflow: 'ellipsis'};
-		var title = corpus.title || corpus.displayName;
-		return	React.createElement("div", {className: corpusContainerClass, key: corpus.displayName}, 
+		return	React.createElement("div", {className: corpusContainerClass, key: corpus.title}, 
 					React.createElement("div", {className: "row corpus", onClick: this.toggleExpansion.bind(this, corpus)}, 
 						React.createElement("div", {className: "col-sm-1 vcenter"}, 
@@ -228,5 +227,5 @@
 							React.createElement("div", {style: indent}, 
 								React.createElement("h3", {style: expansive}, 
-									title, 
+									corpus.title, 
 									 corpus.landingPage ? 
 										React.createElement("a", {href: corpus.landingPage, onClick: this.stop}, 
Index: SRUAggregator/trunk/src/main/resources/assets/js/corpora.jsx
===================================================================
--- SRUAggregator/trunk/src/main/resources/assets/js/corpora.jsx	(revision 5954)
+++ SRUAggregator/trunk/src/main/resources/assets/js/corpora.jsx	(revision 5957)
@@ -96,5 +96,5 @@
 		var querytokens = query.split(" ").filter(function(x){ return x.length > 0; });
 		this.props.corpora.recurse(function(corpus){
-			var title = corpus.title ? corpus.title : corpus.displayName;
+			var title = corpus.title;
 			querytokens.forEach(function(qtoken){
 				if (title && title.toLowerCase().indexOf(qtoken) >= 0) {
@@ -217,6 +217,5 @@
 		var expansive = corpus.expanded ? {overflow:'hidden'} 
 			: {whiteSpace:'nowrap', overflow:'hidden', textOverflow: 'ellipsis'};
-		var title = corpus.title || corpus.displayName;
-		return	<div className={corpusContainerClass} key={corpus.displayName}>
+		return	<div className={corpusContainerClass} key={corpus.title}>
 					<div className="row corpus" onClick={this.toggleExpansion.bind(this, corpus)}>
 						<div className="col-sm-1 vcenter">
@@ -228,5 +227,5 @@
 							<div style={indent}>
 								<h3 style={expansive}> 
-									{title}
+									{corpus.title}
 									{ corpus.landingPage ? 
 										<a href={corpus.landingPage} onClick={this.stop}>
Index: SRUAggregator/trunk/src/main/resources/assets/js/main.js
===================================================================
--- SRUAggregator/trunk/src/main/resources/assets/js/main.js	(revision 5954)
+++ SRUAggregator/trunk/src/main/resources/assets/js/main.js	(revision 5957)
@@ -54,4 +54,5 @@
 			};
 		}
+		// console.log("ajax", ajaxObject);
 		jQuery.ajax(ajaxObject);
 	},
@@ -269,5 +270,32 @@
 });
 
-React.render(React.createElement(Main, null), document.getElementById('reactMain') );
+var Footer = React.createClass({displayName: 'Footer',
+	render: function() {
+		return	(
+			React.createElement("div", {className: "container"}, 
+				React.createElement("div", {id: "CLARIN_footer_left"}, 
+						React.createElement("a", {title: "about", id: "aboutlink", href: "about"}, 
+						React.createElement("span", {className: "glyphicon glyphicon-info-sign"}), 
+						React.createElement("span", null, "VERSION 2.0.0.Î±15")
+					)
+				), 
+				React.createElement("div", {id: "CLARIN_footer_middle"}, 
+					React.createElement("a", {title: "CLARIN ERIC", href: "https://www.clarin.eu/"}, 
+					React.createElement("img", {src: "img/clarindLogo.png", alt: "CLARIN ERIC logo", style: {height:80}})
+					)
+				), 
+				React.createElement("div", {id: "CLARIN_footer_right"}, 
+					React.createElement("a", {title: "contact", href: "mailto:fcs@clarin.eu"}, 
+						React.createElement("span", {className: "glyphicon glyphicon-envelope"}), 
+						React.createElement("span", null, " CONTACT")
+					)
+				)
+			)
+		);
+	}
+});
+
+React.render(React.createElement(Main, null),  document.getElementById('body'));
+React.render(React.createElement(Footer, null), document.getElementById('footer') );
 
 })();
Index: SRUAggregator/trunk/src/main/resources/assets/js/main.jsx
===================================================================
--- SRUAggregator/trunk/src/main/resources/assets/js/main.jsx	(revision 5954)
+++ SRUAggregator/trunk/src/main/resources/assets/js/main.jsx	(revision 5957)
@@ -54,4 +54,5 @@
 			};
 		}
+		// console.log("ajax", ajaxObject);
 		jQuery.ajax(ajaxObject);
 	},
@@ -269,5 +270,32 @@
 });
 
-React.render(<Main />, document.getElementById('reactMain') );
+var Footer = React.createClass({
+	render: function() {
+		return	(
+			<div className="container">
+				<div id="CLARIN_footer_left">
+						<a title="about" id="aboutlink" href="about"> 
+						<span className="glyphicon glyphicon-info-sign"></span>
+						<span>VERSION 2.0.0.Î±15</span>
+					</a>
+				</div>
+				<div id="CLARIN_footer_middle">
+					<a title="CLARIN ERIC" href="https://www.clarin.eu/">
+					<img src="img/clarindLogo.png" alt="CLARIN ERIC logo" style={{height:80}}/>
+					</a>
+				</div>
+				<div id="CLARIN_footer_right">
+					<a title="contact" href="mailto:fcs@clarin.eu">
+						<span className="glyphicon glyphicon-envelope"></span>
+						<span> CONTACT</span>
+					</a>
+				</div>
+			</div>
+		);
+	}
+});
+
+React.render(<Main />,  document.getElementById('body'));
+React.render(<Footer />, document.getElementById('footer') );
 
 })();
Index: SRUAggregator/trunk/src/main/resources/assets/js/search.js
===================================================================
--- SRUAggregator/trunk/src/main/resources/assets/js/search.js	(revision 5954)
+++ SRUAggregator/trunk/src/main/resources/assets/js/search.js	(revision 5957)
@@ -52,7 +52,5 @@
 			return r;
 		}
-		var t1 = x.title ? x.title : x.displayName;
-		var t2 = y.title ? y.title : y.displayName;
-		return t1.toLowerCase().localeCompare(t2.toLowerCase()); 
+		return x.title.toLowerCase().localeCompare(y.title.toLowerCase()); 
 	};
 
@@ -177,4 +175,5 @@
 			corpora: new Corpora([], this.updateCorpora),
 			languageMap: {},
+			query: "",
 			language: this.anyLanguage,
 			languageFilter: 'byMeta',
@@ -214,6 +213,6 @@
 	},
 
-	search: function(query) {
-		// console.log(query);
+	search: function() {
+		var query = this.state.query;
 		if (!query) {
 			this.setState({ hits: this.nohits, searchId: null });
@@ -253,8 +252,7 @@
 					// console.log("new search in: " + this.timeout+ "ms");
 				} else {
-					// console.log("search ended");
+					console.log("search ended; hits:", json);
 				}
 				this.setState({hits:json});
-				// console.log("hits:", json);
 			}.bind(this),
 		});
@@ -316,4 +314,14 @@
 	},
 
+	handleChange: function(event) {
+    	this.setState({query: event.target.value});
+	},
+
+	handleKey: function(event) {
+    	if (event.keyCode==13) {
+    		this.search();
+    	}
+	},
+
 	renderAggregator: function() {
 		var layer = layerMap[this.state.searchLayerId];
@@ -327,5 +335,7 @@
 							), 
 
-							React.createElement(SearchBox, {search: this.search, placeholder: layer.searchPlaceholder}), 
+							React.createElement("input", {className: "form-control input-lg search", name: "query", type: "text", 
+								value: this.state.query, placeholder: this.props.placeholder, 
+								tabIndex: "1", onChange: this.handleChange, onKeyDown: this.handleKey}), 
 							React.createElement("div", {className: "input-group-btn"}, 
 								React.createElement("button", {className: "btn btn-default input-lg", type: "button", onClick: this.search}, 
@@ -496,43 +506,4 @@
 	}
 });
-/////////////////////////////////
-
-var SearchBox = React.createClass({displayName: 'SearchBox',
-	propTypes: {
-		search: PT.func.isRequired,
-		placeholder: PT.string.isRequired,
-	},
-
-	getInitialState: function () {
-		return {
-			query: "",
-		};
-	},
-
-	handleChange: function(event) {
-    	this.setState({query: event.target.value});
-	},
-
-	handleKey: function(event) {
-    	if (event.keyCode==13) {
-    		this.search();
-    	}
-	},
-
-	search: function() {
-		this.props.search(this.state.query);
-	},
-
-	render: function() {
-		return 	React.createElement("input", {className: "form-control input-lg search", 
-					name: "query", 
-					type: "text", 
-					value: this.state.query, 
-					placeholder: this.props.placeholder, 
-					tabIndex: "1", 
-					onChange: this.handleChange, 
-					onKeyDown: this.handleKey})  ;
-	}
-});
 
 /////////////////////////////////
@@ -584,5 +555,5 @@
 		var inline = {display:"inline-block"};
 		return	React.createElement("div", {style: inline}, 
-					React.createElement("span", {className: "corpusName"}, " ", corpus.title ? corpus.title : corpus.displayName), 
+					React.createElement("span", {className: "corpusName"}, " ", corpus.title), 
 					React.createElement("span", {className: "institutionName"}, " â ", corpus.institution.name)
 				);
@@ -592,6 +563,5 @@
 		var inline = {display:"inline-block"};
 		return	React.createElement("div", null, 
-					React.createElement(InfoPopover, {placement: "left", 
-							title: corpus.title ? corpus.title : corpus.displayName}, 
+					React.createElement(InfoPopover, {placement: "left", title: corpus.title}, 
 						React.createElement("dl", {className: "dl-horizontal"}, 
 							React.createElement("dt", null, "Institution"), 
@@ -625,5 +595,5 @@
 
 		return corpusHit.diagnostics.map(function(d) {
-			return 	React.createElement("div", {className: "alert alert-danger", role: "alert"}, 
+			return 	React.createElement("div", {className: "alert alert-danger", role: "alert", key: d.dgnUri}, 
 						d.dgnMessage, ": ", d.dgnDiagnostic
 					);
@@ -653,5 +623,5 @@
 			return false;
 		}
-		return 	React.createElement(Panel, {key: corpusHit.corpus.displayName, 
+		return 	React.createElement(Panel, {key: corpusHit.corpus.title, 
 						title: this.renderPanelTitle(corpusHit.corpus), 
 						info: this.renderPanelInfo(corpusHit.corpus)}, 
Index: SRUAggregator/trunk/src/main/resources/assets/js/search.jsx
===================================================================
--- SRUAggregator/trunk/src/main/resources/assets/js/search.jsx	(revision 5954)
+++ SRUAggregator/trunk/src/main/resources/assets/js/search.jsx	(revision 5957)
@@ -52,7 +52,5 @@
 			return r;
 		}
-		var t1 = x.title ? x.title : x.displayName;
-		var t2 = y.title ? y.title : y.displayName;
-		return t1.toLowerCase().localeCompare(t2.toLowerCase()); 
+		return x.title.toLowerCase().localeCompare(y.title.toLowerCase()); 
 	};
 
@@ -177,4 +175,5 @@
 			corpora: new Corpora([], this.updateCorpora),
 			languageMap: {},
+			query: "",
 			language: this.anyLanguage,
 			languageFilter: 'byMeta',
@@ -214,6 +213,6 @@
 	},
 
-	search: function(query) {
-		// console.log(query);
+	search: function() {
+		var query = this.state.query;
 		if (!query) {
 			this.setState({ hits: this.nohits, searchId: null });
@@ -253,8 +252,7 @@
 					// console.log("new search in: " + this.timeout+ "ms");
 				} else {
-					// console.log("search ended");
+					console.log("search ended; hits:", json);
 				}
 				this.setState({hits:json});
-				// console.log("hits:", json);
 			}.bind(this),
 		});
@@ -316,4 +314,14 @@
 	},
 
+	handleChange: function(event) {
+    	this.setState({query: event.target.value});
+	},
+
+	handleKey: function(event) {
+    	if (event.keyCode==13) {
+    		this.search();
+    	}
+	},
+
 	renderAggregator: function() {
 		var layer = layerMap[this.state.searchLayerId];
@@ -327,5 +335,7 @@
 							</span>
 
-							<SearchBox search={this.search} placeholder={layer.searchPlaceholder} />
+							<input className="form-control input-lg search" name="query" type="text"
+								value={this.state.query} placeholder={this.props.placeholder}
+								tabIndex="1" onChange={this.handleChange} onKeyDown={this.handleKey} />
 							<div className="input-group-btn">
 								<button className="btn btn-default input-lg" type="button" onClick={this.search}>
@@ -496,43 +506,4 @@
 	}
 });
-/////////////////////////////////
-
-var SearchBox = React.createClass({
-	propTypes: {
-		search: PT.func.isRequired,
-		placeholder: PT.string.isRequired,
-	},
-
-	getInitialState: function () {
-		return {
-			query: "",
-		};
-	},
-
-	handleChange: function(event) {
-    	this.setState({query: event.target.value});
-	},
-
-	handleKey: function(event) {
-    	if (event.keyCode==13) {
-    		this.search();
-    	}
-	},
-
-	search: function() {
-		this.props.search(this.state.query);
-	},
-
-	render: function() {
-		return 	<input className="form-control input-lg search" 
-					name="query" 
-					type="text"
-					value={this.state.query} 
-					placeholder={this.props.placeholder} 
-					tabIndex="1" 
-					onChange={this.handleChange}
-					onKeyDown={this.handleKey} />  ;
-	}
-});
 
 /////////////////////////////////
@@ -584,5 +555,5 @@
 		var inline = {display:"inline-block"};
 		return	<div style={inline}>
-					<span className="corpusName"> {corpus.title ? corpus.title : corpus.displayName}</span>
+					<span className="corpusName"> {corpus.title}</span>
 					<span className="institutionName"> â {corpus.institution.name}</span>
 				</div>;
@@ -592,6 +563,5 @@
 		var inline = {display:"inline-block"};
 		return	<div>
-					<InfoPopover placement="left" 
-							title={corpus.title ? corpus.title : corpus.displayName}>
+					<InfoPopover placement="left" title={corpus.title}>
 						<dl className="dl-horizontal">
 							<dt>Institution</dt>
@@ -625,5 +595,5 @@
 
 		return corpusHit.diagnostics.map(function(d) {
-			return 	<div className="alert alert-danger" role="alert">
+			return 	<div className="alert alert-danger" role="alert" key={d.dgnUri}>
 						{d.dgnMessage}{": "}{d.dgnDiagnostic}
 					</div>;
@@ -653,5 +623,5 @@
 			return false;
 		}
-		return 	<Panel key={corpusHit.corpus.displayName} 
+		return 	<Panel key={corpusHit.corpus.title} 
 						title={this.renderPanelTitle(corpusHit.corpus)} 
 						info={this.renderPanelInfo(corpusHit.corpus)}>
Index: SRUAggregator/trunk/src/test/java/eu/clarin/sru/fcs/aggregator/app/CQLEnumerationTest.java
===================================================================
--- SRUAggregator/trunk/src/test/java/eu/clarin/sru/fcs/aggregator/app/CQLEnumerationTest.java	(revision 5954)
+++ SRUAggregator/trunk/src/test/java/eu/clarin/sru/fcs/aggregator/app/CQLEnumerationTest.java	(revision 5957)
@@ -3,4 +3,5 @@
 import eu.clarin.sru.fcs.aggregator.scan.CenterRegistry;
 import eu.clarin.sru.fcs.aggregator.scan.CenterRegistryLive;
+import eu.clarin.sru.fcs.aggregator.scan.Endpoint;
 import eu.clarin.sru.fcs.aggregator.scan.Institution;
 import javax.naming.NamingException;
@@ -24,5 +25,5 @@
 			for (Institution institution : centerRegistry.getCQLInstitutions()) {
 				System.out.println(institution.getName() + ": ");
-				for (String e : institution.getEndpoints()) {
+				for (Endpoint e : institution.getEndpoints()) {
 					System.out.println("\t -> " + e);
 				}
Index: SRUAggregator/trunk/src/test/java/eu/clarin/sru/fcs/aggregator/app/ScanCrawlerTest.java
===================================================================
--- SRUAggregator/trunk/src/test/java/eu/clarin/sru/fcs/aggregator/app/ScanCrawlerTest.java	(revision 5954)
+++ SRUAggregator/trunk/src/test/java/eu/clarin/sru/fcs/aggregator/app/ScanCrawlerTest.java	(revision 5957)
@@ -30,5 +30,5 @@
 				new ClarinFCSClientBuilder()
 				.addDefaultDataViewParsers()
-				.buildThreadedClient(), 2, 2);
+				.buildThreadedClient(), 2);
 
 		try {
@@ -53,5 +53,5 @@
 			Assert.assertTrue("Description problem", tueRootCorpus.getDescription().contains(tueDescSubstring));
 			String tueNameSubstring = "TuebaDDC";
-			Assert.assertTrue("Name problem", tueRootCorpus.getDisplayName().contains(tueNameSubstring));
+			Assert.assertTrue("Name problem", tueRootCorpus.getTitle().contains(tueNameSubstring));
 			String tuePageSubstring = "sfs.uni-tuebingen.de";
 			Assert.assertTrue("Landing page problem", tueRootCorpus.getLandingPage().contains(tuePageSubstring));
