Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Pfeil committed Nov 16, 2024
1 parent c7e6cbb commit d4317c8
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 15 deletions.
2 changes: 1 addition & 1 deletion src/main/java/edu/kit/datamanager/pit/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public class Application {
protected static final String ERROR_COMMUNICATION = "Communication error: {}";
protected static final String ERROR_CONFIGURATION = "Configuration error: {}";

protected static final Executor EXECUTOR = Executors.newWorkStealingPool();
public static final Executor EXECUTOR = Executors.newWorkStealingPool();


@Bean
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/edu/kit/datamanager/pit/domain/ImmutableList.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package edu.kit.datamanager.pit.domain;

import java.util.Collections;
import java.util.List;

public record ImmutableList<I>(List<I> items) {
public ImmutableList {
items = Collections.unmodifiableList(items);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public void validate(PIDRecord pidRecord) throws RecordValidationException, Exte
"Profile attribute " + profileKey + " has no values.");
}

List<CompletableFuture<?>> futures = Streams.stream(Arrays.stream(profilePIDs))
List<CompletableFuture<?>> futures = Streams.failableStream(Arrays.stream(profilePIDs))
.map(profilePID -> {
try {
return this.typeLoader.get(profilePID)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
package edu.kit.datamanager.pit.typeregistry;

import java.io.IOException;
import org.everit.json.schema.Schema;

import edu.kit.datamanager.pit.domain.TypeDefinition;

import java.net.URISyntaxException;
import java.util.concurrent.CompletableFuture;

/**
* Main abstraction interface towards the type registry. Contains all methods
* required from the registry by the core services.
*
*/
public interface ITypeRegistry {

/**
* Queries a type definition record from the type registry.
*
* @param typeIdentifier
* @return a type definition record or null if the type is not registered.
* @throws IOException on communication errors with a remote registry
*/
public TypeDefinition queryTypeDefinition(String typeIdentifier) throws IOException, URISyntaxException;
CompletableFuture<Schema> queryAttributeSchemaOf(String pid);
CompletableFuture<RegisteredProfile> queryAsProfile(String pid);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package edu.kit.datamanager.pit.typeregistry;

import edu.kit.datamanager.pit.domain.ImmutableList;

public record RegisteredProfile(
String pid,
ImmutableList<String> attributePids,
ImmutableList<Boolean> attributeObligations,
ImmutableList<Boolean> attributeCardinalities
) {
}
139 changes: 139 additions & 0 deletions src/main/java/edu/kit/datamanager/pit/typeregistry/impl/TypeApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package edu.kit.datamanager.pit.typeregistry.impl;

import com.fasterxml.jackson.databind.JsonNode;
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import edu.kit.datamanager.pit.Application;
import edu.kit.datamanager.pit.common.ExternalServiceException;
import edu.kit.datamanager.pit.common.TypeNotFoundException;
import edu.kit.datamanager.pit.configuration.ApplicationProperties;
import edu.kit.datamanager.pit.typeregistry.ITypeRegistry;
import edu.kit.datamanager.pit.typeregistry.RegisteredProfile;
import org.everit.json.schema.Schema;
import org.everit.json.schema.loader.SchemaLoader;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.client.RestClient;

import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class TypeApi implements ITypeRegistry {

private static final Logger LOG = LoggerFactory.getLogger(TypeApi.class);
// TODO put into config class to read it from application.properties
private static final String BASE_URL = "https://typeapi.lab.pidconsortium.net";

protected final RestClient http;
protected final AsyncLoadingCache<String, Schema> schemaCache;
protected final AsyncLoadingCache<String, RegisteredProfile> profileCache;

public TypeApi(ApplicationProperties properties) throws URISyntaxException {
String baseUri = new URI(BASE_URL).resolve("v1/types").toString();
this.http = RestClient.builder().baseUrl(baseUri).build();

int maximumSize = properties.getMaximumSize();
long expireAfterWrite = properties.getExpireAfterWrite();
this.schemaCache = Caffeine.newBuilder()
.maximumSize(maximumSize)
.executor(Application.EXECUTOR)
.refreshAfterWrite(Duration.ofMinutes(expireAfterWrite / 2))
.expireAfterWrite(expireAfterWrite, TimeUnit.MINUTES)
.removalListener((key, value, cause) ->
LOG.trace("Removing schema of {} from schema cache. Cause: {}", key, cause)
)
.buildAsync(pid -> {
LOG.trace("Loading schema for identifier {} to cache.", pid);
return this.querySchema(pid);
});
this.profileCache = Caffeine.newBuilder()
.maximumSize(maximumSize)
.executor(Application.EXECUTOR)
.refreshAfterWrite(Duration.ofMinutes(expireAfterWrite / 2))
.expireAfterWrite(expireAfterWrite, TimeUnit.MINUTES)
.removalListener((key, value, cause) ->
LOG.trace("Removing schema of {} from schema cache. Cause: {}", key, cause)
)
.buildAsync(pid -> {
LOG.trace("Loading schema for identifier {} to cache.", pid);
return this.queryProfile(pid);
});
}

protected Schema querySchema(String pid) throws TypeNotFoundException, ExternalServiceException {
return http.get()
.uri(uriBuilder -> uriBuilder
.pathSegment("schema")
.path(pid)
.build())
.exchange((clientRequest, clientResponse) -> {
if (clientResponse.getStatusCode().is2xxSuccessful()) {
InputStream inputStream = clientResponse.getBody();
Schema schema;
try {
JSONObject rawSchema = new JSONObject(new JSONTokener(inputStream));
schema = SchemaLoader.load(rawSchema);
} catch (JSONException e) {
throw new ExternalServiceException("Type-Api", "Response (" + pid + ") is not a valid schema.");
} finally {
inputStream.close();
}
return schema;
} else {
throw new TypeNotFoundException(pid);
}
});
}

protected RegisteredProfile queryProfile(String pid) throws TypeNotFoundException, ExternalServiceException {
return http.get()
.uri(uriBuilder -> uriBuilder
.path(pid)
.build())
.exchange((clientRequest, clientResponse) -> {
if (clientResponse.getStatusCode().is2xxSuccessful()) {
InputStream inputStream = clientResponse.getBody();
String body = new String(inputStream.readAllBytes());
inputStream.close();
return extractProfileInformation(pid, Application.jsonObjectMapper().readTree(body));
} else {
throw new TypeNotFoundException(pid);
}
});
}

private RegisteredProfile extractProfileInformation(String pid, JsonNode body) {
body.path("content").path("properties").forEach(item -> {
String pid = item.path("pid").asText(
item.path("identifier").asText(
item.path("id").asText(
null)));
JsonNode representations = item.path("representationsAndSemantics")
.path(0);
representations.path("obligation").asText();
});
RegisteredProfile p = new RegisteredProfile(
body.path("pid").asText(),
);
if (p.pid() == null) {
throw new ExternalServiceException("TypeApi", "Received JSON without PID for " + pid);
}
}

@Override
public CompletableFuture<Schema> queryAttributeSchemaOf(String pid) {
return this.schemaCache.get(pid);
}

@Override
public CompletableFuture<RegisteredProfile> queryAsProfile(String pid) {
return this.profileCache.get(pid);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package edu.kit.datamanager.pit.typeregistry.impl;

import org.junit.jupiter.api.Test;
import org.springframework.web.client.RestClient;

import java.net.URI;
import java.net.URISyntaxException;

import static org.junit.jupiter.api.Assertions.*;

class TypeApiTest {
@Test
void testUriCreation() throws URISyntaxException {
String url = new URI("https://example.com").resolve("v1/types").toString();
assertEquals("https://example.com/v1/types", url);
RestClient.create(url).get().uri(uriBuilder -> {
return uriBuilder.build();
});
}
}

0 comments on commit d4317c8

Please sign in to comment.