From 658d7ce885e1b6a0e5aceaccf917eafd5cbaa4aa Mon Sep 17 00:00:00 2001
From: Baptiste Toulemonde <toulemonde@cines.fr>
Date: Tue, 1 Mar 2022 09:29:37 +0100
Subject: [PATCH] fix publish distrib to fdp

---
 .../com/smartharvester/config/AsynConfig.java |   7 +-
 .../SmartHarvesterMappingController.java      |  40 +-
 .../CustomAsyncExceptionHandler.java          |  26 +
 .../service/MappingService.java               | 723 ++++++++++--------
 .../util/SmartHarvesterRequestUtil.java       |   6 +-
 5 files changed, 451 insertions(+), 351 deletions(-)
 create mode 100644 src/main/java/com/smartharvester/exception/CustomAsyncExceptionHandler.java

diff --git a/src/main/java/com/smartharvester/config/AsynConfig.java b/src/main/java/com/smartharvester/config/AsynConfig.java
index 705221e..920ab49 100644
--- a/src/main/java/com/smartharvester/config/AsynConfig.java
+++ b/src/main/java/com/smartharvester/config/AsynConfig.java
@@ -1,15 +1,19 @@
 package com.smartharvester.config;
 
+import java.util.concurrent.Callable;
 import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 
 @Configuration
 @EnableAsync
-public class AsynConfig {
+public class AsynConfig implements AsyncConfigurer {
 
 	@Bean
 	public Executor asyncExecutor() {
@@ -27,3 +31,4 @@ public class AsynConfig {
 	    }
 
 }
+
diff --git a/src/main/java/com/smartharvester/controller/SmartHarvesterMappingController.java b/src/main/java/com/smartharvester/controller/SmartHarvesterMappingController.java
index 779fc38..7aba48b 100644
--- a/src/main/java/com/smartharvester/controller/SmartHarvesterMappingController.java
+++ b/src/main/java/com/smartharvester/controller/SmartHarvesterMappingController.java
@@ -1,23 +1,20 @@
 package com.smartharvester.controller;
 
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
+
+import org.json.JSONException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.CrossOrigin;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 import org.springframework.web.client.HttpStatusCodeException;
 
 import com.smartharvester.model.mapping.Path;
@@ -54,7 +51,7 @@ public class SmartHarvesterMappingController {
 
 	@PostMapping("/publish")
 	public ResponseEntity<MappingResponse> map(@RequestBody MappingRequest mappingRequest,
-			@RequestParam("catalogId") String catalogId, @RequestParam String fdpUrl, @RequestParam boolean isJsonpath) throws ExecutionException, InterruptedException {
+			@RequestParam("catalogId") String catalogId, @RequestParam String fdpUrl, @RequestParam boolean isJsonpath)  {
 		ResponseEntity<?> responseEntity;
 		List<String> urls = mappingRequest.getUrls();
 		List<Path> paths = mappingRequest.getPaths();
@@ -67,28 +64,26 @@ public class SmartHarvesterMappingController {
 		for (String url : urls) {
 			String datasetId;
 
-			List<String> properties;
-
-			properties = (isJsonpath) ? this.mappingService.getValuesFromJsonpath(url, paths).get() : this.mappingService.getValues(url, paths).get();
-			String dataset = this.mappingService.getDatasetString(catalogId, properties.get(0), fdpUrl);
 			String locationDataset;
 			try {
+                String dataset = this.mappingService.buildDataset(url, paths, isJsonpath, catalogId, fdpUrl);
 				responseEntity = this.mappingService.asyncPostToFdp("/dataset", fdpUrl, dataset, fdpToken).get();
 				locationDataset = Objects.requireNonNull(responseEntity.getHeaders().getLocation()).toString();
 				datasetId = locationDataset.substring(locationDataset.indexOf("dataset/") + 8);
 
 				HttpStatus statusCode = responseEntity.getStatusCode();
 				if (!distributionPaths.isEmpty()) {
-					String distribution = this.mappingService.getDistributionString(datasetId, properties.get(1),
-							fdpUrl);
-					try {
-						ResponseEntity<String> distributionResponse = this.mappingService.asyncPostToFdp("/distribution", fdpUrl, distribution, fdpToken).get();
+					List<String> distributions = this.mappingService.buildDistribution(url, paths, isJsonpath, datasetId, fdpUrl);
+					for (String distribution: distributions) {
+						try {
+							ResponseEntity<String> distributionResponse = this.mappingService.asyncPostToFdp("/distribution", fdpUrl, distribution, fdpToken).get();
 							String locationDistribution = Objects.requireNonNull(Objects.requireNonNull(distributionResponse.getHeaders().getLocation()).toString());
-						if (distributionResponse.getStatusCode().value() == 201) {
-							this.mappingService.draftToPublished(fdpToken, locationDistribution);
+							if (distributionResponse.getStatusCode().value() == 201) {
+								this.mappingService.draftToPublished(fdpToken, locationDistribution);
+							}
+						} catch (ExecutionException | InterruptedException e) {
+							LOGGER.warn(e.getMessage());
 						}
-					} catch (HttpStatusCodeException e) {
-						LOGGER.warn(e.getMessage());
 					}
 				}
 				if (statusCode.value() == 201) {
@@ -97,10 +92,13 @@ public class SmartHarvesterMappingController {
 				} else {
 					notPublishedUrl.add(url);
 				}
-			} catch (Exception e) {
+			} catch (JSONException | ExecutionException | IllegalStateException e ) {
 				notPublishedUrl.add(url + " => " + e.getMessage());
 				LOGGER.warn(e.getMessage());
+			} catch (InterruptedException e) {
 				Thread.currentThread().interrupt();
+				notPublishedUrl.add(url + " => " + e.getMessage());
+				LOGGER.warn(e.getMessage());
 			}
 
 		}
diff --git a/src/main/java/com/smartharvester/exception/CustomAsyncExceptionHandler.java b/src/main/java/com/smartharvester/exception/CustomAsyncExceptionHandler.java
new file mode 100644
index 0000000..ece5df1
--- /dev/null
+++ b/src/main/java/com/smartharvester/exception/CustomAsyncExceptionHandler.java
@@ -0,0 +1,26 @@
+package com.smartharvester.exception;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
+
+import java.lang.reflect.Method;
+
+public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
+
+    public static final Logger LOGGER = LoggerFactory.getLogger(CustomAsyncExceptionHandler.class);
+
+    @Override
+    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
+        LOGGER.warn(ex.getMessage());
+        LOGGER.warn(method.getName());
+        for (Object param: params) {
+            LOGGER.warn((String) param);
+        }
+    }
+
+    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
+        return new CustomAsyncExceptionHandler();
+    }
+}
diff --git a/src/main/java/com/smartharvester/service/MappingService.java b/src/main/java/com/smartharvester/service/MappingService.java
index 5b40586..2cc51c2 100644
--- a/src/main/java/com/smartharvester/service/MappingService.java
+++ b/src/main/java/com/smartharvester/service/MappingService.java
@@ -1,14 +1,14 @@
 package com.smartharvester.service;
 
-import com.jayway.jsonpath.DocumentContext;
-import com.jayway.jsonpath.JsonPath;
-import com.jayway.jsonpath.PathNotFoundException;
+import com.jayway.jsonpath.*;
 import com.smartharvester.model.mapping.Path;
 import com.smartharvester.model.mapping.request.PublishedRequest;
 import com.smartharvester.util.SmartHarvesterRequestUtil;
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.*;
 import org.springframework.scheduling.annotation.Async;
@@ -16,334 +16,403 @@ import org.springframework.stereotype.Service;
 import org.springframework.web.client.HttpClientErrorException.BadRequest;
 import org.springframework.web.client.RestTemplate;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import java.util.*;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 @Service
 public class MappingService {
 
-	@Autowired
-	private RestTemplate restTemplate;
-
-	@Autowired
-	SmartHarvesterRequestUtil requestUtil;
-
-	public String getDatasetString(String catId, String properties, String fdpUrl) {
-		return "@prefix dcat: <http://www.w3.org/ns/dcat#>.\n" + "@prefix dct: <http://purl.org/dc/terms/>.\n"
-				+ "@prefix adms: <http://www.w3.org/ns/adms#>.\n" + "@prefix dqv:  <http://www.w3.org/ns/dqv#> .\n"
-				+ "@prefix geodcat: <http://data.europa.eu/930/>.\n" + "@prefix prov: <http://www.w3.org/ns/prov#>.\n"
-				+ "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.\n"
-				+ "@prefix language: <http://id.loc.gov/vocabulary/iso639-1/>.\n" + "@prefix s: <" + fdpUrl + "/>.\n"
-				+ "@prefix c: <" + fdpUrl + "/catalog/>.\n" + "s:new \n" + "a dcat:Dataset, dcat:Resource;\n"
-				+ "dct:isPartOf c:" + catId + ";\n" + properties + ".";
-	}
-
-	public String getDistributionString(String datasetId, String properties, String fdpUrl) {
-		return "@prefix dcat: <http://www.w3.org/ns/dcat#>. \n"
-				+ "@prefix dct: <http://purl.org/dc/terms/>.\n" + "@prefix adms: <http://www.w3.org/ns/adms#> .\n"
-				+ "@prefix dqv:  <http://www.w3.org/ns/dqv#> .\n" + "@prefix geodcat: <http://data.europa.eu/930/>. \n"
-				+ "@prefix prov: <http://www.w3.org/ns/prov#>.\n"
-				+ "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.\n"
-				+ "@prefix skos: <http://www.w3.org/2004/02/skos/core#>.\n"
-				+ "@prefix spdx:  <http://spdx.org/rdf/terms#>.\n" + "@prefix foaf: <http://xmlns.com/foaf/0.1/>.\n"
-				+ "@prefix odrl: <http://www.w3.org/ns/odrl/2/>.\n"
-				+ "@prefix cnt:   <http://www.w3.org/2011/content#>.\n"
-				+ "@prefix language: <http://id.loc.gov/vocabulary/iso639-1/>.\n" + "@prefix s: <" + fdpUrl + "/>.\n"
-				+ "@prefix c: <" + fdpUrl + "/dataset/>.\n" + "s:new \n" + "a dcat:Distribution, dcat:Resource;\n"
-				+ "dct:isPartOf c:" + datasetId + ";\n" + properties + ".";
-	}
-
-
-
-	public static String write(String indentifier, String value) {
-		value = value.replaceAll("[\\r\\n]+", " ").replaceAll("\"", "\\\\\"");
-		String property = "";
-
-		switch (indentifier) {
-		case "dcat:landingPage":
-		case "dcat:theme":
-		case "dcat:contactPoint":
-		case "dcat:accessURL":
-		case "dcat:downloadURL":
-		case "dct:language":
-			property += indentifier + " <" + value + ">;\n";
-			break;
-		case "dct:issued":
-		case "dct:modified":
-			property += indentifier + " \"" + value + "\"^^xsd:dateTime;\n";
-			break;
-		case "dct:publisher":
-			property += indentifier + "[ a foaf:Agent; foaf:name \"" + value + "\"];\n";
-			break;
-		default:
-			property += indentifier + " \"" + value + "\";\n";
-			break;
-		}
-
-		return property;
-	}
-
-	public static boolean isReplicable(String card) {
-		return card.endsWith("n");
-	}
-
-	@Async
-	public CompletableFuture<List<String>> getValuesFromJsonpath(String urlRepo, List<Path> paths) {
-		List<String> result = new ArrayList<>();
-		StringBuilder datasetProperties = new StringBuilder();
-		StringBuilder distributionProperties = new StringBuilder();
-		JSONObject json = null;
-
-		try {
-			json = this.requestUtil.getJson(urlRepo);
-		} catch (JSONException e1) {
-			e1.printStackTrace();
-		}
-
-		if (paths.stream().filter(e -> e.getDcatClass().equals("dcat:dataset")).noneMatch(e -> e.getProperty().equals("dct:hasVersion"))) {
-			datasetProperties.append("dct:hasVersion \"null\";\n");
-		}
-		if (paths.stream().filter(e -> e.getDcatClass().equals("dcat:dataset")).noneMatch(e -> e.getProperty().equals("dct:publisher"))) {
-			datasetProperties.append("dct:publisher [ a foaf:Agent; foaf:name \"undefined\"];\n");
-		}
-		distributionProperties.append("dct:hasVersion \"null\";\ndct:publisher [ a foaf:Agent; foaf:name \"undefined\"];\n");
-		
-		for (Path path: paths) {
-			assert json != null;
-			DocumentContext jsonContext = JsonPath.parse(json.toString());
-			Object value;
-			try {
-				value = jsonContext.read(path.getPath());
-			} catch (PathNotFoundException e) {
-				value = "undefined";
-			}
-
-			if (value instanceof String) {
-				String valueString = (String) value;
-				if (path.getDcatClass().equals("dcat:dataset")) {
-					datasetProperties.append(write(path.getProperty(), valueString));
-				} else if (path.getDcatClass().equals("dcat:distribution")) {
-					distributionProperties.append(write(path.getProperty(), valueString));
-				}
-			} else if (value instanceof Integer){
-				int valueInt= (Integer) value;
-				if (path.getDcatClass().equals("dcat:dataset")) {
-					datasetProperties.append(write(path.getProperty(), String.valueOf(valueInt)));
-				} else if (path.getDcatClass().equals("dcat:distribution")) {
-					distributionProperties.append(write(path.getProperty(), String.valueOf(valueInt)));
-				}
-			} else if (value instanceof List<?>) {
-				@SuppressWarnings("unchecked")
-				List<String> valueList = (List<String>) value;
-				for(String element: valueList) {
-					if (path.getDcatClass().equals("dcat:dataset")) {
-						datasetProperties.append(write(path.getProperty(), element));
-					} else if (path.getDcatClass().equals("dcat:distribution")) {
-						distributionProperties.append(write(path.getProperty(), element));
-					}
-				}
-			}
-		}
-		result.add(datasetProperties.toString());
-		result.add(distributionProperties.toString());
-
-		return CompletableFuture.completedFuture(result);
-	}
-
-	@Async
-	public CompletableFuture<List<String>> getValues(String urlRepo, List<Path> paths) {
-
-		List<String> result = new ArrayList<>();
-		StringBuilder datasetProperties = new StringBuilder();
-		StringBuilder distributionProperties = new StringBuilder();
-		JSONObject json = null;
-
-		try {
-			json = this.requestUtil.getJson(urlRepo);
-		} catch (JSONException e1) {
-			e1.printStackTrace();
-		}
-
-		if (paths.stream().filter(e -> e.getDcatClass().equals("dcat:dataset")).noneMatch(e -> e.getProperty().equals("dct:hasVersion"))) {
-			datasetProperties.append("dct:hasVersion \"null\";\n");
-		}
-		if (paths.stream().filter(e -> e.getDcatClass().equals("dcat:dataset")).noneMatch(e -> e.getProperty().equals("dct:publisher"))) {
-			datasetProperties.append("dct:publisher [ a foaf:Agent; foaf:name \"undefined\"];\n");
-		}
-		distributionProperties.append("dct:hasVersion \"null\";\ndct:publisher [ a foaf:Agent; foaf:name \"undefined\"];\n");
-		for (Path path : paths) {
-			try {
-				String[] array = path.getPath().split(" : ");
-				List<String> list = new ArrayList<>(Arrays.asList(array));
-
-				JSONObject jsonT = json;
-				if (list.size() == 1) {
-					assert jsonT != null;
-					if (jsonT.optJSONArray(list.get(0)) != null) {
-						JSONArray jsonA = jsonT.optJSONArray(list.get(0));
-						if (isReplicable(path.getCard())) {
-							for (int j = 0; j < jsonA.length(); j++) {
-								if (path.getDcatClass().equals("dcat:dataset")) {
-									datasetProperties.append(write(path.getProperty(), jsonA.getString(j)));
-								} else if (path.getDcatClass().equals("dcat:distribution")) {
-									distributionProperties.append(write(path.getProperty(), jsonA.getString(j)));
-								}
-							}
-						} else {
-							if (path.getDcatClass().equals("dcat:dataset")) {
-								datasetProperties.append(write(path.getProperty(), jsonA.getString(0)));
-							} else if (path.getDcatClass().equals("dcat:distribution")) {
-								distributionProperties.append(write(path.getProperty(), jsonA.getString(0)));
-							}
-						}
-
-					} else {
-						if (path.getDcatClass().equals("dcat:dataset")) {
-							datasetProperties.append(write(path.getProperty(), jsonT.getString(list.get(0))));
-						} else if (path.getDcatClass().equals("dcat:distribution")) {
-							distributionProperties.append(write(path.getProperty(), jsonT.getString(list.get(0))));
-						}
-					}
-				} else {
-					for (int i = 0; i < list.size(); i++) {
-						if (i < list.size() - 1) {
-							assert jsonT != null;
-							if (jsonT.optJSONObject(list.get(i)) != null) {
-								jsonT = jsonT.optJSONObject(list.get(i));
-							} else if (jsonT.optJSONArray(list.get(i)) != null) {
-								JSONArray jsonA = jsonT.optJSONArray(list.get(i));
-
-								if (i < list.size() - 2) {
-									jsonT = jsonA.getJSONObject(Integer.parseInt(list.get(i + 1)));
-
-									list.remove(i + 1);
-								}
-							}
-						} else {
-							if (jsonT.optJSONArray(list.get(i)) != null) {
-								if (!jsonT.has(list.get(i))) {
-									if (path.getDcatClass().equals("dcat:dataset")) {
-										datasetProperties.append(write(path.getProperty(), "undefined"));
-									} else if (path.getDcatClass().equals("dcat:distribution")) {
-										distributionProperties.append(write(path.getProperty(), "undefined"));
-									}
-								} else {
-									JSONArray jsonA = jsonT.optJSONArray(list.get(i));
-									if (isReplicable(path.getCard())) {
-										for (int j = 0; j < jsonA.length(); j++) {
-											if (path.getDcatClass().equals("dcat:dataset")) {
-												datasetProperties.append(write(path.getProperty(), jsonA.optString(j)));
-											} else if (path.getDcatClass().equals("dcat:distribution")) {
-												distributionProperties.append(write(path.getProperty(), jsonA.optString(j)));
-											}
-										}
-										break;
-									} else {
-										if (path.getDcatClass().equals("dcat:dataset")) {
-											datasetProperties.append(write(path.getProperty(), jsonA.optString(0)));
-										} else if (path.getDcatClass().equals("dcat:distribution")) {
-											distributionProperties.append(write(path.getProperty(), jsonA.optString(0)));
-										}
-									}
-
-									break;
-								}
-
-							} else if (jsonT.optJSONArray(list.get(list.size() - 2)) != null) {
-
-								JSONArray jsonA = jsonT.optJSONArray(list.get(list.size() - 2));
-								if (path.getDcatClass().equals("dcat:dataset")) {
-									datasetProperties.append(write(path.getProperty(),
-											jsonA.optString(Integer.parseInt(list.get(i)))));
-								} else if (path.getDcatClass().equals("dcat:distribution")) {
-									distributionProperties.append(write(path.getProperty(),
-											jsonA.optString(Integer.parseInt(list.get(i)))));
-								}
-								break;
-							} else if (jsonT.optString(list.get(i)) != null && jsonT.has(list.get(i))) {
-								if (path.getDcatClass().equals("dcat:dataset")) {
-									datasetProperties.append(write(path.getProperty(), jsonT.optString(list.get(i))));
-								} else if (path.getDcatClass().equals("dcat:distribution")) {
-									distributionProperties.append(write(path.getProperty(), jsonT.optString(list.get(i))));
-								}
-								break;
-							} else {
-								var keys = jsonT.keys();
-								while (keys.hasNext()) {
-									String key = keys.next().toString();
-									if (jsonT.optJSONArray(key) != null) {
-										JSONArray jsonA = jsonT.optJSONArray(key);
-										if (isReplicable(path.getCard())) {
-											for (int j = 0; j < jsonA.length(); j++) {
-												String value = jsonA.getJSONObject(j).getString(list.get(i));
-												if (path.getDcatClass().equals("dcat:dataset")) {
-													datasetProperties.append(write(path.getProperty(), value));
-												} else if (path.getDcatClass().equals("dcat:distribution")) {
-													distributionProperties.append(write(path.getProperty(), value));
-												}
-											}
-										} else {
-											String value = jsonA.getJSONObject(0).getString(list.get(i));
-											if (path.getDcatClass().equals("dcat:dataset")) {
-												datasetProperties.append(write(path.getProperty(), value));
-											} else if (path.getDcatClass().equals("dcat:distribution")) {
-												distributionProperties.append(write(path.getProperty(), value));
-											}
-										}
-
-									}
-								}
-							}
-						}
-
-					}
-				}
-			} catch (JSONException e1) {
-				if (path.getDcatClass().equals("dcat:dataset")) {
-					datasetProperties.append(write(path.getProperty(), "undefined"));
-				} else if (path.getDcatClass().equals("dcat:distribution")) {
-					distributionProperties.append(write(path.getProperty(), "undefined"));
-				}
-			}
-
-		}
-
-		result.add(datasetProperties.toString());
-		result.add(distributionProperties.toString());
-
-		return CompletableFuture.completedFuture(result);
-
-	}
-
-
-	@Async
-	public CompletableFuture<ResponseEntity<String>> asyncPostToFdp(String path, String fdpUrl, String dataset, String fdpToken)
-			throws BadRequest {
-
-		HttpHeaders headers = new HttpHeaders();
-		headers.add("Accept", "text/turtle;charset=UTF-8");
-		headers.add("Content-Type", "text/turtle;charset=UTF-8");
-		headers.setBearerAuth(fdpToken);
-
-		HttpEntity<String> entity = new HttpEntity<>(dataset, headers);
-
-		return CompletableFuture.completedFuture(this.restTemplate.exchange(fdpUrl + path, HttpMethod.POST, entity, String.class));
-
-	}
-
-	@Async
-	public void draftToPublished(String fdpToken, String url) {
-		HttpHeaders headers = new HttpHeaders();
-
-		headers.setBearerAuth(fdpToken);
-		headers.setContentType(MediaType.APPLICATION_JSON);
-
-		HttpEntity<PublishedRequest> entity = new HttpEntity<>(new PublishedRequest("PUBLISHED"),
-				headers);
-
-		this.restTemplate.exchange(url + "/meta/state", HttpMethod.PUT, entity, Void.class);
-	}
+    @Autowired
+    private RestTemplate restTemplate;
+
+    @Autowired
+    SmartHarvesterRequestUtil requestUtil;
+
+    public static final Logger LOGGER = LoggerFactory.getLogger(MappingService.class);
+
+    public StringBuilder getDatasetString(String catId, String fdpUrl) {
+        return new StringBuilder("@prefix dcat: <http://www.w3.org/ns/dcat#>.\n" + "@prefix dct: <http://purl.org/dc/terms/>.\n" + "@prefix adms: <http://www.w3.org/ns/adms#>.\n" + "@prefix dqv:  <http://www.w3.org/ns/dqv#> .\n" + "@prefix geodcat: <http://data.europa.eu/930/>.\n" + "@prefix prov: <http://www.w3.org/ns/prov#>.\n" + "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.\n" + "@prefix language: <http://id.loc.gov/vocabulary/iso639-1/>.\n" + "@prefix s: <" + fdpUrl + "/>.\n" + "@prefix c: <" + fdpUrl + "/catalog/>.\n" + "s:new \n" + "a dcat:Dataset, dcat:Resource;\n" + "dct:isPartOf c:" + catId + ";\n");
+    }
+
+    public StringBuilder getDistributionString(String datasetId, String fdpUrl) {
+        return new StringBuilder("@prefix dcat: <http://www.w3.org/ns/dcat#>. \n" + "@prefix dct: <http://purl.org/dc/terms/>.\n" + "@prefix adms: <http://www.w3.org/ns/adms#> .\n" + "@prefix dqv:  <http://www.w3.org/ns/dqv#> .\n" + "@prefix geodcat: <http://data.europa.eu/930/>. \n" + "@prefix prov: <http://www.w3.org/ns/prov#>.\n" + "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.\n" + "@prefix skos: <http://www.w3.org/2004/02/skos/core#>.\n" + "@prefix spdx:  <http://spdx.org/rdf/terms#>.\n" + "@prefix foaf: <http://xmlns.com/foaf/0.1/>.\n" + "@prefix odrl: <http://www.w3.org/ns/odrl/2/>.\n" + "@prefix cnt:   <http://www.w3.org/2011/content#>.\n" + "@prefix language: <http://id.loc.gov/vocabulary/iso639-1/>.\n" + "@prefix s: <" + fdpUrl + "/>.\n" + "@prefix c: <" + fdpUrl + "/dataset/>.\n" + "s:new \n" + "a dcat:Distribution, dcat:Resource;\n" + "dct:isPartOf c:" + datasetId + ";\n");
+    }
+
+    public String buildDataset(String urlRepo, List<Path> paths, boolean isJsonpath, String catId, String fdpUrl) throws JSONException, ExecutionException, InterruptedException {
+        Map<String, List<Map<String, List<String>>>> dcatPropertiesMap = this.mapToDcat(urlRepo, paths, isJsonpath);
+        Map<String, List<String>> datasetMap = dcatPropertiesMap.get("dataset").get(0);
+
+        datasetMap.putIfAbsent("dct:hasVersion", List.of("undefined"));
+
+
+        datasetMap.putIfAbsent("dct:publisher", List.of("undefined"));
+
+
+        StringBuilder datasetString = this.getDatasetString(catId, fdpUrl);
+        for (Map.Entry<String, List<String>> entry : datasetMap.entrySet()) {
+            if (entry.getValue().isEmpty()) {
+                entry.setValue(List.of("undefined"));
+            }
+            if (isReplicable(entry.getKey(), paths) && (entry.getValue().size() > 1)) {
+                for (String value : entry.getValue()) {
+                    datasetString.append(this.write(entry.getKey(), value));
+                }
+            } else {
+                datasetString.append(this.write(entry.getKey(), entry.getValue().get(0)));
+            }
+
+        }
+        datasetString.append(".");
+        LOGGER.info(datasetString.toString());
+        return datasetString.toString();
+    }
+
+    public List<String> buildDistribution(String urlRepo, List<Path> paths, boolean isJsonpath, String datasetId, String fdpUrl) throws JSONException, ExecutionException, InterruptedException {
+        Map<String, List<Map<String, List<String>>>> dcatPropertiesMap = this.mapToDcat(urlRepo, paths, isJsonpath);
+        List<Map<String, List<String>>> distributionList = dcatPropertiesMap.get("distribution");
+        List<String> distributionStrings = new ArrayList<>();
+
+        if (!distributionList.get(0).entrySet().stream().map(v -> v.getValue()).allMatch(v -> v.get(0).equals("undefined"))) {
+            for (Map<String, List<String>> distributionMap : distributionList) {
+                distributionMap.putIfAbsent("dct:hasVersion", List.of("undefined"));
+
+                distributionMap.putIfAbsent("dct:publisher", List.of("undefined"));
+
+
+                StringBuilder datasetString = this.getDistributionString(datasetId, fdpUrl);
+                for (Map.Entry<String, List<String>> entry : distributionMap.entrySet()) {
+                    if (isReplicable(entry.getKey(), paths)) {
+                        for (String value : entry.getValue()) {
+                            datasetString.append(this.write(entry.getKey(), value));
+                        }
+                    } else {
+                        datasetString.append(this.write(entry.getKey(), entry.getValue().get(0)));
+                    }
+
+                }
+                datasetString.append(".");
+                distributionStrings.add(datasetString.toString());
+            }
+            for (String distribString : distributionStrings) {
+                LOGGER.info(distribString);
+            }
+        }
+
+
+        return distributionStrings;
+    }
+
+    public String write(String indentifier, String value) {
+        value = value.replaceAll("[\\r\\n]+", " ").replaceAll("\"", "\\\\\"");
+        String property = "";
+
+        switch (indentifier) {
+            case "dcat:landingPage":
+            case "dcat:theme":
+            case "dcat:contactPoint":
+            case "dcat:accessURL":
+            case "dcat:downloadURL":
+            case "dct:language":
+                if (value.startsWith("http")) {
+                    property += indentifier + " <" + value + ">;\n";
+                }
+                break;
+            case "dct:issued":
+            case "dct:modified":
+                property += indentifier + " \"" + value + "\"^^xsd:dateTime;\n";
+                break;
+            case "dct:publisher":
+                property += indentifier + "[ a foaf:Agent; foaf:name \"" + value + "\"];\n";
+                break;
+            default:
+                property += indentifier + " \"" + value + "\";\n";
+                break;
+        }
+
+        return property;
+    }
+
+    public static boolean isReplicable(String dcatProperty, List<Path> paths) {
+        String card = paths.stream().filter(e -> e.getProperty().equals(dcatProperty)).map(e -> e.getCard()).findFirst().orElse("1..1");
+        return card.endsWith("n");
+    }
+
+    @Async
+    public CompletableFuture<JSONObject> getJson(String url) throws JSONException {
+        return this.requestUtil.getJson(url);
+    }
+
+    @Async
+    public CompletableFuture<List<Map<String, List<String>>>> getAllValuesFomCustomPath(JSONObject json, List<Path> paths) {
+        Map<String, List<String>> propertyValueMap = new HashMap<>();
+        List<Map<String, List<String>>> propertyValueList = new ArrayList<>();
+
+        for (Path path : paths) {
+            try {
+                String[] array = path.getPath().split(" : ");
+                List<String> list = new ArrayList<>(Arrays.asList(array));
+
+                JSONObject jsonT = json;
+                if (list.size() == 1) {
+                    assert jsonT != null;
+                    if (jsonT.optJSONArray(list.get(0)) != null) {
+                        JSONArray jsonA = jsonT.optJSONArray(list.get(0));
+                        List<String> valueList = new ArrayList<>();
+                        for (int j = 0; j < jsonA.length(); j++) {
+                            valueList.add(jsonA.getString(j));
+                        }
+                        if (propertyValueMap.containsKey(path.getProperty())) {
+                            List<String> values = propertyValueMap.get(path.getProperty());
+                            values.addAll(valueList);
+                            propertyValueMap.replace(path.getProperty(), values);
+                        } else {
+                            propertyValueMap.put(path.getProperty(), valueList);
+                        }
+                    } else {
+                        if (propertyValueMap.containsKey(path.getProperty())) {
+                            List<String> values = propertyValueMap.get(path.getProperty());
+                            values.add(jsonT.getString(list.get(0)));
+                            propertyValueMap.replace(path.getProperty(), values);
+                        } else {
+                            propertyValueMap.put(path.getProperty(), List.of(jsonT.getString(list.get(0))));
+                        }
+                    }
+                } else {
+                    for (int i = 0; i < list.size(); i++) {
+                        if (i < list.size() - 1) {
+                            assert jsonT != null;
+                            if (jsonT.optJSONObject(list.get(i)) != null) {
+                                jsonT = jsonT.optJSONObject(list.get(i));
+                            } else if (jsonT.optJSONArray(list.get(i)) != null) {
+                                JSONArray jsonA = jsonT.optJSONArray(list.get(i));
+
+                                if (i < list.size() - 2) {
+                                    jsonT = jsonA.getJSONObject(Integer.parseInt(list.get(i + 1)));
+
+                                    list.remove(i + 1);
+                                }
+                            }
+                        } else {
+                            if (jsonT.optJSONArray(list.get(i)) != null) {
+                                if (!jsonT.has(list.get(i))) {
+                                    if (propertyValueMap.containsKey(path.getProperty())) {
+                                        List<String> values = propertyValueMap.get(path.getProperty());
+                                        values.add("undefined");
+                                        propertyValueMap.replace(path.getProperty(), values);
+                                    } else {
+                                        propertyValueMap.put(path.getProperty(), List.of("undefined"));
+                                    }
+                                } else {
+                                    JSONArray jsonA = jsonT.optJSONArray(list.get(i));
+                                    List<String> valueList = new ArrayList<>();
+                                    for (int j = 0; j < jsonA.length(); j++) {
+                                        valueList.add(jsonA.getString(j));
+                                    }
+                                    if (propertyValueMap.containsKey(path.getProperty())) {
+                                        List<String> values = propertyValueMap.get(path.getProperty());
+                                        values.addAll(valueList);
+                                        propertyValueMap.replace(path.getProperty(), values);
+                                    } else {
+                                        propertyValueMap.put(path.getProperty(), valueList);
+                                    }
+                                    break;
+
+                                }
+                            } else if (jsonT.optJSONArray(list.get(list.size() - 2)) != null) {
+
+                                JSONArray jsonA = jsonT.optJSONArray(list.get(list.size() - 2));
+                                String value = jsonA.optString(Integer.parseInt(list.get(i)));
+                                if (propertyValueMap.containsKey(path.getProperty())) {
+                                    List<String> values = propertyValueMap.get(path.getProperty());
+                                    values.add(value);
+                                    propertyValueMap.replace(path.getProperty(), values);
+                                } else {
+                                    propertyValueMap.put(path.getProperty(), List.of(value));
+                                }
+                                break;
+                            } else if (jsonT.optString(list.get(i)) != null && jsonT.has(list.get(i))) {
+                                String value = jsonT.optString(list.get(i));
+                                if (propertyValueMap.containsKey(path.getProperty())) {
+                                    List<String> values = propertyValueMap.get(path.getProperty());
+                                    values.add(value);
+                                    propertyValueMap.replace(path.getProperty(), values);
+                                } else {
+                                    propertyValueMap.put(path.getProperty(), List.of(value));
+                                }
+                                break;
+                            } else {
+                                var keys = jsonT.keys();
+                                while (keys.hasNext()) {
+                                    String key = keys.next().toString();
+                                    if (jsonT.optJSONArray(key) != null) {
+                                        JSONArray jsonA = jsonT.optJSONArray(key);
+                                        for (int j = 0; j < jsonA.length(); j++) {
+                                            String value = jsonA.getJSONObject(j).getString(list.get(i));
+                                            if (propertyValueMap.containsKey(path.getProperty())) {
+                                                List<String> values = propertyValueMap.get(path.getProperty());
+                                                values.add(value);
+                                                propertyValueMap.replace(path.getProperty(), values);
+                                            } else {
+                                                propertyValueMap.put(path.getProperty(), List.of(value));
+                                            }
+                                        }
+
+                                    }
+                                }
+                            }
+                        }
+
+                    }
+                }
+            } catch (JSONException e1) {
+                if (propertyValueMap.containsKey(path.getProperty())) {
+                    List<String> values = propertyValueMap.get(path.getProperty());
+                    values.add("undefined");
+                    propertyValueMap.replace(path.getProperty(), values);
+                } else {
+                    propertyValueMap.put(path.getProperty(), List.of("undefined"));
+                }
+                LOGGER.warn(e1.getMessage());
+            }
+        }
+        propertyValueList.add(propertyValueMap);
+        return CompletableFuture.completedFuture(propertyValueList);
+    }
+
+    @Async
+    public CompletableFuture<List<Map<String, List<String>>>> getAllValuesFomJsonpath(JSONObject json, List<Path> paths) {
+        Map<String, List<String>> propertyValueMap = new HashMap<>();
+        List<Map<String, List<String>>> propertyValueList = new ArrayList<>();
+
+        for (Path path : paths) {
+            DocumentContext jsonContext = JsonPath.parse(json.toString());
+            Object value;
+            try {
+                value = jsonContext.read(path.getPath());
+            } catch (PathNotFoundException e) {
+                LOGGER.warn(e.getMessage());
+                value = "undefined";
+            }
+
+            if (value instanceof String) {
+                String valueString = (String) value;
+                if (propertyValueMap.containsKey(path.getProperty())) {
+                    List<String> values = propertyValueMap.get(path.getProperty());
+                    values.add(valueString);
+                    propertyValueMap.replace(path.getProperty(), values);
+                } else {
+                    propertyValueMap.put(path.getProperty(), List.of(valueString));
+                }
+            } else if (value instanceof Integer || value instanceof Long) {
+                String valueInt = "" + value;
+                if (propertyValueMap.containsKey(path.getProperty())) {
+                    List<String> values = propertyValueMap.get(path.getProperty());
+                    values.add(String.valueOf(valueInt));
+                    propertyValueMap.replace(path.getProperty(), values);
+                } else {
+                    propertyValueMap.put(path.getProperty(), List.of(String.valueOf(valueInt)));
+                }
+            } else if (value instanceof net.minidev.json.JSONArray) {
+                List<String> valueList = (List<String>) value;
+                if (propertyValueMap.containsKey(path.getProperty())) {
+                    List<String> values = propertyValueMap.get(path.getProperty());
+                    values.addAll(valueList);
+                    propertyValueMap.replace(path.getProperty(), values);
+                } else {
+                    propertyValueMap.put(path.getProperty(), valueList);
+                }
+            }
+        }
+        propertyValueList.add(propertyValueMap);
+        return CompletableFuture.completedFuture(propertyValueList);
+    }
+
+    public Map<String, List<Map<String, List<String>>>> mapToDcat(String urlRepo, List<Path> paths, boolean isJsonpath) throws JSONException, ExecutionException, InterruptedException {
+        JSONObject jsonDataset = this.getJson(urlRepo).get();
+        boolean distributionPathIsPresent = paths.stream().anyMatch(e -> e.getProperty().equals("dcat:distribution"));
+
+        List<Path> datasetPaths = paths.stream().filter(e -> e.getDcatClass().equals("dcat:dataset")).collect(Collectors.toList());
+        List<Path> distributionPaths = paths.stream().filter(e -> e.getDcatClass().equals("dcat:distribution")).collect(Collectors.toList());
+
+        List<Map<String, List<String>>> datasetValues = (isJsonpath) ? this.getAllValuesFomJsonpath(jsonDataset, datasetPaths).get() : this.getAllValuesFomCustomPath(jsonDataset, datasetPaths).get();
+        List<Map<String, List<String>>> distributionValues = new ArrayList<>();
+        if (distributionPathIsPresent) {
+
+            String distributionPath = paths.stream().filter(e -> e.getProperty().equals("dcat:distribution") && e.getDcatClass().equals("dcat:distribution")).map(Path::getPath).collect(Collectors.toList()).get(0);
+            distributionPaths.remove(0);
+
+            try {
+                JSONArray jsonDistribution = new JSONArray();
+                if (!isJsonpath) {
+                    List<String> parentDistributionPaths = List.of(distributionPath.split(" : "));
+                    JSONObject jsonT = jsonDataset;
+                    for (int i = 0; i < parentDistributionPaths.size(); i++) {
+
+                        if (i < parentDistributionPaths.size() - 1) {
+                            jsonT = jsonT.getJSONObject(parentDistributionPaths.get(i));
+                        } else {
+                            jsonDistribution = jsonT.getJSONArray(parentDistributionPaths.get(i));
+                        }
+                    }
+                } else {
+                    DocumentContext jsonContext = JsonPath.parse(jsonDataset.toString());
+                    net.minidev.json.JSONArray read = jsonContext.read(distributionPath);
+
+                    for (Object dist : read) {
+                        jsonDistribution.put(new JSONObject((LinkedHashMap) dist));
+                    }
+
+                }
+
+
+                for (int i = 0; i < jsonDistribution.length(); i++) {
+                    if ((isJsonpath)) {
+                        distributionValues.addAll(this.getAllValuesFomJsonpath((JSONObject) jsonDistribution.get(i), distributionPaths).get());
+                    } else {
+                        distributionValues.addAll(this.getAllValuesFomCustomPath((JSONObject) jsonDistribution.get(i), distributionPaths).get());
+                    }
+                }
+            } catch (PathNotFoundException e) {
+                LOGGER.error(e.getMessage());
+            }
+        } else {
+            distributionValues = (isJsonpath) ? this.getAllValuesFomJsonpath(jsonDataset, distributionPaths).get() : this.getAllValuesFomCustomPath(jsonDataset, distributionPaths).get();
+        }
+
+        Map<String, List<Map<String, List<String>>>> valuesMap = new HashMap<>();
+        valuesMap.put("dataset", datasetValues);
+        valuesMap.put("distribution", distributionValues);
+        return valuesMap;
+    }
+
+
+    @Async
+    public CompletableFuture<ResponseEntity<String>> asyncPostToFdp(String path, String fdpUrl, String dataset, String fdpToken) throws BadRequest {
+
+        HttpHeaders headers = new HttpHeaders();
+        headers.add("Accept", "text/turtle;charset=UTF-8");
+        headers.add("Content-Type", "text/turtle;charset=UTF-8");
+        headers.setBearerAuth(fdpToken);
+
+        HttpEntity<String> entity = new HttpEntity<>(dataset, headers);
+
+        return CompletableFuture.completedFuture(this.restTemplate.exchange(fdpUrl + path, HttpMethod.POST, entity, String.class));
+
+    }
+
+    @Async
+    public void draftToPublished(String fdpToken, String url) {
+        HttpHeaders headers = new HttpHeaders();
+
+        headers.setBearerAuth(fdpToken);
+        headers.setContentType(MediaType.APPLICATION_JSON);
+
+        HttpEntity<PublishedRequest> entity = new HttpEntity<>(new PublishedRequest("PUBLISHED"), headers);
+
+        this.restTemplate.exchange(url + "/meta/state", HttpMethod.PUT, entity, Void.class);
+    }
 
 }
diff --git a/src/main/java/com/smartharvester/util/SmartHarvesterRequestUtil.java b/src/main/java/com/smartharvester/util/SmartHarvesterRequestUtil.java
index 8aab62e..0c589b9 100644
--- a/src/main/java/com/smartharvester/util/SmartHarvesterRequestUtil.java
+++ b/src/main/java/com/smartharvester/util/SmartHarvesterRequestUtil.java
@@ -24,7 +24,9 @@ import org.springframework.web.client.RestTemplate;
 @Component
 public class SmartHarvesterRequestUtil {
 
-    public JSONObject getJson(String urlRepo) throws JSONException {
+
+    @Async
+    public CompletableFuture<JSONObject> getJson(String urlRepo) throws JSONException {
         URL url;
         HttpURLConnection urlConnection = null;
         String result = null;
@@ -45,7 +47,7 @@ public class SmartHarvesterRequestUtil {
 
         json = new JSONObject(result);
 
-        return json;
+        return CompletableFuture.completedFuture(json);
     }
 
     private  String readStream(InputStream input) throws IOException {
-- 
GitLab