" +
+ "The Scholix Swagger API allows clients to run REST queries over the Scholexplorer index in order to fetch links matching given criteria. In the current version, clients can search for:" +
+ "
Links whose source object has a given PID or PID type
" +
+ "
Links whose source object has been published by a given data source (\"data source as publisher\")
" +
+ "
Links that were collected from a given data source (\"data source as provider\").
";
+
+
+ public static String SCHOLIX_MANAGER_COUNTER_NAME= "scholixLinkCounter";
+ public static final String SCHOLIX_MANAGER_TAG_NAME = "links";
+
+ public static String SCHOLIX_COUNTER_PREFIX = "scholix";
+
+
+
+
+}
diff --git a/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/ScholixAPIVersion.java b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/ScholixAPIVersion.java
new file mode 100644
index 00000000..265d33eb
--- /dev/null
+++ b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/ScholixAPIVersion.java
@@ -0,0 +1,8 @@
+package eu.dnetlib.scholix.api;
+
+public enum ScholixAPIVersion {
+
+ V1,
+ V2
+
+}
diff --git a/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/ScholixException.java b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/ScholixException.java
new file mode 100644
index 00000000..21d63b71
--- /dev/null
+++ b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/ScholixException.java
@@ -0,0 +1,35 @@
+package eu.dnetlib.scholix.api;
+
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+public class ScholixException extends Exception{
+
+ private static final long serialVersionUID = -3414428892721711308L;
+
+
+ public ScholixException() {
+ super();
+ }
+
+ public ScholixException(String message, Throwable cause, boolean enableSuppression,
+ boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+ public ScholixException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ScholixException(String message) {
+ super(message);
+ }
+
+ public ScholixException(Throwable cause) {
+ super(cause);
+ }
+
+
+}
diff --git a/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/TaggedCounter.java b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/TaggedCounter.java
new file mode 100644
index 00000000..8cf48fba
--- /dev/null
+++ b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/TaggedCounter.java
@@ -0,0 +1,33 @@
+package eu.dnetlib.scholix.api;
+
+import io.micrometer.core.instrument.Counter;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import java.util.HashMap;
+import java.util.Map;
+
+
+public class TaggedCounter {
+
+ private String name;
+ private String tagName;
+ private MeterRegistry registry;
+ private Map counters = new HashMap<>();
+
+
+ public TaggedCounter(String name, String tagName, MeterRegistry registry) {
+ this.name = name;
+ this.tagName = tagName;
+ this.registry = registry;
+ }
+
+
+ public void increment(String tagValue){
+ Counter counter = counters.get(tagValue);
+ if(counter == null) {
+ counter = Counter.builder(name).tags(tagName, tagValue).register(registry);
+ counters.put(tagValue, counter);
+ }
+ counter.increment();
+ }
+}
\ No newline at end of file
diff --git a/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/controller/DatasourceV1.java b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/controller/DatasourceV1.java
new file mode 100644
index 00000000..ea5f58a1
--- /dev/null
+++ b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/controller/DatasourceV1.java
@@ -0,0 +1,50 @@
+package eu.dnetlib.scholix.api.controller;
+
+import eu.dnetlib.common.controller.AbstractDnetController;
+import eu.dnetlib.dhp.schema.sx.api.model.v1.LinkPublisher;
+import eu.dnetlib.scholix.api.ScholixException;
+import eu.dnetlib.scholix.api.index.ScholixIndexManager;
+
+import io.micrometer.core.annotation.Timed;
+import io.swagger.annotations.Api;
+import io.swagger.v3.oas.annotations.Operation;
+import org.apache.commons.lang3.tuple.Pair;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+
+@RestController
+@RequestMapping("/v1")
+@Api(tags = {
+ "Datasources"
+})
+public class DatasourceV1 extends AbstractDnetController {
+
+ @Autowired
+ private ScholixIndexManager manager;
+
+ @Timed(value = "scholix.v1.datasources", description = "Time taken to return all datasources on Version 1.0 of Scholix")
+ @Operation(
+ summary = "Get all Datasources",
+ description = "returns a list of all datasources")
+ @GetMapping("/listDatasources")
+ public List getDatasources() throws ScholixException {
+
+
+ final List> result = manager.totalLinksByProvider(null);
+
+ if (result == null)
+ return new ArrayList<>();
+
+ return result.stream().map(p -> new LinkPublisher().name(p.getKey()).totalRelationships(p.getValue().intValue())).collect(Collectors.toList());
+
+
+ }
+}
diff --git a/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/controller/LinkProviderV2.java b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/controller/LinkProviderV2.java
new file mode 100644
index 00000000..ecce061e
--- /dev/null
+++ b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/controller/LinkProviderV2.java
@@ -0,0 +1,48 @@
+package eu.dnetlib.scholix.api.controller;
+import eu.dnetlib.scholix.api.ScholixException;
+import eu.dnetlib.scholix.api.index.ScholixIndexManager;
+import eu.dnetlib.dhp.schema.sx.api.model.v2.LinkProviderType;
+import io.swagger.annotations.Api;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import org.apache.commons.lang3.tuple.Pair;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RestController
+@RequestMapping("/v2")
+@Api(tags = {
+ "LinkProvider : Operation related to the Link Provider"
+})
+public class LinkProviderV2 {
+
+ @Autowired
+ ScholixIndexManager manager;
+
+ @Operation(
+ summary = "Get all Link Providers",
+ description = "Return a list of link provider and relative number of relations")
+ @GetMapping("/LinkProvider")
+ public List getLinkProviders(
+ @Parameter(in = ParameterIn.QUERY, description = "Filter the link provider name") @RequestParam(required = false) String name
+ ) throws ScholixException {
+
+ List> result = manager.totalLinksByProvider(name);
+
+ if (result==null)
+ return new ArrayList<>();
+
+ return result.stream().map(s -> new LinkProviderType().name(s.getLeft()).totalRelationships(s.getValue().intValue())).collect(Collectors.toList());
+
+
+
+ }
+}
diff --git a/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/controller/LinkPublisherV2.java b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/controller/LinkPublisherV2.java
new file mode 100644
index 00000000..e1a9210c
--- /dev/null
+++ b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/controller/LinkPublisherV2.java
@@ -0,0 +1,61 @@
+package eu.dnetlib.scholix.api.controller;
+import eu.dnetlib.scholix.api.ScholixException;
+import eu.dnetlib.scholix.api.index.ScholixIndexManager;
+import eu.dnetlib.dhp.schema.sx.api.model.v2.LinkProviderType;
+import io.swagger.annotations.Api;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import org.apache.commons.lang3.tuple.Pair;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RestController
+@RequestMapping("/v2/LinkPublisher")
+@Api(tags = {
+ "LinkPublisher : Operation related to the Link Publisher"
+})
+public class LinkPublisherV2 {
+
+ @Autowired
+ ScholixIndexManager manager;
+
+ @Operation(
+ summary = "Get All Publishers that provide source object",
+ description = "Return a List of all Publishers that provide source objects in Scholix " +
+ "links and the total number of links where the source object comes from this publisher")
+ @GetMapping("/inSource")
+ public List getInSource(
+ @Parameter(in = ParameterIn.QUERY, description = "Filter the link publisher name") @RequestParam(required = false) String name
+ ) throws ScholixException {
+ List> result = manager.totalLinksPublisher(ScholixIndexManager.RelationPrefix.source,name);
+
+ if (result==null)
+ return new ArrayList<>();
+
+ return result.stream().map(s -> new LinkProviderType().name(s.getLeft()).totalRelationships(s.getValue().intValue())).collect(Collectors.toList());
+ }
+
+ @Operation(
+ summary = "Get All Publishers that provide target object",
+ description = "Return a List of all Publishers that provide source objects in Scholix " +
+ "links and the total number of links where the target object comes from this publisher")
+ @GetMapping("/inTarget")
+ public List getInTarget(
+ @Parameter(in = ParameterIn.QUERY, description = "Filter the link publisher name") @RequestParam(required = false) String name
+ ) throws ScholixException {
+ List> result = manager.totalLinksPublisher(ScholixIndexManager.RelationPrefix.target,name);
+
+ if (result==null)
+ return new ArrayList<>();
+
+ return result.stream().map(s -> new LinkProviderType().name(s.getLeft()).totalRelationships(s.getValue().intValue())).collect(Collectors.toList());
+ }
+}
diff --git a/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/controller/ScholixControllerV1.java b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/controller/ScholixControllerV1.java
new file mode 100644
index 00000000..85c76531
--- /dev/null
+++ b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/controller/ScholixControllerV1.java
@@ -0,0 +1,114 @@
+package eu.dnetlib.scholix.api.controller;
+
+
+import eu.dnetlib.common.controller.AbstractDnetController;
+import eu.dnetlib.dhp.schema.sx.scholix.Scholix;
+import eu.dnetlib.scholix.api.ScholixException;
+import eu.dnetlib.scholix.api.index.ScholixIndexManager;
+import eu.dnetlib.dhp.schema.sx.api.model.v1.ScholixV1;
+import io.micrometer.core.annotation.Timed;
+import io.swagger.annotations.Api;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.commons.lang3.tuple.Pair;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import javax.validation.constraints.NotNull;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+
+@RestController
+@RequestMapping("/v1")
+@Api(tags = {"Scholix"})
+public class ScholixControllerV1 extends AbstractDnetController {
+
+ @Autowired
+ ScholixIndexManager manager;
+
+ @Operation(
+ summary = "Get all Scholix relation collected from a publisher",
+ description = "return a list of scholix object published from a specific publisher"
+ )
+ @GetMapping("/linksFromPublisher")
+ @Timed(value = "scholix.v1.linksFromPublisher", description = "Time taken to return links on Version 1.0 of Scholix collected from a publisher")
+ public List linksFromPublisher(
+ @Parameter(
+ in = ParameterIn.QUERY,
+ description = "Filter Scholix relationships collected from a publisher",
+ schema = @Schema(), required = true) String publisher,
+ @Parameter(in = ParameterIn.QUERY,
+ description = "The page number") @RequestParam(required = false) Integer page
+ ) throws ScholixException {
+
+ final int currentPage = page != null ? page : 0;
+
+ Pair> scholixResult =manager.linksFromPid(null,null,null,publisher,
+ null,null,null,null,null,null, currentPage
+ );
+ List scholixData = scholixResult.getValue();
+ if (scholixData== null)
+ return null;
+ return scholixData.stream().map(ScholixV1::fromScholix).collect(Collectors.toList());
+ }
+
+ @Operation(
+ summary = "Get all Scholix relation collected from a datasource",
+ description = "return a list of scholix object collected from a specific datasource"
+ )
+ @GetMapping("/linksFromDatasource")
+ @Timed(value = "scholix.v1.linksFromDatasource", description = "Time taken to return links on Version 1.0 of Scholix collected from a LinkProvider")
+ public List linksFromDatasource(
+ @Parameter(
+ in = ParameterIn.QUERY,
+ description = "Filter Scholix relationships collected from a LinkProvider",
+ schema = @Schema()) @NotNull String datasource,
+ @Parameter(in = ParameterIn.QUERY,
+ description = "The page number") @RequestParam(required = false) Integer page
+ ) throws ScholixException {
+
+ final int currentPage = page != null ? page : 0;
+ Pair> scholixResult =manager.linksFromPid(datasource,null,null,null,
+ null,null,null,null,null,null, currentPage
+ );
+ List scholixData = scholixResult.getValue();
+ if (scholixData== null)
+ return null;
+ return scholixData.stream().map(ScholixV1::fromScholix).collect(Collectors.toList());
+ }
+
+
+ @Operation(
+ summary = "Retrieve all scholix links from a persistent identifier",
+ description = "The linksFromPid endpoint returns a list of scholix object related from a specific persistent identifier"
+ )
+ @GetMapping("/linksFromPid")
+ @Timed(value = "scholix.v1.linksFromPid", description = "Time taken to return links on Version 1.0 of Scholix related from a specific persistent identifier")
+ public List linksFromPid(
+ @Parameter(in = ParameterIn.QUERY, description = "persistent Identifier") @NotNull String pid,
+ @Parameter(in = ParameterIn.QUERY, description = "Persistent Identifier Type") @RequestParam(required = false) String pidType,
+ @Parameter(in = ParameterIn.QUERY, description = "typology target filter should be publication, dataset or unknown") @RequestParam(required = false) String typologyTarget,
+ @Parameter(in = ParameterIn.QUERY, description = "a datasource provenance filter of the target relation") @RequestParam(required = false) String datasourceTarget,
+ @Parameter(in = ParameterIn.QUERY,
+ description = "The page number") @RequestParam(required = false) Integer page
+ ) throws ScholixException {
+
+ final int currentPage = page != null ? page : 0;
+ Pair> scholixResult =manager.linksFromPid(datasourceTarget,null,null,null,
+ typologyTarget,pid,pidType,null,null,null, currentPage
+ );
+ List scholixData = scholixResult.getValue();
+ if (scholixData== null)
+ return null;
+ return scholixData.stream().map(ScholixV1::fromScholix).collect(Collectors.toList());
+ }
+
+
+
+}
diff --git a/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/controller/ScholixControllerV2.java b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/controller/ScholixControllerV2.java
new file mode 100644
index 00000000..a0d5d3b2
--- /dev/null
+++ b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/controller/ScholixControllerV2.java
@@ -0,0 +1,77 @@
+package eu.dnetlib.scholix.api.controller;
+
+
+import eu.dnetlib.common.controller.AbstractDnetController;
+import eu.dnetlib.dhp.schema.sx.api.model.v2.PageResultType;
+import eu.dnetlib.dhp.schema.sx.api.model.v2.ScholixType;
+import eu.dnetlib.dhp.schema.sx.scholix.Scholix;
+import eu.dnetlib.scholix.api.ScholixException;
+import eu.dnetlib.scholix.api.index.ScholixIndexManager;
+import io.micrometer.core.annotation.Timed;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RestController
+@RequestMapping("/v2")
+@Api(tags = {
+ "Links : Operation related to the Scholix Links"
+})
+public class ScholixControllerV2 extends AbstractDnetController {
+
+ @Autowired
+ private ScholixIndexManager manager;
+
+ @Timed(value = "scholix.v2.links", description = "Time taken to return links on Version 2.0 of Scholix")
+ @ApiOperation("Get Scholix Links")
+ @GetMapping("/Links")
+ public PageResultType links(
+ @Parameter(in = ParameterIn.QUERY,
+ description = "Filter Scholix relationships collected from a LinkProvider") String linkProvider,
+ @Parameter(in = ParameterIn.QUERY,
+ description = "Filter Scholix relationships having a target pid") String targetPid,
+ @Parameter(in = ParameterIn.QUERY,
+ description = "Filter Scholix relationships having a target pid type") String targetPidType,
+ @Parameter(in = ParameterIn.QUERY,
+ description = "Filter Scholix relationships having a target published in a Publisher named targetPublisher") String targetPublisher,
+ @Parameter(in = ParameterIn.QUERY,
+ description = "Filter Scholix relationships having a target type (literature, dataset, unknown)") String targetType,
+ @Parameter(in = ParameterIn.QUERY,
+ description = "Filter Scholix relationships having a source pid") String sourcePid,
+ @Parameter(in = ParameterIn.QUERY,
+ description = "Filter Scholix relationships having a source pid type") String sourcePidType,
+ @Parameter(in = ParameterIn.QUERY,
+ description = "Filter Scholix relationships having a source published in a Publisher named sourcePublisher") String sourcePublisher,
+ @Parameter(in = ParameterIn.QUERY,
+ description = "Filter Scholix relationships having a source type (literature, dataset, unknown)") String sourceType,
+ @Parameter(in = ParameterIn.QUERY,
+ description = "Filter scholix Links having collected after this date") String harvestedAfter,
+ @Parameter(in = ParameterIn.QUERY,
+ description = "select page of result") Integer page) throws Exception {
+
+ if (StringUtils.isEmpty(sourcePid) && StringUtils.isEmpty(targetPid) && StringUtils.isEmpty(sourcePublisher)&& StringUtils.isEmpty(targetPublisher)&& StringUtils.isEmpty(linkProvider))
+ throw new ScholixException("The method requires one of the following parameters: sourcePid, targetPid, sourcePublisher, targetPublisher, linkProvider");
+
+ try {
+ final int currentPage = page != null ? page : 0;
+ Pair> scholixResult = manager.linksFromPid( linkProvider, targetPid, targetPidType, targetPublisher, targetType, sourcePid, sourcePidType, sourcePublisher, sourceType, harvestedAfter, currentPage);
+ final PageResultType pageResult = new PageResultType();
+ pageResult.setTotalPages(scholixResult.getLeft().intValue() / 10);
+ pageResult.setTotalLinks(scholixResult.getLeft().intValue());
+ pageResult.setResult(scholixResult.getRight().stream().map(ScholixType::fromScholix).collect(Collectors.toList()));
+ return pageResult;
+ } catch (Throwable e) {
+ throw new ScholixException("Error on requesting url ", e);
+ }
+ }
+}
diff --git a/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/index/ElasticSearchClientFactory.java b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/index/ElasticSearchClientFactory.java
new file mode 100644
index 00000000..b00b6dc3
--- /dev/null
+++ b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/index/ElasticSearchClientFactory.java
@@ -0,0 +1,74 @@
+package eu.dnetlib.scholix.api.index;
+
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.pool2.PooledObject;
+import org.apache.commons.pool2.PooledObjectFactory;
+import org.apache.commons.pool2.impl.DefaultPooledObject;
+import org.elasticsearch.client.RequestOptions;
+import org.elasticsearch.client.RestHighLevelClient;
+import org.springframework.data.elasticsearch.client.ClientConfiguration;
+import org.springframework.data.elasticsearch.client.RestClients;
+import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
+
+/**
+ * The type Elastic search client factory.
+ */
+public class ElasticSearchClientFactory implements PooledObjectFactory> {
+
+ private final ElasticSearchProperties elasticSearchProperties;
+
+
+ /**
+ * Instantiates a new Elastic search client factory.
+ *
+ * @param elasticSearchProperties the elastic search properties
+ */
+ public ElasticSearchClientFactory(final ElasticSearchProperties elasticSearchProperties){
+ this.elasticSearchProperties = elasticSearchProperties;
+
+ }
+
+ public PooledObject> makeObject() throws Exception {
+
+ final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
+ .connectedTo(elasticSearchProperties.getClusterNodes().split(","))
+ .withConnectTimeout(elasticSearchProperties.getConnectionTimeout())
+ .withSocketTimeout(elasticSearchProperties.getSocketTimeout())
+ .build();
+ RestHighLevelClient cc = RestClients.create(clientConfiguration).rest();
+
+ return new DefaultPooledObject<>(new ImmutablePair<>(cc, new ElasticsearchRestTemplate(cc)));
+ }
+
+ public void destroyObject(PooledObject> pooledObject) throws Exception {
+ RestHighLevelClient client = pooledObject.getObject().getLeft();
+ if(client!=null&&client.ping(RequestOptions.DEFAULT)){
+ try {
+ client.close();
+ }catch (Exception e){
+ //ignore
+ }
+ }
+ }
+
+ public boolean validateObject(PooledObject> pooledObject) {
+ RestHighLevelClient client = pooledObject.getObject().getLeft();
+ try {
+ return client.ping(RequestOptions.DEFAULT);
+ }catch(Exception e){
+ return false;
+ }
+ }
+
+ public void activateObject(PooledObject> pooledObject) throws Exception {
+ RestHighLevelClient client = pooledObject.getObject().getLeft();
+ boolean response = client.ping(RequestOptions.DEFAULT);
+ }
+
+ public void passivateObject(PooledObject> pooledObject) throws Exception {
+ //nothing
+ }
+
+
+}
\ No newline at end of file
diff --git a/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/index/ElasticSearchPool.java b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/index/ElasticSearchPool.java
new file mode 100644
index 00000000..04a88afb
--- /dev/null
+++ b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/index/ElasticSearchPool.java
@@ -0,0 +1,34 @@
+package eu.dnetlib.scholix.api.index;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.elasticsearch.client.RestHighLevelClient;
+import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
+
+/**
+ * The type Elastic search pool.
+ */
+public class ElasticSearchPool extends Pool> {
+
+ private final ElasticSearchProperties elasticSearchProperties;
+
+ /**
+ * Instantiates a new Elastic search pool.
+ *
+ * @param elasticSearchProperties the elastic search properties
+ */
+ public ElasticSearchPool(ElasticSearchProperties elasticSearchProperties){
+ super(elasticSearchProperties, new ElasticSearchClientFactory(elasticSearchProperties));
+ this.elasticSearchProperties = elasticSearchProperties;
+ }
+
+ /**
+ * Gets elastic search properties.
+ *
+ * @return the elastic search properties
+ */
+ public ElasticSearchProperties getElasticSearchProperties() {
+ return elasticSearchProperties;
+ }
+}
+
+
diff --git a/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/index/ElasticSearchProperties.java b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/index/ElasticSearchProperties.java
new file mode 100644
index 00000000..15f53154
--- /dev/null
+++ b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/index/ElasticSearchProperties.java
@@ -0,0 +1,104 @@
+package eu.dnetlib.scholix.api.index;
+
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * The type Elastic search properties.
+ */
+@Component("elasticSearchProperties")
+@ConfigurationProperties(prefix = "scholix.elastic")
+public class ElasticSearchProperties extends GenericObjectPoolConfig {
+
+ @NotNull
+ private String clusterNodes;
+ @NotNull
+ private String indexName;
+ @NotNull
+ private long connectionTimeout;
+ @NotNull
+ private long socketTimeout;
+
+ /**
+ * Gets cluster nodes.
+ *
+ * @return the cluster nodes
+ */
+ public String getClusterNodes() {
+ return clusterNodes;
+ }
+
+ /**
+ * Sets cluster nodes.
+ *
+ * @param clusterNodes the cluster nodes
+ * @return the cluster nodes
+ */
+ public ElasticSearchProperties setClusterNodes(String clusterNodes) {
+ this.clusterNodes = clusterNodes;
+ return this;
+ }
+
+ /**
+ * Gets index name.
+ *
+ * @return the index name
+ */
+ public String getIndexName() {
+ return indexName;
+ }
+
+ /**
+ * Sets index name.
+ *
+ * @param indexName the index name
+ * @return the index name
+ */
+ public ElasticSearchProperties setIndexName(String indexName) {
+ this.indexName = indexName;
+ return this;
+ }
+
+ /**
+ * Gets connection timeout.
+ *
+ * @return the connection timeout
+ */
+ public long getConnectionTimeout() {
+ return connectionTimeout;
+ }
+
+ /**
+ * Sets connection timeout.
+ *
+ * @param connectionTimeout the connection timeout
+ * @return the connection timeout
+ */
+ public ElasticSearchProperties setConnectionTimeout(long connectionTimeout) {
+ this.connectionTimeout = connectionTimeout;
+ return this;
+ }
+
+ /**
+ * Gets socket timeout.
+ *
+ * @return the socket timeout
+ */
+ public long getSocketTimeout() {
+ return socketTimeout;
+ }
+
+ /**
+ * Sets socket timeout.
+ *
+ * @param socketTimeout the socket timeout
+ * @return the socket timeout
+ */
+ public ElasticSearchProperties setSocketTimeout(long socketTimeout) {
+ this.socketTimeout = socketTimeout;
+ return this;
+ }
+}
diff --git a/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/index/Pool.java b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/index/Pool.java
new file mode 100644
index 00000000..b5e3849f
--- /dev/null
+++ b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/index/Pool.java
@@ -0,0 +1,231 @@
+package eu.dnetlib.scholix.api.index;
+
+import eu.dnetlib.scholix.api.ScholixException;
+import org.apache.commons.pool2.PooledObjectFactory;
+import org.apache.commons.pool2.impl.GenericObjectPool;
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+
+/**
+ * When using the Java High Level REST Client provided by the Elasticsearch official website, it is found that there is no in the client API.
+ * Connecting to connect the pool, create a new connection every time, this is impact in high concurrency situation, so it is ready to be on the client
+ * API increases the concept of pool.
+ *
+ * Fortunately, we don't need to turn your weight to write the implementation of the connection pool, because Apache provides us with the general framework of the connection pool.
+ * Commons-pool2, and we only need to implement some logic according to the frame design. Used in the REDIS client API
+ * Jedispool is based on Commons-pool2 implementation.
+ *
+ * Let's take a look at how to achieve it.
+ *
+ * First we have to create a pool class, this pool introduces GenericObjectPool in Commons-pool2 through dependent manner. In this class
+ * In, we define how to borrow objects and returns objects from the pool.
+ *
+ * @param the type parameter
+ */
+public class Pool implements Cloneable {
+
+ /**
+ * The Internal pool.
+ */
+ protected GenericObjectPool internalPool ;
+
+ /**
+ * Instantiates a new Pool.
+ */
+ public Pool(){
+ super();
+ }
+
+ /**
+ * Instantiates a new Pool.
+ *
+ * @param poolConfig the pool config
+ * @param factory the factory
+ */
+ public Pool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory factory){
+ initPool(poolConfig, factory);
+ }
+
+ /**
+ * Init pool.
+ *
+ * @param poolConfig the pool config
+ * @param factory the factory
+ */
+ public void initPool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory factory) {
+
+ if (this.internalPool != null) {
+ try {
+ closeInternalPool();
+ } catch (Exception e) {
+ }
+ }
+
+ this.internalPool = new GenericObjectPool(factory, poolConfig);
+ }
+
+ /**
+ * Close internal pool.
+ *
+ * @throws ScholixException the scholix exception
+ */
+ protected void closeInternalPool() throws ScholixException {
+ try {
+ internalPool.close();
+ } catch (Exception e) {
+ throw new ScholixException("Could not destroy the pool", e);
+ }
+ }
+
+ /**
+ * Gets resource.
+ *
+ * @return the resource
+ * @throws ScholixException the scholix exception
+ */
+ public T getResource() throws ScholixException {
+ try {
+ return internalPool.borrowObject();
+ } catch (Exception e) {
+ throw new ScholixException("Could not get a resource from the pool", e);
+ }
+ }
+
+
+ /**
+ * Return resource.
+ *
+ * @param resource the resource
+ * @throws ScholixException the scholix exception
+ */
+ public void returnResource(final T resource) throws ScholixException {
+ if (resource != null) {
+ returnResourceObject(resource);
+ }
+ }
+
+ private void returnResourceObject(final T resource) throws ScholixException {
+ if (resource == null) {
+ return;
+ }
+ try {
+ internalPool.returnObject(resource);
+ } catch (Exception e) {
+ throw new ScholixException("Could not return the resource to the pool", e);
+ }
+ }
+
+ /**
+ * Return broken resource.
+ *
+ * @param resource the resource
+ * @throws ScholixException the scholix exception
+ */
+ public void returnBrokenResource(final T resource) throws ScholixException {
+ if (resource != null) {
+ returnBrokenResourceObject(resource);
+ }
+ }
+
+ private void returnBrokenResourceObject(T resource) throws ScholixException {
+ try {
+ internalPool.invalidateObject(resource);
+ } catch (Exception e) {
+ throw new ScholixException("Could not return the resource to the pool", e);
+ }
+ }
+
+ /**
+ * Destroy.
+ *
+ * @throws ScholixException the scholix exception
+ */
+ public void destroy() throws ScholixException {
+ closeInternalPool();
+ }
+
+
+ /**
+ * Gets num active.
+ *
+ * @return the num active
+ */
+ public int getNumActive() {
+ if (poolInactive()) {
+ return -1;
+ }
+
+ return this.internalPool.getNumActive();
+ }
+
+ /**
+ * Gets num idle.
+ *
+ * @return the num idle
+ */
+ public int getNumIdle() {
+ if (poolInactive()) {
+ return -1;
+ }
+
+ return this.internalPool.getNumIdle();
+ }
+
+ /**
+ * Gets num waiters.
+ *
+ * @return the num waiters
+ */
+ public int getNumWaiters() {
+ if (poolInactive()) {
+ return -1;
+ }
+
+ return this.internalPool.getNumWaiters();
+ }
+
+ /**
+ * Gets mean borrow wait time millis.
+ *
+ * @return the mean borrow wait time millis
+ */
+ public long getMeanBorrowWaitTimeMillis() {
+ if (poolInactive()) {
+ return -1;
+ }
+
+ return this.internalPool.getMeanBorrowWaitTimeMillis();
+ }
+
+ /**
+ * Gets max borrow wait time millis.
+ *
+ * @return the max borrow wait time millis
+ */
+ public long getMaxBorrowWaitTimeMillis() {
+ if (poolInactive()) {
+ return -1;
+ }
+
+ return this.internalPool.getMaxBorrowWaitTimeMillis();
+ }
+
+ private boolean poolInactive() {
+ return this.internalPool == null || this.internalPool.isClosed();
+ }
+
+ /**
+ * Add objects.
+ *
+ * @param count the count
+ * @throws Exception the exception
+ */
+ public void addObjects(int count) throws Exception {
+ try {
+ for (int i = 0; i < count; i++) {
+ this.internalPool.addObject();
+ }
+ } catch (Exception e) {
+ throw new Exception("Error trying to add idle objects", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/index/ScholixIndexManager.java b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/index/ScholixIndexManager.java
new file mode 100644
index 00000000..23aa2d47
--- /dev/null
+++ b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/index/ScholixIndexManager.java
@@ -0,0 +1,315 @@
+package eu.dnetlib.scholix.api.index;
+
+
+import eu.dnetlib.dhp.schema.sx.scholix.Scholix;
+import eu.dnetlib.scholix.api.ScholixException;
+import eu.dnetlib.scholix.api.TaggedCounter;
+import io.micrometer.core.annotation.Timed;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.lucene.search.join.ScoreMode;
+import org.elasticsearch.action.search.SearchType;
+import org.elasticsearch.client.RestHighLevelClient;
+import org.elasticsearch.index.query.*;
+import org.elasticsearch.search.aggregations.Aggregation;
+import org.elasticsearch.search.aggregations.AggregationBuilders;
+import org.elasticsearch.search.aggregations.Aggregations;
+import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
+import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
+import org.springframework.data.elasticsearch.core.SearchHit;
+import org.springframework.data.elasticsearch.core.SearchHits;
+import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
+import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
+import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * The type Scholix index manager.
+ */
+@Component
+public class ScholixIndexManager {
+
+ /**
+ * The Elastic search properties.
+ */
+ @Autowired
+ ElasticSearchProperties elasticSearchProperties;
+
+ /**
+ * The Elasticsearch template.
+ */
+ @Autowired
+ ElasticSearchPool connectionPool;
+
+ /**
+ * The My counter.
+ */
+ @Autowired
+ TaggedCounter myCounter;
+
+
+ /**
+ * The enum Pid type prefix.
+ */
+ public enum RelationPrefix {
+ /**
+ * Source pid type prefix.
+ */
+ source,
+ /**
+ * Target pid type prefix.
+ */
+ target
+ }
+
+
+ private QueryBuilder createObjectTypeQuery(final RelationPrefix prefix, final String objectType ) throws ScholixException{
+ if (prefix == null){
+ throw new ScholixException("prefix cannot be null");
+ }
+ return new NestedQueryBuilder(String.format("%s", prefix), new TermQueryBuilder(String.format("%s.objectType",prefix), objectType), ScoreMode.None);
+ }
+
+
+ private QueryBuilder createPidTypeQuery(final RelationPrefix prefix, final String pidTypeValue ) throws ScholixException{
+ if (prefix == null){
+ throw new ScholixException("prefix cannot be null");
+ }
+ return new NestedQueryBuilder(String.format("%s.identifier", prefix), new TermQueryBuilder(String.format("%s.identifier.schema",prefix), pidTypeValue), ScoreMode.None);
+ }
+
+
+ private QueryBuilder createLinkProviderQuery(final String providerName ) throws ScholixException{
+ if (providerName == null){
+ throw new ScholixException("prefix cannot be null");
+ }
+ return new NestedQueryBuilder("linkprovider", new TermQueryBuilder("linkprovider.name",providerName), ScoreMode.None);
+ }
+
+ private QueryBuilder createLinkPublisherQuery(final RelationPrefix prefix, final String publisher ) throws ScholixException{
+ if (prefix == null){
+ throw new ScholixException("prefix cannot be null");
+ }
+ return new NestedQueryBuilder(String.format("%s.publisher", prefix), new TermQueryBuilder(String.format("%s.publisher.name",prefix), publisher), ScoreMode.None);
+ }
+
+ private QueryBuilder createPidValueQuery(final RelationPrefix prefix, final String pidValue ) throws ScholixException{
+ if (prefix == null){
+ throw new ScholixException("prefix cannot be null");
+ }
+ return new NestedQueryBuilder(String.format("%s.identifier", prefix), new TermQueryBuilder(String.format("%s.identifier.identifier",prefix), pidValue), ScoreMode.None);
+ }
+
+
+ private QueryBuilder createFinalQuery(final List queries) throws ScholixException{
+
+ if (queries == null || queries.isEmpty())
+ throw new ScholixException("the list of queries must be not empty");
+
+
+ if (queries.size() ==1) {
+ return queries.get(0);
+ }
+
+ else {
+ final BoolQueryBuilder b = new BoolQueryBuilder();
+ b.must().addAll(queries);
+
+ return b;
+ }
+
+ }
+
+ private void incrementPidCounter(RelationPrefix prefix, String value) {
+ switch (value.toLowerCase()){
+ case "doi": {
+ myCounter.increment(String.format("%s_doi", prefix));
+ break;
+ }
+ case "pmc": {
+ myCounter.increment(String.format("%s_pmc", prefix));
+ break;
+ }
+ default:
+ myCounter.increment(String.format("%s_other", prefix));
+ }
+ }
+
+
+ public List> totalLinksByProvider(final String filterName) throws ScholixException {
+
+
+ final QueryBuilder query = StringUtils.isNoneBlank(filterName)?createLinkProviderQuery(filterName):QueryBuilders.matchAllQuery();
+
+ final NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
+ .withQuery(query)
+ .withSearchType(SearchType.DEFAULT)
+ .withPageable(PageRequest.of(0,10))
+ .addAggregation(AggregationBuilders.nested("nested", "linkprovider")
+ .subAggregation(AggregationBuilders.terms("by_map").field("linkprovider.name").size(100).minDocCount(1)))
+ .build();
+
+
+ Pair resource = connectionPool.getResource();
+ ElasticsearchRestTemplate client = resource.getValue();
+ final SearchHits hits = client.search(searchQuery, Scholix.class, IndexCoordinates.of(elasticSearchProperties.getIndexName()));
+
+ final Aggregations aggregations = hits.getAggregations();
+ connectionPool.returnResource(resource);
+
+ if(aggregations == null)
+ return null;
+
+ final Aggregation aggByMap = ((ParsedNested) aggregations.asMap().get("nested")).getAggregations().asMap().get("by_map");
+
+
+ return ((ParsedStringTerms) aggByMap).getBuckets()
+ .stream()
+ .map(b -> new ImmutablePair<>(b.getKeyAsString(), b.getDocCount()))
+ .collect(Collectors.toList());
+ }
+
+
+ public List> totalLinksPublisher(final RelationPrefix prefix, final String filterName) throws ScholixException {
+
+
+ final QueryBuilder query = StringUtils.isNoneBlank(filterName)?createLinkPublisherQuery(prefix,filterName):QueryBuilders.matchAllQuery();
+
+ final NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
+ .withQuery(query)
+ .withSearchType(SearchType.DEFAULT)
+ .withPageable(PageRequest.of(0,10))
+ .addAggregation(AggregationBuilders.nested("nested", String.format("%s.publisher", prefix ))
+ .subAggregation(AggregationBuilders.terms("by_map").field(String.format("%s.publisher.name", prefix )).size(100).minDocCount(1)))
+ .build();
+
+
+ Pair resource = connectionPool.getResource();
+ ElasticsearchRestTemplate client = resource.getValue();
+ final SearchHits hits = client.search(searchQuery, Scholix.class, IndexCoordinates.of(elasticSearchProperties.getIndexName()));
+
+ final Aggregations aggregations = hits.getAggregations();
+ connectionPool.returnResource(resource);
+
+ if(aggregations == null)
+ return null;
+
+ final Aggregation aggByMap = ((ParsedNested) aggregations.asMap().get("nested")).getAggregations().asMap().get("by_map");
+
+
+ return ((ParsedStringTerms) aggByMap).getBuckets()
+ .stream()
+ .map(b -> new ImmutablePair<>(b.getKeyAsString(), b.getDocCount()))
+ .collect(Collectors.toList());
+ }
+
+
+
+
+
+
+
+ /**
+ * Links from pid pair.
+ *
+ * @param linkProvider the link provider
+ * @param targetPid the target pid
+ * @param targetPidType the target pid type
+ * @param targetPublisher the target publisher
+ * @param targetType the target type
+ * @param sourcePid the source pid
+ * @param sourcePidType the source pid type
+ * @param sourcePublisher the source publisher
+ * @param sourceType the source type
+ * @param harvestedAfter the harvested after
+ * @param page the page
+ * @return the pair
+ * @throws ScholixException the scholix exception
+ */
+ @Timed(value = "scholix.index.request.links", description = "Time taken to request index")
+ public Pair> linksFromPid ( final String linkProvider,
+ final String targetPid, final String targetPidType, final String targetPublisher,
+ final String targetType, final String sourcePid, final String sourcePidType,
+ final String sourcePublisher, final String sourceType, final String harvestedAfter,
+ final Integer page) throws ScholixException {
+
+
+
+ if (sourcePid==null && sourcePidType==null && targetPid==null && targetPidType==null && sourcePublisher==null && targetPublisher==null && linkProvider==null)
+ throw new ScholixException("One of sourcePid, targetPid, sourcePublisher, targetPublisher, linkProvider should be not null");
+
+ final List queries = new ArrayList<>();
+
+ if (StringUtils.isNoneBlank(linkProvider)) {
+ myCounter.increment("linkProvider");
+ queries.add(createLinkProviderQuery(linkProvider));
+ }
+
+ if (StringUtils.isNoneBlank(targetPid)) {
+ myCounter.increment("targetPid");
+ queries.add(createPidValueQuery(RelationPrefix.target, targetPid));
+ }
+ if (StringUtils.isNoneBlank(sourcePid)) {
+ myCounter.increment("sourcePid");
+ queries.add(createPidValueQuery(RelationPrefix.source, sourcePid));
+ }
+
+ if (StringUtils.isNoneBlank(targetPidType)) {
+ assert targetPidType != null;
+ incrementPidCounter(RelationPrefix.target,targetPidType);
+ queries.add(createPidTypeQuery(RelationPrefix.target, targetPidType));
+ }
+ if (StringUtils.isNoneBlank(sourcePidType)) {
+ assert sourcePidType != null;
+ incrementPidCounter(RelationPrefix.source,sourcePidType);
+ queries.add(createPidTypeQuery(RelationPrefix.source, sourcePidType));
+ }
+
+ if (StringUtils.isNoneBlank(targetType)) {
+ if ("dataset".equalsIgnoreCase(targetType) || "publication".equalsIgnoreCase(targetType))
+ myCounter.increment(String.format("targetType_%s", targetType));
+ queries.add(createObjectTypeQuery(RelationPrefix.target, targetType));
+ }
+
+ if (StringUtils.isNoneBlank(sourceType)) {
+ if ("dataset".equalsIgnoreCase(sourceType) || "publication".equalsIgnoreCase(sourceType)) {
+ myCounter.increment(String.format("sourceType_%s", sourceType));
+ }
+ queries.add(createObjectTypeQuery(RelationPrefix.source, sourceType));
+ }
+
+ if (StringUtils.isNoneBlank(targetPublisher)) {
+ myCounter.increment("targetPublisher");
+
+ queries.add(createLinkPublisherQuery(RelationPrefix.target,targetPublisher));
+ }
+
+ QueryBuilder result = createFinalQuery(queries);
+
+ NativeSearchQuery finalQuery = new NativeSearchQueryBuilder()
+ .withQuery(result)
+ .withPageable(PageRequest.of(page,10))
+ .build();
+
+
+ Pair resource = connectionPool.getResource();
+ ElasticsearchRestTemplate client = resource.getValue();
+
+ long tt = client.count(finalQuery, Scholix.class, IndexCoordinates.of(elasticSearchProperties.getIndexName()));
+
+ SearchHits scholixRes = client.search(finalQuery, Scholix.class, IndexCoordinates.of(elasticSearchProperties.getIndexName()));
+
+ connectionPool.returnResource(resource);
+
+ return new ImmutablePair<>(tt,scholixRes.stream().map(SearchHit::getContent).collect(Collectors.toList()));
+ }
+
+}
diff --git a/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/metrics/RequestCounter.java b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/metrics/RequestCounter.java
new file mode 100644
index 00000000..56c9ed2c
--- /dev/null
+++ b/apps/scholexplorer-api/src/main/java/eu/dnetlib/scholix/api/metrics/RequestCounter.java
@@ -0,0 +1,13 @@
+package eu.dnetlib.scholix.api.metrics;
+
+import eu.dnetlib.common.metrics.MetricInfo;
+import org.springframework.stereotype.Component;
+
+
+@Component("scholexplorer_test")
+public class RequestCounter implements MetricInfo {
+ @Override
+ public double obtainValue() {
+ return 0L;
+ }
+}
diff --git a/apps/scholexplorer-api/src/main/resources/application.properties b/apps/scholexplorer-api/src/main/resources/application.properties
new file mode 100644
index 00000000..03eedf2b
--- /dev/null
+++ b/apps/scholexplorer-api/src/main/resources/application.properties
@@ -0,0 +1,26 @@
+spring.main.banner-mode = console
+
+logging.level.root = INFO
+dhp.swagger.api.host = localhost:8080
+#dhp.swagger.api.host = api.scholexplorer.openaire.eu
+dhp.swagger.api.basePath = /
+
+maven.pom.path = /META-INF/maven/eu.dnetlib.dhp/scholexplorer-api/effective-pom.xml
+#
+#spring.thymeleaf.cache=false
+#
+management.endpoints.web.exposure.include = prometheus,health
+management.endpoints.web.base-path = /
+management.endpoints.web.path-mapping.prometheus = metrics
+management.endpoints.web.path-mapping.health = health
+
+management.metrics.distribution.percentiles-histogram.http.server.requests=false
+management.metrics.distribution.slo.http.server.requests=50ms, 100ms, 200ms, 400ms
+management.metrics.distribution.percentiles.http.server.requests=0.5, 0.9, 0.95, 0.99, 0.999
+
+
+
+scholix.elastic.clusterNodes = localhost:9200
+scholix.elastic.indexName = dli_shadow_scholix
+scholix.elastic.socketTimeout = 60000
+scholix.elastic.connectionTimeout= 60000
diff --git a/apps/scholexplorer-api/src/main/resources/static/logo.png b/apps/scholexplorer-api/src/main/resources/static/logo.png
new file mode 100644
index 00000000..4614e2d5
Binary files /dev/null and b/apps/scholexplorer-api/src/main/resources/static/logo.png differ
diff --git a/apps/scholexplorer-api/src/main/resources/static/new_swagger.html b/apps/scholexplorer-api/src/main/resources/static/new_swagger.html
new file mode 100644
index 00000000..a22ffb29
--- /dev/null
+++ b/apps/scholexplorer-api/src/main/resources/static/new_swagger.html
@@ -0,0 +1,135 @@
+
+
+
+
+
+ Custom Swagger UI
+
+
+
+
+
+
+
+