diff --git a/docker/Dockerfile b/docker/Dockerfile index 35eb42b..43d7d91 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM eclipse-temurin:17-jre +FROM eclipse-temurin:17-jre-jammy ARG CONNECTOR_JAR # System env vars diff --git a/extensions/auth-oauth2-jwt/src/main/java/org/upm/inesdata/auth/Oauth2JwtAuthenticationExtension.java b/extensions/auth-oauth2-jwt/src/main/java/org/upm/inesdata/auth/Oauth2JwtAuthenticationExtension.java index d0ec2a3..bf8a085 100644 --- a/extensions/auth-oauth2-jwt/src/main/java/org/upm/inesdata/auth/Oauth2JwtAuthenticationExtension.java +++ b/extensions/auth-oauth2-jwt/src/main/java/org/upm/inesdata/auth/Oauth2JwtAuthenticationExtension.java @@ -1,6 +1,7 @@ package org.upm.inesdata.auth; import org.eclipse.edc.api.auth.spi.AuthenticationService; +import org.eclipse.edc.api.auth.spi.registry.ApiAuthenticationRegistry; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provides; @@ -29,6 +30,8 @@ public class Oauth2JwtAuthenticationExtension implements ServiceExtension { @Inject private IdentityService identityService; @Inject + private ApiAuthenticationRegistry authenticationRegistry; + @Inject private Vault vault; @Override @@ -45,6 +48,6 @@ public void initialize(ServiceExtensionContext context) { var rolesConfig = context.getConfig(AUTH_SETTING_ALLOWEDROLES); List allowedRoles = rolesConfig.partition().map(conf -> conf.getString(ROLE_PROPERTY_NAME)).collect(Collectors.toList()); - context.registerService(AuthenticationService.class, new Oauth2JwtAuthenticationService(identityService, participantId, allowedRoles)); + authenticationRegistry.register("management-api", new Oauth2JwtAuthenticationService(identityService, participantId, allowedRoles)); } } diff --git a/extensions/inesdata-search-extension/src/main/java/org/upm/inesdata/search/extension/CriterionToWhereClauseConverterImpl.java b/extensions/inesdata-search-extension/src/main/java/org/upm/inesdata/search/extension/CriterionToWhereClauseConverterImpl.java index ef1b007..c219c15 100644 --- a/extensions/inesdata-search-extension/src/main/java/org/upm/inesdata/search/extension/CriterionToWhereClauseConverterImpl.java +++ b/extensions/inesdata-search-extension/src/main/java/org/upm/inesdata/search/extension/CriterionToWhereClauseConverterImpl.java @@ -107,9 +107,16 @@ private WhereClause generateVocabularyWhereClause(Criterion criterion) { switch (propertiesList.length) { case 3 -> - generateNonObjectPropertySQL(sqlWhereBuilder, propertiesList, criterion.getOperandRight().toString()); - case 4 -> + generateNonObjectPropertySQL(sqlWhereBuilder, propertiesList, criterion.getOperandRight().toString(), false); + case 4 -> { + if (propertiesList[3].equals("'@id'")) { + generateNonObjectPropertySQL(sqlWhereBuilder, propertiesList, criterion.getOperandRight().toString(), true); + } else { generateObjectPropertySQL(sqlWhereBuilder, propertiesList, criterion.getOperandRight().toString()); + } + + } + default -> throw new InvalidRequestException("Invalid vocabulary argument in the operandLeft: %s" .formatted(criterion.getOperandLeft().toString())); } @@ -117,18 +124,6 @@ private WhereClause generateVocabularyWhereClause(Criterion criterion) { return new WhereClause(sqlWhereBuilder.toString(), unmodifiableCollection(new ArrayList<>())); } - private void generateNonObjectPropertySQL(StringBuilder sqlWhereBuilder, String[] propertiesList, String operandRight) { - sqlWhereBuilder.append("(properties::jsonb -> ") - .append(propertiesList[0]) - .append(" -> ") - .append(propertiesList[1]) - .append(")::jsonb @> '[{") - .append(propertiesList[2].replaceAll("'", "\"")) - .append(": [{\"@value\": \"") - .append(operandRight) - .append("\"}]}]'::jsonb"); - } - private void generateObjectPropertySQL(StringBuilder sqlWhereBuilder, String[] propertiesList, String operandRight) { sqlWhereBuilder.append("EXISTS (SELECT 1 FROM jsonb_array_elements((properties::jsonb -> ") .append(propertiesList[0]) @@ -143,6 +138,18 @@ private void generateObjectPropertySQL(StringBuilder sqlWhereBuilder, String[] p .append("\"}]}]')"); } + private void generateNonObjectPropertySQL(StringBuilder sqlWhereBuilder, String[] propertiesList, String operandRight, boolean isIdProperty) { + sqlWhereBuilder.append("(properties::jsonb -> ") + .append(propertiesList[0]) + .append(" -> ") + .append(propertiesList[1]) + .append(")::jsonb @> '[{") + .append(propertiesList[2].replaceAll("'", "\"")) + .append(isIdProperty ? ": [{\"@id\": \"" : ": [{\"@value\": \"") + .append(operandRight) + .append("\"}]}]'::jsonb"); + } + private String[] splitByDotOutsideQuotes(String input) { List parts = new ArrayList<>(); diff --git a/extensions/participants-from-registration-service/src/main/java/org/upm/inesdata/catalog/FromRegistrationServiceParticipantsExtension.java b/extensions/participants-from-registration-service/src/main/java/org/upm/inesdata/catalog/FromRegistrationServiceParticipantsExtension.java index e66d7dc..ffa2723 100644 --- a/extensions/participants-from-registration-service/src/main/java/org/upm/inesdata/catalog/FromRegistrationServiceParticipantsExtension.java +++ b/extensions/participants-from-registration-service/src/main/java/org/upm/inesdata/catalog/FromRegistrationServiceParticipantsExtension.java @@ -1,5 +1,6 @@ package org.upm.inesdata.catalog; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.edc.catalog.directory.InMemoryNodeDirectory; import org.eclipse.edc.crawler.spi.TargetNodeDirectory; @@ -35,7 +36,10 @@ public class FromRegistrationServiceParticipantsExtension implements ServiceExte public void initialize(ServiceExtensionContext context) { var periodSeconds = context.getSetting(EXECUTION_PLAN_PERIOD_SECONDS, DEFAULT_EXECUTION_PERIOD_SECONDS); var monitor = context.getMonitor(); - var participantRegistrationService = new ParticipantRegistrationService(monitor, new ObjectMapper()); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + var participantRegistrationService = new ParticipantRegistrationService(monitor, objectMapper); // Initial update updateTargetNodeDirectory(context, participantRegistrationService); diff --git a/extensions/participants-from-registration-service/src/main/java/org/upm/inesdata/catalog/ParticipantRegistrationService.java b/extensions/participants-from-registration-service/src/main/java/org/upm/inesdata/catalog/ParticipantRegistrationService.java index cb9e026..57f2bdf 100644 --- a/extensions/participants-from-registration-service/src/main/java/org/upm/inesdata/catalog/ParticipantRegistrationService.java +++ b/extensions/participants-from-registration-service/src/main/java/org/upm/inesdata/catalog/ParticipantRegistrationService.java @@ -4,6 +4,7 @@ import java.util.List; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.ws.rs.client.Client; @@ -47,9 +48,6 @@ public ParticipantRegistrationService(Monitor monitor, ObjectMapper objectMapper this.objectMapper = objectMapper; } - - - /** * Makes an HTTP GET request to the specified URL and returns the response as a string. * @@ -72,29 +70,61 @@ public String makeHttpGetRequest(String url, Result tokenRe * @return list of TargetNodes from configuration */ public List getTargetNodes(Config baseConfig, Result tokenRepresentationResult) { + var response = getNodes(baseConfig, tokenRepresentationResult); + if(response != null){ + return processResponseToTargetNodes(response); + }else{ + return new ArrayList<>(); + } + } + + /** + * Retrieve SharedUrlParticipant from configuration + * + * @param baseConfig EDC Configuration + * @param tokenRepresentationResult token + * @return list of SharedUrlParticipant from configuration + */ + public List getSharedUrlParticipantNodes(Config baseConfig, Result tokenRepresentationResult) { + var response = getNodes(baseConfig, tokenRepresentationResult); + if(response != null){ + return processResponseToSharedUrlParticipant(response); + }else{ + return new ArrayList<>(); + } + } + + private String getNodes(Config baseConfig, Result tokenRepresentationResult) { var participantsConfig = baseConfig.getConfig(EDC_CATALOG_REGISTRATION_SERVICE_HOST); if (participantsConfig.getEntries().isEmpty()) { monitor.severe("Error processing url registration service."); - return new ArrayList<>(); + return null; } else { var url = participantsConfig.getEntries().get(EDC_CATALOG_REGISTRATION_SERVICE_HOST) + RESOURCE_URL; try { - String response = makeHttpGetRequest(url, tokenRepresentationResult); - if(response==null){ - return new ArrayList<>(); - } - // Process the response and convert it to TargetNodes - // Assuming a method processResponseToTargetNodes(response) - return processResponseToTargetNodes(response); + return makeHttpGetRequest(url, tokenRepresentationResult); } catch (Exception e) { monitor.severe("Exception occurred while making HTTP GET request: " + e.getMessage()); - return new ArrayList<>(); + return null; } } } + + private List processResponseToSharedUrlParticipant(String response) { + List sharedUrlParticipants = new ArrayList<>(); + + try { + sharedUrlParticipants = objectMapper.readValue(response, new TypeReference<>() {}); + } catch (Exception e) { + monitor.severe("Failed to deserialize the registration service response"); + } + + return sharedUrlParticipants; + } + private List processResponseToTargetNodes(String response) { List targetNodes = new ArrayList<>(); diff --git a/extensions/participants-from-registration-service/src/main/java/org/upm/inesdata/catalog/SharedUrlParticipant.java b/extensions/participants-from-registration-service/src/main/java/org/upm/inesdata/catalog/SharedUrlParticipant.java new file mode 100644 index 0000000..255adb9 --- /dev/null +++ b/extensions/participants-from-registration-service/src/main/java/org/upm/inesdata/catalog/SharedUrlParticipant.java @@ -0,0 +1,31 @@ +package org.upm.inesdata.catalog; + +public class SharedUrlParticipant { + private String participantId; + private String url; + private String sharedUrl; + + public String getParticipantId() { + return participantId; + } + + public void setParticipantId(String participantId) { + this.participantId = participantId; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getSharedUrl() { + return sharedUrl; + } + + public void setSharedUrl(String sharedUrl) { + this.sharedUrl = sharedUrl; + } +} diff --git a/extensions/shared-api-configuration/README.md b/extensions/shared-api-configuration/README.md new file mode 100644 index 0000000..e69de29 diff --git a/extensions/shared-api-configuration/build.gradle.kts b/extensions/shared-api-configuration/build.gradle.kts new file mode 100644 index 0000000..a7f8129 --- /dev/null +++ b/extensions/shared-api-configuration/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + `java-library` + id("com.gmv.inesdata.edc-application") +} + +dependencies { + api(libs.edc.web.spi) + api(libs.edc.auth.spi) + api(libs.edc.iam.oauth2.service) + api(libs.edc.spi.jsonld) + api(libs.edc.jersey.providers.lib) +} \ No newline at end of file diff --git a/extensions/shared-api-configuration/src/main/java/org/upm/inesdata/api/shared/configuration/SharedApiConfigurationExtension.java b/extensions/shared-api-configuration/src/main/java/org/upm/inesdata/api/shared/configuration/SharedApiConfigurationExtension.java new file mode 100644 index 0000000..294decc --- /dev/null +++ b/extensions/shared-api-configuration/src/main/java/org/upm/inesdata/api/shared/configuration/SharedApiConfigurationExtension.java @@ -0,0 +1,111 @@ +package org.upm.inesdata.api.shared.configuration; + +import org.eclipse.edc.api.auth.spi.AuthenticationRequestFilter; +import org.eclipse.edc.api.auth.spi.registry.ApiAuthenticationRegistry; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Provides; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.runtime.metamodel.annotation.SettingContext; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.iam.IdentityService; +import org.eclipse.edc.spi.system.ExecutorInstrumentation; +import org.eclipse.edc.spi.system.Hostname; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.types.TypeManager; +import org.eclipse.edc.web.jersey.providers.jsonld.JerseyJsonLdInterceptor; +import org.eclipse.edc.web.jersey.providers.jsonld.ObjectMapperProvider; +import org.eclipse.edc.web.spi.WebServer; +import org.eclipse.edc.web.spi.WebService; +import org.eclipse.edc.web.spi.configuration.WebServiceConfiguration; +import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; +import org.eclipse.edc.web.spi.configuration.WebServiceSettings; + +import java.net.URI; + +import static java.lang.String.format; +import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_PREFIX; +import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_SCHEMA; +import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; + +/** + * This extension provides generic endpoints which are open to all connectors. + */ +@Provides(SharedApiUrl.class) +@Extension(value = SharedApiConfigurationExtension.NAME) +public class SharedApiConfigurationExtension implements ServiceExtension { + public static final String NAME = "Shared Public API"; + + private static final int DEFAULT_SHARED_PORT = 8186; + private static final String SHARED_CONTEXT_PATH = "/api/v1/shared"; + + @SettingContext("Shared API context setting key") + private static final String SHARED_CONFIG_KEY = "web.http.shared"; + + @Setting(value = "Base url of the shared API endpoint without the trailing slash. This should correspond to the values configured " + + "in '" + DEFAULT_SHARED_PORT + "' and '" + SHARED_CONTEXT_PATH + "'.", defaultValue = "http://:" + DEFAULT_SHARED_PORT + SHARED_CONTEXT_PATH) + private static final String SHARED_ENDPOINT = "edc.shared.endpoint"; + + private static final WebServiceSettings SHARED_SETTINGS = WebServiceSettings.Builder.newInstance() + .apiConfigKey(SHARED_CONFIG_KEY) + .contextAlias("shared") + .defaultPath(SHARED_CONTEXT_PATH) + .defaultPort(DEFAULT_SHARED_PORT) + .name(NAME) + .build(); + + private static final String SHARED_SCOPE = "SHARED_API"; + + @Inject + private WebServer webServer; + @Inject + private ApiAuthenticationRegistry authenticationRegistry; + @Inject + private WebServiceConfigurer webServiceConfigurer; + @Inject + private WebService webService; + @Inject + private ExecutorInstrumentation executorInstrumentation; + @Inject + private Hostname hostname; + @Inject + private IdentityService identityService; + @Inject + private JsonLd jsonLd; + @Inject + private TypeManager typeManager; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + var config = context.getConfig(SHARED_CONFIG_KEY); + var configuration = webServiceConfigurer.configure(config, webServer, SHARED_SETTINGS); + + context.registerService(SharedApiUrl.class, sharedApiUrl(context, configuration)); + + var authenticationFilter = new AuthenticationRequestFilter(authenticationRegistry, "shared-api"); + webService.registerResource("shared", authenticationFilter); + + jsonLd.registerNamespace(ODRL_PREFIX, ODRL_SCHEMA, SHARED_SCOPE); + var jsonLdMapper = typeManager.getMapper(JSON_LD); + webService.registerResource("shared", new ObjectMapperProvider(jsonLdMapper)); + webService.registerResource("shared", new JerseyJsonLdInterceptor(jsonLd, jsonLdMapper, SHARED_SCOPE)); + } + + private SharedApiUrl sharedApiUrl(ServiceExtensionContext context, WebServiceConfiguration config) { + var callbackAddress = context.getSetting(SHARED_ENDPOINT, format("http://%s:%s%s", hostname.get(), config.getPort(), config.getPath())); + try { + var url = URI.create(callbackAddress); + return () -> url; + } catch (IllegalArgumentException e) { + context.getMonitor().severe("Error creating shared endpoint url", e); + throw new EdcException(e); + } + } +} diff --git a/extensions/shared-api-configuration/src/main/java/org/upm/inesdata/api/shared/configuration/SharedApiUrl.java b/extensions/shared-api-configuration/src/main/java/org/upm/inesdata/api/shared/configuration/SharedApiUrl.java new file mode 100644 index 0000000..7d44526 --- /dev/null +++ b/extensions/shared-api-configuration/src/main/java/org/upm/inesdata/api/shared/configuration/SharedApiUrl.java @@ -0,0 +1,17 @@ +package org.upm.inesdata.api.shared.configuration; + +import java.net.URI; + +/** + * Provides the Shared Api URL exposed, useful for setting callbacks. + */ +@FunctionalInterface +public interface SharedApiUrl { + + /** + * URI on which the Shared API is exposed + * + * @return Shared API URI. + */ + URI get(); +} diff --git a/extensions/shared-api-configuration/src/main/java/org/upm/inesdata/api/shared/configuration/auth/SharedOauth2JwtAuthenticationExtension.java b/extensions/shared-api-configuration/src/main/java/org/upm/inesdata/api/shared/configuration/auth/SharedOauth2JwtAuthenticationExtension.java new file mode 100644 index 0000000..1a2bed8 --- /dev/null +++ b/extensions/shared-api-configuration/src/main/java/org/upm/inesdata/api/shared/configuration/auth/SharedOauth2JwtAuthenticationExtension.java @@ -0,0 +1,39 @@ +package org.upm.inesdata.api.shared.configuration.auth; + +import org.eclipse.edc.api.auth.spi.AuthenticationService; +import org.eclipse.edc.api.auth.spi.registry.ApiAuthenticationRegistry; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Provides; +import org.eclipse.edc.spi.iam.IdentityService; +import org.eclipse.edc.spi.security.Vault; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.upm.inesdata.api.shared.configuration.auth.service.SharedOauth2JwtAuthenticationService; + +/** + * Extension that registers an SharedAuthenticationService that uses Oauth2 JWT Tokens + */ +@Provides(AuthenticationService.class) +@Extension(value = SharedOauth2JwtAuthenticationExtension.NAME) +public class SharedOauth2JwtAuthenticationExtension implements ServiceExtension { + + public static final String NAME = "Shared Oauth2 JWT Authentication"; + + @Inject + private IdentityService identityService; + @Inject + private ApiAuthenticationRegistry authenticationRegistry; + @Inject + private Vault vault; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + authenticationRegistry.register("shared-api",new SharedOauth2JwtAuthenticationService(identityService)); + } +} diff --git a/extensions/shared-api-configuration/src/main/java/org/upm/inesdata/api/shared/configuration/auth/service/SharedOauth2JwtAuthenticationService.java b/extensions/shared-api-configuration/src/main/java/org/upm/inesdata/api/shared/configuration/auth/service/SharedOauth2JwtAuthenticationService.java new file mode 100644 index 0000000..6d2917b --- /dev/null +++ b/extensions/shared-api-configuration/src/main/java/org/upm/inesdata/api/shared/configuration/auth/service/SharedOauth2JwtAuthenticationService.java @@ -0,0 +1,69 @@ +package org.upm.inesdata.api.shared.configuration.auth.service; + +import jakarta.ws.rs.core.HttpHeaders; +import org.eclipse.edc.api.auth.spi.AuthenticationService; +import org.eclipse.edc.spi.iam.IdentityService; +import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.web.spi.exception.AuthenticationFailedException; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * SharedAuthenticationService for access control based on JWT tokens + */ +public class SharedOauth2JwtAuthenticationService implements AuthenticationService { + private static final String BEARER_PREFIX = "Bearer "; + + private static final String AUTHORIZATION_HEADER_ERROR = HttpHeaders.AUTHORIZATION + " header not found"; + private static final String BEARER_PREFIX_ERROR = "Bearer token not found"; + + + private final IdentityService identityService; + + public SharedOauth2JwtAuthenticationService(IdentityService identityService) { + this.identityService = identityService; + } + + /** + * Checks whether a particular request is authorized based on the "Authorization" header. + * The header must contains a valid JWT token + * + * @param headers the headers + * @throws IllegalArgumentException The map of headers did not contain the "Authorization" header + * @return whether it is a valid JWT token or not + */ + @Override + public boolean isAuthenticated(Map> headers) { + + Objects.requireNonNull(headers, "headers"); + + // Get the Authorization header + var apiKey = headers.keySet().stream() + .filter(k -> k.equalsIgnoreCase(HttpHeaders.AUTHORIZATION)) + .map(headers::get) + .findFirst(); + + return apiKey.map(this::checkTokenValid).orElseThrow(() -> new AuthenticationFailedException(AUTHORIZATION_HEADER_ERROR)); + } + + private boolean checkTokenValid(List tokenKeys) { + return tokenKeys.size() == 1 && tokenKeys.stream().allMatch(this::isJwtTokenValid); + } + + @SuppressWarnings("unchecked") + private boolean isJwtTokenValid(String jwtToken) { + boolean valid = false; + + if (!jwtToken.startsWith(BEARER_PREFIX)) { + throw new AuthenticationFailedException(BEARER_PREFIX_ERROR); + } + + var tokenRepresentation = TokenRepresentation.Builder.newInstance().token(jwtToken.replace(BEARER_PREFIX, "")).build(); + var tokenValidation = identityService.verifyJwtToken(tokenRepresentation, null); + valid = tokenValidation.succeeded(); + + return valid; + } +} diff --git a/extensions/shared-api-configuration/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/shared-api-configuration/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000..faa9ce2 --- /dev/null +++ b/extensions/shared-api-configuration/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,2 @@ +org.upm.inesdata.api.shared.configuration.SharedApiConfigurationExtension +org.upm.inesdata.api.shared.configuration.auth.SharedOauth2JwtAuthenticationExtension \ No newline at end of file diff --git a/extensions/vocabulary-api/src/main/java/org/upm/inesdata/vocabulary/controller/VocabularyApi.java b/extensions/vocabulary-api/src/main/java/org/upm/inesdata/vocabulary/controller/VocabularyApi.java index e96d394..8d23914 100644 --- a/extensions/vocabulary-api/src/main/java/org/upm/inesdata/vocabulary/controller/VocabularyApi.java +++ b/extensions/vocabulary-api/src/main/java/org/upm/inesdata/vocabulary/controller/VocabularyApi.java @@ -126,6 +126,7 @@ record VocabularyOutputSchema( { "@id": "vocabularyId", "name": "vocabulary name", + "connectorId": "connector-c1", "jsonSchema": "{ \\"title\\": \\"vocabulary\\", \\"type\\": \\"object\\", \\"properties\\": { \\"name\\": { \\"type\\": \\"string\\", \\"title\\": \\"Name\\" }, \\"dct:keyword\\": { \\"type\\": \\"array\\", \\"title\\": \\"Keywords\\", \\"items\\": { \\"type\\": \\"string\\" } } }, \\"required\\": [ \\"name\\" ], \\"@context\\": { \\"dct\\": \\"http:\\/\\/purl.org\\/dc\\/terms\\/\" } }", "category": "dataset" } diff --git a/extensions/vocabulary-api/src/main/java/org/upm/inesdata/vocabulary/service/VocabularyServiceImpl.java b/extensions/vocabulary-api/src/main/java/org/upm/inesdata/vocabulary/service/VocabularyServiceImpl.java index 9ed1d0b..3e50196 100644 --- a/extensions/vocabulary-api/src/main/java/org/upm/inesdata/vocabulary/service/VocabularyServiceImpl.java +++ b/extensions/vocabulary-api/src/main/java/org/upm/inesdata/vocabulary/service/VocabularyServiceImpl.java @@ -13,8 +13,8 @@ */ public class VocabularyServiceImpl implements VocabularyService { - private final VocabularyIndex index; - private final TransactionContext transactionContext; + protected final VocabularyIndex index; + protected final TransactionContext transactionContext; /** * Constructor diff --git a/extensions/vocabulary-api/src/main/java/org/upm/inesdata/vocabulary/storage/InMemoryVocabularyIndex.java b/extensions/vocabulary-api/src/main/java/org/upm/inesdata/vocabulary/storage/InMemoryVocabularyIndex.java index d961d79..c063863 100644 --- a/extensions/vocabulary-api/src/main/java/org/upm/inesdata/vocabulary/storage/InMemoryVocabularyIndex.java +++ b/extensions/vocabulary-api/src/main/java/org/upm/inesdata/vocabulary/storage/InMemoryVocabularyIndex.java @@ -97,10 +97,33 @@ public StoreResult updateVocabulary(Vocabulary vocabulary) { } } + @Override + public Stream searchVocabulariesByConnector(String connectorId) { + lock.readLock().lock(); + try { + return cache.values().stream() + .filter(vocabulary -> vocabulary.getConnectorId().equals(connectorId)); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public StoreResult deleteByConnectorId(String connectorId) { + lock.writeLock().lock(); + try { + delete(connectorId); + } finally { + lock.writeLock().unlock(); + } + + return StoreResult.success(); + } + /** - * Remove a vocabulary from cache based on its id + * Remove a vocabulary from cache based on a key */ - private Vocabulary delete(String vocabularyId) { - return cache.remove(vocabularyId); + private Vocabulary delete(String key) { + return cache.remove(key); } } diff --git a/extensions/vocabulary-api/src/main/java/org/upm/inesdata/vocabulary/transformer/JsonObjectFromVocabularyTransformer.java b/extensions/vocabulary-api/src/main/java/org/upm/inesdata/vocabulary/transformer/JsonObjectFromVocabularyTransformer.java index 811cec0..0feafec 100644 --- a/extensions/vocabulary-api/src/main/java/org/upm/inesdata/vocabulary/transformer/JsonObjectFromVocabularyTransformer.java +++ b/extensions/vocabulary-api/src/main/java/org/upm/inesdata/vocabulary/transformer/JsonObjectFromVocabularyTransformer.java @@ -13,6 +13,7 @@ import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; import static org.upm.inesdata.spi.vocabulary.domain.Vocabulary.EDC_VOCABULARY_TYPE; import static org.upm.inesdata.spi.vocabulary.domain.Vocabulary.PROPERTY_CATEGORY; +import static org.upm.inesdata.spi.vocabulary.domain.Vocabulary.PROPERTY_CONNECTOR_ID; import static org.upm.inesdata.spi.vocabulary.domain.Vocabulary.PROPERTY_JSON_SCHEMA; import static org.upm.inesdata.spi.vocabulary.domain.Vocabulary.PROPERTY_NAME; @@ -39,6 +40,7 @@ public JsonObjectFromVocabularyTransformer(JsonBuilderFactory jsonFactory, Objec .add(ID, vocabulary.getId()) .add(TYPE, EDC_VOCABULARY_TYPE) .add(PROPERTY_NAME, vocabulary.getName()) + .add(PROPERTY_CONNECTOR_ID, vocabulary.getConnectorId()) .add(PROPERTY_JSON_SCHEMA, vocabulary.getJsonSchema()) .add(PROPERTY_CATEGORY, vocabulary.getCategory()); diff --git a/extensions/vocabulary-api/src/main/java/org/upm/inesdata/vocabulary/transformer/JsonObjectToVocabularyTransformer.java b/extensions/vocabulary-api/src/main/java/org/upm/inesdata/vocabulary/transformer/JsonObjectToVocabularyTransformer.java index b46d88e..8fb0c6a 100644 --- a/extensions/vocabulary-api/src/main/java/org/upm/inesdata/vocabulary/transformer/JsonObjectToVocabularyTransformer.java +++ b/extensions/vocabulary-api/src/main/java/org/upm/inesdata/vocabulary/transformer/JsonObjectToVocabularyTransformer.java @@ -8,6 +8,7 @@ import org.upm.inesdata.spi.vocabulary.domain.Vocabulary; import static org.upm.inesdata.spi.vocabulary.domain.Vocabulary.PROPERTY_CATEGORY; +import static org.upm.inesdata.spi.vocabulary.domain.Vocabulary.PROPERTY_CONNECTOR_ID; import static org.upm.inesdata.spi.vocabulary.domain.Vocabulary.PROPERTY_JSON_SCHEMA; import static org.upm.inesdata.spi.vocabulary.domain.Vocabulary.PROPERTY_NAME; @@ -30,6 +31,7 @@ public JsonObjectToVocabularyTransformer() { visitProperties(jsonObject, key -> switch (key) { case PROPERTY_NAME -> value -> builder.name(transformString(value, context)); + case PROPERTY_CONNECTOR_ID -> value -> builder.connectorId(transformString(value, context)); case PROPERTY_JSON_SCHEMA -> value -> builder.jsonSchema(transformString(value, context)); case PROPERTY_CATEGORY -> value -> builder.category(transformString(value, context)); default -> doNothing(); diff --git a/extensions/vocabulary-index-sql/README.md b/extensions/vocabulary-index-sql/README.md index 2e8e55c..0aa1547 100644 --- a/extensions/vocabulary-index-sql/README.md +++ b/extensions/vocabulary-index-sql/README.md @@ -8,13 +8,15 @@ Please apply this [schema](docs/schema.sql) to your SQL database. ## Entity Diagram -![ER Diagram](//www.plantuml.com/plantuml/png/SoWkIImgAStDuKhDAyaigLH8JKcEByjFJamgpKaigbIevb9Gq5B8JB5IA2ufoinBLx2n2V2simEBvYNcfiB4mG9PnVbvmSaPgRc9ACB9HQc99QafZYLM2ZdvO35TNQvQBeVKl1IWnG00) +![ER Diagram](//https://www.plantuml.com/plantuml/png/RSun2iCm38NXtQVGNCW5GWZ9MBeKUe2YsY9riIMGbO0flNj3GeT0r-_zmnkAeTgSaoEsQ1Ke-FiY7XzpGgtmTW0dYA65OXfvWgwxNlf-Ko_Cv4tq_7TcpFJplKUZIRGUy5M4R_v96O-jqbg7qLf8ibdJk8yRYCDwzWi0)