-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
186 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
src/main/java/edu/kit/datamanager/pit/domain/ImmutableList.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 4 additions & 13 deletions
17
src/main/java/edu/kit/datamanager/pit/typeregistry/ITypeRegistry.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
11 changes: 11 additions & 0 deletions
11
src/main/java/edu/kit/datamanager/pit/typeregistry/RegisteredProfile.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
139
src/main/java/edu/kit/datamanager/pit/typeregistry/impl/TypeApi.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
src/test/java/edu/kit/datamanager/pit/typeregistry/impl/TypeApiTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); | ||
} | ||
} |