diff --git a/Dockerfile b/Dockerfile index d11797a..12f26b7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,17 @@ +### Build using Maven ### +FROM maven:3.8-jdk-11-slim AS maven + +COPY pom.xml /tmp/ +COPY . /tmp/ + +WORKDIR /tmp/ + +## run maven package ## +RUN mvn package -U -DskipTests + + FROM openjdk:11 -ARG JAR_FILE=target/*.jar -COPY ${JAR_FILE} intelcomp-catalogue.jar + +COPY --from=maven /tmp/target/*.jar /intelcomp-catalogue.jar ENTRYPOINT ["java","-jar","/intelcomp-catalogue.jar"] diff --git a/pom.xml b/pom.xml index 3955ac8..343c6fa 100644 --- a/pom.xml +++ b/pom.xml @@ -5,18 +5,19 @@ org.springframework.boot spring-boot-starter-parent - 2.5.6 + 2.5.7 eu.intelcomp catalogue - 1.0.0-SNAPSHOT + 2.0.0-SNAPSHOT jar intelcomp Intelcomp Catalogue Project 11 - 2.3.7 + 2.7.3 + 7.17.14 @@ -46,16 +47,35 @@ + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-configuration-processor + true + + org.springframework.boot spring-boot-starter-oauth2-client - 2.6.1 org.springframework.boot spring-boot-starter-oauth2-resource-server - 2.6.1 + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.session + spring-session-data-redis @@ -78,7 +98,7 @@ gr.athenarc catalogue - 3.0.0 + 5.0.2 @@ -92,9 +112,9 @@ - omtd-snapshots + madgik-snapshots default - https://repo.openminted.eu/content/repositories/snapshots/ + https://repo.madgik.di.uoa.gr/content/repositories/snapshots/ false @@ -103,9 +123,9 @@ - omtd-releases + madgik-releases default - https://repo.openminted.eu/content/repositories/releases + https://repo.madgik.di.uoa.gr/content/repositories/releases true @@ -165,14 +185,46 @@ + + cz.habarta.typescript-generator + typescript-generator-maven-plugin + 2.16.538 + + + generate + + generate + + process-classes + + + + jackson2 + + eu.intelcomp.xsd2java.** + eu.intelcomp.catalogue.domain.** + + + function(name, simpleName) { return name; } + + target/domain.ts + module + implementationFile + asEnum + asClasses + + src/main/resources true + + **/application.yml + **/resourceTypes/ + - **/application.yml - **/registry.properties + **/application.properties diff --git a/src/main/java/eu/intelcomp/catalogue/IntelcompApplication.java b/src/main/java/eu/intelcomp/catalogue/IntelcompApplication.java index fe815ca..6a4083a 100644 --- a/src/main/java/eu/intelcomp/catalogue/IntelcompApplication.java +++ b/src/main/java/eu/intelcomp/catalogue/IntelcompApplication.java @@ -2,13 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) +@SpringBootApplication public class IntelcompApplication { - public static void main(String[] args) { - SpringApplication.run(IntelcompApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(IntelcompApplication.class, args); + } } diff --git a/src/main/java/eu/intelcomp/catalogue/aspects/SecurityAspect.java b/src/main/java/eu/intelcomp/catalogue/aspects/SecurityAspect.java new file mode 100644 index 0000000..759bc3c --- /dev/null +++ b/src/main/java/eu/intelcomp/catalogue/aspects/SecurityAspect.java @@ -0,0 +1,80 @@ +package eu.intelcomp.catalogue.aspects; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +import java.util.Arrays; + +@Aspect +@Component +public class SecurityAspect { + + private static final Logger logger = LoggerFactory.getLogger(SecurityAspect.class); + + public SecurityAspect() { + } + + @Before(value = "execution(* gr.athenarc.catalogue.controller.GenericItemController.create(String,..)) && args(resourceType)", argNames = "resourceType") + void beforeCreate(String resourceType) { + authorize(resourceType); + } + + @Before(value = "(execution(* gr.athenarc.catalogue.controller.GenericItemController.update(String, String, ..)) ||" + + "execution(* gr.athenarc.catalogue.controller.GenericItemController.delete(String, String, ..))) " + + "&& args(id, resourceType,..)", argNames = "id,resourceType") + void beforeUpdate_Delete(String id, String resourceType) { + authorize(resourceType); + } + + void authorize(String resourceType) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + boolean authorized = false; + if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ADMIN"))) { + return; + } + switch (resourceType) { + case "tool": + authorized = authorizeAiTool(authentication); + break; + case "ai_model": + authorized = authorizeAiModel(authentication); + break; + case "dataset_type": + authorized = authorizeDatasetType(authentication); + break; + case "dataset_instance": + authorized = authorizeDatasetInstance(authentication); + break; + default: + authorized = authentication.getAuthorities().contains(new SimpleGrantedAuthority("ADMIN")); + } + if (!authorized) { + throw new AccessDeniedException("Forbidden"); + } + } + + boolean authorizeDatasetType(Authentication authentication) { + SimpleGrantedAuthority[] authorities = {new SimpleGrantedAuthority("OPERATOR_DATASET-INGESTOR")}; + return Arrays.stream(authorities).anyMatch(authority -> authentication.getAuthorities().contains(authority)); + } + + boolean authorizeDatasetInstance(Authentication authentication) { + return authentication.getAuthorities().contains(new SimpleGrantedAuthority("OPERATOR_DATASET-INGESTOR")); + } + + boolean authorizeAiTool(Authentication authentication) { + return authentication.getAuthorities().contains(new SimpleGrantedAuthority("OPERATOR_DEVELOPER")); + } + + boolean authorizeAiModel(Authentication authentication) { + return authentication.getAuthorities().contains(new SimpleGrantedAuthority("OPERATOR_DEVELOPER")); + } +} diff --git a/src/main/java/eu/intelcomp/catalogue/config/AuthSuccessHandler.java b/src/main/java/eu/intelcomp/catalogue/config/AuthSuccessHandler.java index 815549d..6886150 100644 --- a/src/main/java/eu/intelcomp/catalogue/config/AuthSuccessHandler.java +++ b/src/main/java/eu/intelcomp/catalogue/config/AuthSuccessHandler.java @@ -1,10 +1,10 @@ package eu.intelcomp.catalogue.config; +import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; @@ -14,19 +14,18 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.time.Instant; -import java.util.Date; @Component public class AuthSuccessHandler implements AuthenticationSuccessHandler { private static final Logger logger = LoggerFactory.getLogger(AuthSuccessHandler.class); - private final ApplicationProperties applicationProperties; + private final IntelcompProperties intelcompProperties; + private final ObjectMapper objectMapper = new ObjectMapper(); @Autowired - public AuthSuccessHandler(ApplicationProperties applicationProperties) { - this.applicationProperties = applicationProperties; + public AuthSuccessHandler(IntelcompProperties intelcompProperties) { + this.intelcompProperties = intelcompProperties; } @Override @@ -36,28 +35,13 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - Cookie cookie = new Cookie("AccessToken", ((OidcUser) authentication.getPrincipal()).getIdToken().getTokenValue()); - cookie.setMaxAge(createCookieMaxAge(authentication)); + Cookie cookie = new Cookie("AccessToken", "deprecated"); cookie.setPath("/"); -// cookie.setSecure(true); + logger.debug("Assigning Cookie: {}", objectMapper.writeValueAsString(cookie)); response.addCookie(cookie); - response.sendRedirect(applicationProperties.getLoginRedirect()); - } - - private int createCookieMaxAge(Authentication authentication) { - Integer age = getExp(authentication); - return age != null ? age : 3600; - } - - private Integer getExp(Authentication authentication) { - OidcUser user = ((OidcUser) authentication.getPrincipal()); - if (user.getAttribute("exp") instanceof Instant) { - Instant exp = user.getAttribute("exp"); - int age = (int) (exp.getEpochSecond() - (new Date().getTime() / 1000)); - return age; - } - return null; + logger.debug("Authentication Successful - Redirecting to: {}", intelcompProperties.getLoginRedirect()); + response.sendRedirect(intelcompProperties.getLoginRedirect()); } } diff --git a/src/main/java/eu/intelcomp/catalogue/config/CompleteLogoutSuccessHandler.java b/src/main/java/eu/intelcomp/catalogue/config/CompleteLogoutSuccessHandler.java index b4ee3bb..b27f369 100644 --- a/src/main/java/eu/intelcomp/catalogue/config/CompleteLogoutSuccessHandler.java +++ b/src/main/java/eu/intelcomp/catalogue/config/CompleteLogoutSuccessHandler.java @@ -23,12 +23,12 @@ public class CompleteLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler private final String OPENID_CONFIGURATION = "/.well-known/openid-configuration"; private final String END_SESSION_ENDPOINT = "end_session_endpoint"; - private final ApplicationProperties applicationProperties; + private final IntelcompProperties intelcompProperties; private final RestTemplate restTemplate = new RestTemplate(); @Autowired - public CompleteLogoutSuccessHandler(ApplicationProperties applicationProperties) { - this.applicationProperties = applicationProperties; + public CompleteLogoutSuccessHandler(IntelcompProperties intelcompProperties) { + this.intelcompProperties = intelcompProperties; } @Override @@ -44,9 +44,9 @@ public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse resp String logoutEndpoint = getLogoutEndpoint(url); String logoutUrl; if (logoutEndpoint != null) { - logoutUrl = String.format("%s?redirect_uri=%s", logoutEndpoint, applicationProperties.getLogoutRedirect()); + logoutUrl = String.format("%s?redirect_uri=%s", logoutEndpoint, intelcompProperties.getLogoutRedirect()); } else { - logoutUrl = applicationProperties.getLogoutRedirect(); + logoutUrl = intelcompProperties.getLogoutRedirect(); } response.sendRedirect(logoutUrl); super.onLogoutSuccess(request, response, authentication); diff --git a/src/main/java/eu/intelcomp/catalogue/config/IntelcompConfiguration.java b/src/main/java/eu/intelcomp/catalogue/config/IntelcompConfiguration.java index 838fb42..5b77c3d 100644 --- a/src/main/java/eu/intelcomp/catalogue/config/IntelcompConfiguration.java +++ b/src/main/java/eu/intelcomp/catalogue/config/IntelcompConfiguration.java @@ -1,29 +1,12 @@ package eu.intelcomp.catalogue.config; -import eu.openminted.registry.core.controllers.ResourceSyncController; -import gr.athenarc.catalogue.CatalogueApplication; -import gr.athenarc.catalogue.config.CatalogueLibConfiguration; -import gr.athenarc.catalogue.config.LibConfiguration; -import gr.athenarc.catalogue.config.RegistryCoreConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; @Configuration -@ComponentScan(value = { - "gr.athenarc", - "eu.openminted.registry.core", -}, -excludeFilters = { - @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = CatalogueApplication.class), - @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = LibConfiguration.class), // TODO: remove if lib is fixed - @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = RegistryCoreConfiguration.class), - @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = ResourceSyncController.class) -}) -public class IntelcompConfiguration implements CatalogueLibConfiguration { - - @Override - public String generatedClassesPackageName() { - return "eu.intelcomp.xsd2java"; - } +@EnableConfigurationProperties(IntelcompProperties.class) +@ComponentScan(value = {"eu.openminted.registry.core"}, excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = eu.openminted.registry.core.controllers.GenericController.class)}) +public class IntelcompConfiguration { } diff --git a/src/main/java/eu/intelcomp/catalogue/config/ApplicationProperties.java b/src/main/java/eu/intelcomp/catalogue/config/IntelcompProperties.java similarity index 89% rename from src/main/java/eu/intelcomp/catalogue/config/ApplicationProperties.java rename to src/main/java/eu/intelcomp/catalogue/config/IntelcompProperties.java index 9641a85..6ee1702 100644 --- a/src/main/java/eu/intelcomp/catalogue/config/ApplicationProperties.java +++ b/src/main/java/eu/intelcomp/catalogue/config/IntelcompProperties.java @@ -1,13 +1,11 @@ package eu.intelcomp.catalogue.config; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; import java.util.Set; -@Component @ConfigurationProperties(prefix = "intelcomp") -public class ApplicationProperties { +public class IntelcompProperties { private Set admins; diff --git a/src/main/java/eu/intelcomp/catalogue/config/SecurityConfig.java b/src/main/java/eu/intelcomp/catalogue/config/SecurityConfig.java index ea8a6b3..442d71c 100644 --- a/src/main/java/eu/intelcomp/catalogue/config/SecurityConfig.java +++ b/src/main/java/eu/intelcomp/catalogue/config/SecurityConfig.java @@ -3,20 +3,21 @@ import com.nimbusds.jose.shaded.json.JSONArray; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.core.oidc.OidcIdToken; import org.springframework.security.oauth2.core.oidc.OidcUserInfo; import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; +import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import java.util.HashSet; @@ -25,31 +26,40 @@ @Configuration @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) -public class SecurityConfig extends WebSecurityConfigurerAdapter { +public class SecurityConfig { private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class); private final AuthenticationSuccessHandler authSuccessHandler; private final CompleteLogoutSuccessHandler logoutSuccessHandler; - private final ApplicationProperties applicationProperties; + private final IntelcompProperties intelcompProperties; - @Autowired public SecurityConfig(AuthenticationSuccessHandler authSuccessHandler, CompleteLogoutSuccessHandler logoutSuccessHandler, - ApplicationProperties applicationProperties) { + IntelcompProperties intelcompProperties) { this.authSuccessHandler = authSuccessHandler; this.logoutSuccessHandler = logoutSuccessHandler; - this.applicationProperties = applicationProperties; + this.intelcompProperties = intelcompProperties; } - @Override - protected void configure(HttpSecurity http) throws Exception { + /* + * Needed to save client repository in redis session. + */ + @Bean + public OAuth2AuthorizedClientRepository authorizedClientRepository() { + return new HttpSessionOAuth2AuthorizedClientRepository(); + } + + @Bean + public SecurityFilterChain clientFilterChain(HttpSecurity http) throws Exception { http .authorizeRequests(authorizeRequests -> authorizeRequests - .regexMatchers("/dump/.*", "/restore/", "/resources.*", "/resourceType.*", "/search.*", "/logs.*").hasAnyAuthority("ADMIN") - .antMatchers(HttpMethod.GET, "/forms/**").permitAll() - .antMatchers( "/forms/**").hasAnyAuthority("ADMIN") +// .regexMatchers("/dump/.*", "/restore/", "/resources.*", "/resourceType.*", "/search.*", "/logs.*").hasAnyAuthority("ADMIN") +// .antMatchers(HttpMethod.GET, "/forms/**").permitAll() +// .antMatchers( "/forms/**").hasAnyAuthority("ADMIN") .anyRequest().permitAll()) + .oauth2Client() + .and() .oauth2Login() .successHandler(authSuccessHandler) .and() @@ -60,6 +70,7 @@ protected void configure(HttpSecurity http) throws Exception { .cors() .and() .csrf().disable(); + return http.build(); } @Bean @@ -68,28 +79,30 @@ public GrantedAuthoritiesMapper userAuthoritiesMapper() { Set mappedAuthorities = new HashSet<>(); authorities.forEach(authority -> { - if (OidcUserAuthority.class.isInstance(authority)) { + if (authority instanceof OidcUserAuthority) { OidcUserAuthority oidcUserAuthority = (OidcUserAuthority) authority; OidcIdToken idToken = oidcUserAuthority.getIdToken(); OidcUserInfo userInfo = oidcUserAuthority.getUserInfo(); - JSONArray icRoles = ((JSONArray) oidcUserAuthority.getAttributes().get("ic-roles")); + logger.debug("User attributes: {}", oidcUserAuthority.getAttributes()); + logger.debug("User Token Claims: {}", idToken.getClaims()); + JSONArray icRoles = ((JSONArray) oidcUserAuthority.getIdToken().getClaims().get("ic-roles")); + logger.debug("User attributes.ic-roles: {}", icRoles); if (icRoles != null) { for (int i = 0; i < icRoles.size(); i++) { - mappedAuthorities.add(new SimpleGrantedAuthority(icRoles.get(i).toString().toUpperCase())); } } - if (idToken != null && applicationProperties.getAdmins().contains(idToken.getClaims().get("email"))) { + if (idToken != null && intelcompProperties.getAdmins().contains(idToken.getClaims().get("email"))) { mappedAuthorities.add(new SimpleGrantedAuthority("ADMIN")); - } else if (userInfo != null && applicationProperties.getAdmins().contains(userInfo.getEmail())) { + } else if (userInfo != null && intelcompProperties.getAdmins().contains(userInfo.getEmail())) { mappedAuthorities.add(new SimpleGrantedAuthority("ADMIN")); } else { if (((OidcUserAuthority) authority).getAttributes() != null && ((OidcUserAuthority) authority).getAttributes().containsKey("email") - && (applicationProperties.getAdmins().contains(((OidcUserAuthority) authority).getAttributes().get("email")))) { + && (intelcompProperties.getAdmins().contains(((OidcUserAuthority) authority).getAttributes().get("email")))) { mappedAuthorities.add(new SimpleGrantedAuthority("ADMIN")); } } @@ -97,20 +110,22 @@ public GrantedAuthoritiesMapper userAuthoritiesMapper() { // Map the claims found in idToken and/or userInfo // to one or more GrantedAuthority's and add it to mappedAuthorities - } else if (OAuth2UserAuthority.class.isInstance(authority)) { - OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority) authority; + } else if (authority instanceof OAuth2UserAuthority) { - Map userAttributes = oauth2UserAuthority.getAttributes(); + OAuth2User oauth2User = (OAuth2User) authority; + Map userAttributes = oauth2User.getAttributes(); - if (userAttributes != null && applicationProperties.getAdmins().contains(userAttributes.get("email"))) { + if (userAttributes != null && intelcompProperties.getAdmins().contains(userAttributes.get("email"))) { mappedAuthorities.add(new SimpleGrantedAuthority("ADMIN")); } + + mappedAuthorities.addAll(oauth2User.getAuthorities()); // Map the attributes found in userAttributes // to one or more GrantedAuthority's and add it to mappedAuthorities } }); - + logger.debug("Granted Authorities: {}", mappedAuthorities); return mappedAuthorities; }; } diff --git a/src/main/java/eu/intelcomp/catalogue/config/SpringFoxConfiguration.java b/src/main/java/eu/intelcomp/catalogue/config/SpringFoxConfiguration.java deleted file mode 100644 index 03b0a5d..0000000 --- a/src/main/java/eu/intelcomp/catalogue/config/SpringFoxConfiguration.java +++ /dev/null @@ -1,9 +0,0 @@ -package eu.intelcomp.catalogue.config; - -import gr.athenarc.catalogue.config.SpringFoxConfig; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class SpringFoxConfiguration extends SpringFoxConfig { - -} diff --git a/src/main/java/eu/intelcomp/catalogue/config/logging/LogUser.java b/src/main/java/eu/intelcomp/catalogue/config/logging/LogUser.java index 94c97c8..305352a 100644 --- a/src/main/java/eu/intelcomp/catalogue/config/logging/LogUser.java +++ b/src/main/java/eu/intelcomp/catalogue/config/logging/LogUser.java @@ -1,6 +1,5 @@ package eu.intelcomp.catalogue.config.logging; -import eu.intelcomp.catalogue.domain.User; import gr.athenarc.catalogue.config.logging.AbstractLogContextFilter; import org.slf4j.spi.MDCAdapter; import org.springframework.security.authentication.InsufficientAuthenticationException; @@ -13,17 +12,19 @@ @Component public class LogUser extends AbstractLogContextFilter { + public static final String TRANSACTION_ID = "transaction_id"; + public static final String USER_INFO = "user_info"; + @Override public void editMDC(MDCAdapter mdc) { String transactionId = UUID.randomUUID().toString(); - mdc.put("transaction_id", transactionId); + mdc.put(TRANSACTION_ID, transactionId); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null) { try { - User user = User.of(authentication); - mdc.put("user_info", user.toString()); + mdc.put(USER_INFO, authentication.toString()); } catch (InsufficientAuthenticationException e) { - mdc.put("user_info", authentication.toString()); + mdc.put(USER_INFO, authentication.toString()); } } } diff --git a/src/main/java/eu/intelcomp/catalogue/controller/DatasetController.java b/src/main/java/eu/intelcomp/catalogue/controller/DatasetController.java index 2e92468..7e974ae 100644 --- a/src/main/java/eu/intelcomp/catalogue/controller/DatasetController.java +++ b/src/main/java/eu/intelcomp/catalogue/controller/DatasetController.java @@ -1,15 +1,12 @@ package eu.intelcomp.catalogue.controller; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import eu.intelcomp.catalogue.domain.ModelAnswer; +import eu.openminted.registry.core.domain.Resource; import eu.openminted.registry.core.service.SearchService; import gr.athenarc.catalogue.service.GenericItemService; -import org.json.simple.JSONObject; -import org.json.simple.JSONValue; 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.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -21,33 +18,26 @@ public class DatasetController { private static final Logger logger = LoggerFactory.getLogger(DatasetController.class); - private final ObjectMapper objectMapper = new ObjectMapper(); private final GenericItemService genericItemService; - @Autowired public DatasetController(GenericItemService genericItemService) { this.genericItemService = genericItemService; } @GetMapping("{id}") - public ModelAnswer get(@PathVariable("id") String id) { - ModelAnswer dataset = null; - try { - String json = objectMapper.writeValueAsString(genericItemService.get("dataset_type", id)); - Object answer = JSONValue.parse(json); - dataset = new ModelAnswer((JSONObject) answer); - } catch (JsonProcessingException e) { - logger.error(e.getMessage(), e); - } - - return dataset; + public ResponseEntity get(@PathVariable("id") String id) { + return ResponseEntity.ok(genericItemService.get("dataset_type", id)); } // TODO: helper method, remove when job filter is implemented correctly from @CITE @GetMapping("instances/{type}/{version}/internalid") - public String getCoreId(@PathVariable("type") String type, @PathVariable("version") String version) { + public ResponseEntity getCoreId(@PathVariable("type") String type, @PathVariable("version") String version) { SearchService.KeyValue typeKeyValue = new SearchService.KeyValue("type", type); SearchService.KeyValue versionKeyValue = new SearchService.KeyValue("version", version); - return genericItemService.searchResource("dataset_instance", typeKeyValue, versionKeyValue).getId(); + Resource resource = genericItemService.searchResource("dataset_instance", typeKeyValue, versionKeyValue); + if (resource == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(resource.getId(), HttpStatus.OK); } } diff --git a/src/main/java/eu/intelcomp/catalogue/controller/IntelcompGenericExceptionController.java b/src/main/java/eu/intelcomp/catalogue/controller/IntelcompGenericExceptionController.java deleted file mode 100644 index d966eb4..0000000 --- a/src/main/java/eu/intelcomp/catalogue/controller/IntelcompGenericExceptionController.java +++ /dev/null @@ -1,44 +0,0 @@ -package eu.intelcomp.catalogue.controller; - -import gr.athenarc.catalogue.RequestUtils; -import gr.athenarc.catalogue.config.logging.LogTransactionsFilter; -import gr.athenarc.catalogue.controller.GenericExceptionController; -import gr.athenarc.catalogue.exception.ResourceException; -import gr.athenarc.catalogue.exception.ServerError; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authentication.InsufficientAuthenticationException; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.client.HttpClientErrorException; - -import javax.servlet.http.HttpServletRequest; - -@ControllerAdvice -public class IntelcompGenericExceptionController extends GenericExceptionController { - - private static final Logger logger = LoggerFactory.getLogger(IntelcompGenericExceptionController.class); - - @ExceptionHandler(Exception.class) - ResponseEntity handleException(HttpServletRequest req, Exception ex) { - logger.info(ex.getMessage(), ex); - HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; - if (ex instanceof ResourceException) { - status = ((ResourceException) ex).getStatus(); - } else if (ex instanceof HttpClientErrorException) { - status = ((HttpClientErrorException) ex).getStatusCode(); - } else if (ex instanceof AccessDeniedException) { - status = HttpStatus.FORBIDDEN; - } else if (ex instanceof InsufficientAuthenticationException) { - status = HttpStatus.UNAUTHORIZED; - } - return ResponseEntity - .status(status) - .body(new ServerError(status, req, ex)); - } - - -} diff --git a/src/main/java/eu/intelcomp/catalogue/controller/JobController.java b/src/main/java/eu/intelcomp/catalogue/controller/JobController.java index 1ae9f2a..93d3a60 100644 --- a/src/main/java/eu/intelcomp/catalogue/controller/JobController.java +++ b/src/main/java/eu/intelcomp/catalogue/controller/JobController.java @@ -1,38 +1,120 @@ package eu.intelcomp.catalogue.controller; -import eu.intelcomp.catalogue.domain.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.intelcomp.catalogue.domain.Job; +import eu.intelcomp.catalogue.domain.JobFilters; +import eu.intelcomp.catalogue.domain.JobInfo; +import eu.intelcomp.catalogue.domain.User; +import eu.intelcomp.catalogue.service.JobProperties; import eu.intelcomp.catalogue.service.JobService; -import org.springframework.beans.factory.annotation.Autowired; +import gr.athenarc.catalogue.exception.ResourceException; +import io.swagger.v3.oas.annotations.Parameter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.InputStreamResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; -import springfox.documentation.annotations.ApiIgnore; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Flux; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; import java.util.List; +import java.util.UUID; @RestController @RequestMapping("jobs") public class JobController { + private static final Logger logger = LoggerFactory.getLogger(JobController.class); + private static final ObjectMapper mapper = new ObjectMapper(); private final JobService jobService; + private final JobProperties jobProperties; - @Autowired - public JobController(JobService jobService) { + public JobController(JobService jobService, JobProperties jobProperties) { this.jobService = jobService; + this.jobProperties = jobProperties; } @PostMapping("execute") - @PreAuthorize("hasAuthority('OPERATOR_DATA-PROCESSOR')") - public ResponseEntity add(@RequestBody Job job, @ApiIgnore Authentication authentication) { + @PreAuthorize("(hasAuthority('OPERATOR-WORKFLOW_PROCESSOR') && @jobController.jobIsWorkflow(#job)) || (hasAuthority('OPERATOR_DATA-PROCESSOR') && !@jobController.jobIsWorkflow(#job))") + public ResponseEntity add(@RequestBody Job job, @Parameter(hidden = true) Authentication authentication) { return new ResponseEntity<>(jobService.add(job, authentication), HttpStatus.OK); } + @PostMapping(value = "execute/custom", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) + @PreAuthorize("hasAuthority('ADMIN') or (hasAuthority('OPERATOR-WORKFLOW_PROCESSOR') && @jobController.jobIsWorkflow(#jobString))") + public ResponseEntity addJobWithFile(@RequestParam(name = "job") String jobString, @RequestPart MultipartFile file, @Parameter(hidden = true) Authentication authentication) throws IOException { + // save + Job job = mapper.readValue(jobString, Job.class); + String location = String.format("%s/%s/%s/", + jobProperties.getData().getDirectories().getBase(), + jobProperties.getData().getDirectories().getInputRelativePath(), + UUID.randomUUID() + ); + Files.createDirectories(Paths.get(location)); + String filepath = location + file.getOriginalFilename(); + File f = new File(filepath); + Job.JobArgument fileArgument = new Job.JobArgument(); + file.transferTo(f); + fileArgument.setName("file"); + fileArgument.setValue(List.of(filepath)); + job.getJobArguments().add(fileArgument); + JobInfo info = jobService.add(job, authentication); + return new ResponseEntity<>(info, HttpStatus.OK); + } + + @GetMapping(path = "/{jobId}/output/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + public ResponseEntity download(@PathVariable("jobId") String id, + @RequestParam String process, + @RequestParam String filename, + @Parameter(hidden = true) Authentication authentication) { + JobInfo info = get(id, null, authentication).getBody(); + ResponseEntity responseEntity = ResponseEntity.notFound().build(); + if (info != null) { + try { + File file = new File(String.format("%s/%s/%s/%s/%s", + jobProperties.getData().getDirectories().getBase(), + process, + id, + jobProperties.getData().getDirectories().getOutputRelativePath(), + filename) + ); + Path path = Paths.get(file.getAbsolutePath()); + + Resource resource = new InputStreamResource(new FileInputStream(path.toFile())); + HttpHeaders headers = new HttpHeaders(); + headers.add(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=%s_output.csv", id)); + responseEntity = ResponseEntity.ok() + .headers(headers) + .contentLength(file.length()) + .body(resource); + } catch (Exception e) { + logger.warn("Could not find file : {}", e.getMessage()); + } + } + return responseEntity; + } + @GetMapping("/{jobId}") public ResponseEntity get(@PathVariable("jobId") String id, @RequestParam(value = "user", required = false) String userId, - @ApiIgnore Authentication authentication) { + @Parameter(hidden = true) Authentication authentication) { if (userId == null || "".equals(userId)) { userId = User.of(authentication).getSub(); } @@ -40,18 +122,35 @@ public ResponseEntity get(@PathVariable("jobId") String id, } @PostMapping - public ResponseEntity> browse(@RequestBody JobFilters jobFilters, @ApiIgnore Authentication authentication) { + public ResponseEntity> browse(@RequestBody JobFilters jobFilters, @Parameter(hidden = true) Authentication authentication) { return new ResponseEntity<>(jobService.browse(jobFilters, authentication), HttpStatus.OK); } @DeleteMapping("/{jobId}") public ResponseEntity delete(@PathVariable("jobId") String id, @RequestParam(value = "user", required = false) String userId, - @ApiIgnore Authentication authentication) { + @Parameter(hidden = true) Authentication authentication) { if (userId == null || "".equals(userId)) { userId = User.of(authentication).getSub(); } jobService.delete(id, userId); return new ResponseEntity<>(HttpStatus.OK); } + + public static boolean jobIsWorkflow(Object obj) throws JsonProcessingException { + Job job; + if (obj instanceof String) { + job = mapper.readValue((String) obj, Job.class); + } else { + job = (Job) obj; + } + if (job == null || job.getServiceArguments() == null || job.getJobArguments() == null) { + throw new ResourceException("Incomplete Job information", HttpStatus.BAD_REQUEST); + } + if (job.getServiceArguments().getProcessId() == null || "".equals(job.getServiceArguments().getProcessId())) { + throw new ResourceException("'processId' cannot be empty", HttpStatus.BAD_REQUEST); + } + + return job.getServiceArguments().getProcessId().endsWith("-workflow"); + } } diff --git a/src/main/java/eu/intelcomp/catalogue/controller/UserController.java b/src/main/java/eu/intelcomp/catalogue/controller/UserController.java index a81d2be..231f68c 100644 --- a/src/main/java/eu/intelcomp/catalogue/controller/UserController.java +++ b/src/main/java/eu/intelcomp/catalogue/controller/UserController.java @@ -1,26 +1,57 @@ package eu.intelcomp.catalogue.controller; import eu.intelcomp.catalogue.domain.User; -import org.springframework.beans.factory.annotation.Autowired; +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.Parameter; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import springfox.documentation.annotations.ApiIgnore; @RestController -@RequestMapping("user") +@RequestMapping(value = "user", produces = MediaType.APPLICATION_JSON_VALUE) public class UserController { - @Autowired public UserController() { - } @GetMapping("info") - public ResponseEntity getInfo(@ApiIgnore Authentication authentication) { + public ResponseEntity getInfo(@Parameter(hidden = true) Authentication authentication) { return new ResponseEntity<>(User.of(authentication), HttpStatus.OK); } + + @PreAuthorize("isFullyAuthenticated()") + @GetMapping("token") + public OAuth2AccessToken getAccessToken(@RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient) { + OAuth2AccessToken accessToken = authorizedClient.getAccessToken(); + return accessToken; + } + + @Hidden + @GetMapping("auth/oidc-principal") + public OidcUser getOidcUserPrincipal(@AuthenticationPrincipal OidcUser principal) { + return principal; + } + + @Hidden + @GetMapping("auth/oauth2-principal") + public OAuth2User getOAuth2UserPrincipal(@AuthenticationPrincipal OAuth2User principal) { + return principal; + } + + @Hidden + @GetMapping("auth") + public Authentication getAuth(Authentication auth) { + return auth; + } } diff --git a/src/main/java/eu/intelcomp/catalogue/controller/WorkflowDatasetController.java b/src/main/java/eu/intelcomp/catalogue/controller/WorkflowDatasetController.java new file mode 100644 index 0000000..546ff72 --- /dev/null +++ b/src/main/java/eu/intelcomp/catalogue/controller/WorkflowDatasetController.java @@ -0,0 +1,129 @@ +package eu.intelcomp.catalogue.controller; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.intelcomp.catalogue.domain.Dataset; +import eu.intelcomp.catalogue.domain.User; +import eu.intelcomp.catalogue.service.DatasetService; +import eu.intelcomp.catalogue.service.FileService; +import eu.intelcomp.catalogue.service.JobProperties; +import eu.openminted.registry.core.domain.FacetFilter; +import eu.openminted.registry.core.domain.Paging; +import gr.athenarc.catalogue.annotations.Browse; +import io.swagger.v3.oas.annotations.Parameter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.util.Map; +import java.util.UUID; + +import static gr.athenarc.catalogue.utils.PagingUtils.createFacetFilter; + +@RestController +@RequestMapping("workflow-datasets") +public class WorkflowDatasetController { + + private static final Logger logger = LoggerFactory.getLogger(WorkflowDatasetController.class); + + private final DatasetService datasetService; + private final FileService fileService; + private final JobProperties jobProperties; + private final ObjectMapper mapper = new ObjectMapper(); + + public WorkflowDatasetController(DatasetService datasetService, + FileService fileService, + JobProperties jobProperties) { + this.datasetService = datasetService; + this.fileService = fileService; + this.jobProperties = jobProperties; + } + + @GetMapping("{datasetId}") + @PreAuthorize("isAuthenticated() and not anonymous") + public ResponseEntity get(@PathVariable("datasetId") String id) { + return ResponseEntity.ok(datasetService.get(id)); + } + + @GetMapping("{datasetId}/file") + @PreAuthorize("isAuthenticated() and not anonymous") + public ResponseEntity getFile(@PathVariable("datasetId") String id) { + Dataset dataset = datasetService.get(id); + File file = new File(dataset.getFileLocation()); + Resource resource = fileService.get(dataset.getFileLocation()); + + HttpHeaders headers = new HttpHeaders(); + headers.add(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=%s", file.getName())); + + return ResponseEntity.ok() + .headers(headers) + .contentLength(file.length()) + .body(resource); + } + + @Browse + @GetMapping + @PreAuthorize("isAuthenticated()") + public ResponseEntity> browse(@Parameter(hidden = true) @RequestParam Map allRequestParams) { + FacetFilter ff = createFacetFilter(allRequestParams); + ff.addFilter("owner", User.getId(SecurityContextHolder.getContext().getAuthentication())); + return ResponseEntity.ok(datasetService.browse(ff)); + } + + @PostMapping() + @PreAuthorize("isAuthenticated() and not anonymous") + public ResponseEntity add(@RequestParam(name = "dataset") String dataset, @RequestPart MultipartFile file, @Parameter(hidden = true) Authentication authentication) throws JsonProcessingException { + // save + Dataset datasetObj = mapper.readValue(dataset, Dataset.class); + String id = UUID.randomUUID().toString(); + logger.info("created dataset id: {}", id); + datasetObj.setFileLocation(createFileLocation(id, file)); + Dataset saved = datasetService.add(id, datasetObj); + fileService.save(file, saved.getFileLocation()); + return ResponseEntity.ok(saved); + } + + @PutMapping("{datasetId}") + @PreAuthorize("isAuthenticated()") + public ResponseEntity update(@PathVariable("datasetId") String id, @RequestParam(name = "dataset") String dataset, @RequestPart MultipartFile file) throws JsonProcessingException { + Dataset datasetObj = mapper.readValue(dataset, Dataset.class); + if (file != null) { + fileService.delete(datasetObj.getFileLocation()); + datasetObj.setFileLocation(createFileLocation(id, file)); + fileService.save(file, datasetObj.getFileLocation()); + } + return ResponseEntity.ok(datasetService.update(id, datasetObj)); + } + + @DeleteMapping("{datasetId}") + @PreAuthorize("isAuthenticated()") + public ResponseEntity delete(@PathVariable("datasetId") String id) { + Dataset dataset = datasetService.get(id); + datasetService.delete(id); + fileService.delete(dataset.getFileLocation()); + + return ResponseEntity.ok().build(); + } + + private String createFileLocation(String datasetId, MultipartFile file) { + String location = null; + if (file != null && datasetId != null) { + location = String.format("%s/%s/%s/%s", + jobProperties.getData().getDirectories().getBase(), + jobProperties.getData().getDirectories().getInputRelativePath(), + datasetId, + file.getOriginalFilename() + ); + } + return location; + } + +} diff --git a/src/main/java/eu/intelcomp/catalogue/domain/ChapterAnswer.java b/src/main/java/eu/intelcomp/catalogue/domain/ChapterAnswer.java deleted file mode 100644 index 2774341..0000000 --- a/src/main/java/eu/intelcomp/catalogue/domain/ChapterAnswer.java +++ /dev/null @@ -1,48 +0,0 @@ -package eu.intelcomp.catalogue.domain; - -import org.json.simple.JSONObject; - -public class ChapterAnswer { - - private String chapterId; - private JSONObject answer; - - public ChapterAnswer() { - answer = new JSONObject(); - } - - public ChapterAnswer(String id, JSONObject answer) { - this.answer = answer; - } - - public ChapterAnswer(String chapterAnswerId, String chapterId) { - this.answer = new JSONObject(); - this.setId(chapterAnswerId); - this.chapterId = chapterId; - } - - public String getId() { - return answer.get("id").toString(); - } - - public void setId(String id) { - this.answer.put("id", id); - } - - public String getChapterId() { - return chapterId; - } - - public void setChapterId(String chapterId) { - this.chapterId = chapterId; - } - - public JSONObject getAnswer() { - return answer; - } - - public void setAnswer(JSONObject answer) { - this.answer = answer; - } -} - diff --git a/src/main/java/eu/intelcomp/catalogue/domain/Dataset.java b/src/main/java/eu/intelcomp/catalogue/domain/Dataset.java new file mode 100644 index 0000000..a5c7dee --- /dev/null +++ b/src/main/java/eu/intelcomp/catalogue/domain/Dataset.java @@ -0,0 +1,102 @@ +package eu.intelcomp.catalogue.domain; + +import java.util.Date; +import java.util.List; + + +public class Dataset { + + private String id; + private String name; + private String description; + private List usages; + private String owner; + private String fileLocation; + private Date creationDate; + private String createdBy; + private Date modificationDate; + private String modifiedBy; + + public Dataset() { + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getUsages() { + return usages; + } + + public void setUsages(List usages) { + this.usages = usages; + } + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + + public String getFileLocation() { + return fileLocation; + } + + public void setFileLocation(String fileLocation) { + this.fileLocation = fileLocation; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public Date getModificationDate() { + return modificationDate; + } + + public void setModificationDate(Date modificationDate) { + this.modificationDate = modificationDate; + } + + public String getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(String modifiedBy) { + this.modifiedBy = modifiedBy; + } +} diff --git a/src/main/java/eu/intelcomp/catalogue/domain/Job.java b/src/main/java/eu/intelcomp/catalogue/domain/Job.java index 71dd23e..b6193b8 100644 --- a/src/main/java/eu/intelcomp/catalogue/domain/Job.java +++ b/src/main/java/eu/intelcomp/catalogue/domain/Job.java @@ -32,9 +32,9 @@ public void setCallerAttributes(String callerAttributes) { this.callerAttributes = callerAttributes; } - public void addJobArgument(String name, String value) { - this.jobArguments.add(new JobArgument(name, value)); - } +// public void addJobArgument(String name, String value) { +// this.jobArguments.add(new JobArgument(name, value)); +// } // public void addJobArgument(String name, String value) { // this.jobArguments.add(new JobArgument(name, Collections.singletonList(value))); @@ -52,7 +52,7 @@ public void setJobArguments(List jobArguments) { this.jobArguments = jobArguments; } - public class ServiceArguments { + public static class ServiceArguments { private String processId; private String user; private String infraId = "k8s"; @@ -98,8 +98,7 @@ public void setInfraId(String infraId) { public static class JobArgument { private String name; - private String value; -// private List value = new ArrayList<>(); + private Object value; public JobArgument() { } @@ -109,10 +108,10 @@ public JobArgument(String name, String value) { this.value = value; } -// public JobArgument(String name, List value) { -// this.name = name; -// this.value = value; -// } + public JobArgument(String name, List value) { + this.name = name; + this.value = value; + } public String getName() { return name; @@ -122,20 +121,12 @@ public void setName(String name) { this.name = name; } - public String getValue() { + public Object getValue() { return value; } - public void setValue(String value) { + public void setValue(Object value) { this.value = value; } - -// public List getValue() { -// return value; -// } -// -// public void setValue(List value) { -// this.value = value; -// } } } diff --git a/src/main/java/eu/intelcomp/catalogue/domain/JobFilters.java b/src/main/java/eu/intelcomp/catalogue/domain/JobFilters.java index 4694632..afcb0ed 100644 --- a/src/main/java/eu/intelcomp/catalogue/domain/JobFilters.java +++ b/src/main/java/eu/intelcomp/catalogue/domain/JobFilters.java @@ -2,11 +2,13 @@ import java.util.Date; import java.util.List; +import java.util.Map; public class JobFilters { private String user; private List ids; + private List processes; private List statuses; // private List statuses; #["QUEUED", "FINISHED", "RUNNING"] private Date createdAfter; @@ -35,6 +37,14 @@ public void setUser(String user) { this.user = user; } + public List getProcesses() { + return processes; + } + + public void setProcesses(List processes) { + this.processes = processes; + } + public List getIds() { return ids; } diff --git a/src/main/java/eu/intelcomp/catalogue/domain/ModelAnswer.java b/src/main/java/eu/intelcomp/catalogue/domain/ModelAnswer.java deleted file mode 100644 index 1f977f4..0000000 --- a/src/main/java/eu/intelcomp/catalogue/domain/ModelAnswer.java +++ /dev/null @@ -1,48 +0,0 @@ -package eu.intelcomp.catalogue.domain; - -import org.json.simple.JSONObject; - -import java.util.Map; -import java.util.TreeMap; - -public class ModelAnswer { - - private String id; - private String modelId; - private Map chapterAnswers; - - public ModelAnswer() { - this.chapterAnswers = new TreeMap<>(); - } - - public ModelAnswer(JSONObject answer) { - this.chapterAnswers = new TreeMap<>(); - String answerId = answer.get("id").toString(); - this.id = answerId; - this.chapterAnswers.put(answerId, new ChapterAnswer(answerId, answer)); - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getModelId() { - return modelId; - } - - public void setModelId(String modelId) { - this.modelId = modelId; - } - - public Map getChapterAnswers() { - return chapterAnswers; - } - - public void setChapterAnswers(Map chapterAnswers) { - this.chapterAnswers = chapterAnswers; - } -} diff --git a/src/main/java/eu/intelcomp/catalogue/service/DatasetService.java b/src/main/java/eu/intelcomp/catalogue/service/DatasetService.java new file mode 100644 index 0000000..c98cdd5 --- /dev/null +++ b/src/main/java/eu/intelcomp/catalogue/service/DatasetService.java @@ -0,0 +1,19 @@ +package eu.intelcomp.catalogue.service; + +import eu.intelcomp.catalogue.domain.Dataset; +import eu.openminted.registry.core.domain.FacetFilter; +import eu.openminted.registry.core.domain.Paging; + +public interface DatasetService { + + Dataset get(String id); + + Paging browse(FacetFilter filter); + + Dataset add(String id, Dataset dataset); + + Dataset update(String id, Dataset dataset); + + void delete(String id); + +} diff --git a/src/main/java/eu/intelcomp/catalogue/service/ExternalApiJobService.java b/src/main/java/eu/intelcomp/catalogue/service/ExternalApiJobService.java index 73d2b49..5a37320 100644 --- a/src/main/java/eu/intelcomp/catalogue/service/ExternalApiJobService.java +++ b/src/main/java/eu/intelcomp/catalogue/service/ExternalApiJobService.java @@ -1,18 +1,19 @@ package eu.intelcomp.catalogue.service; -import eu.intelcomp.catalogue.domain.*; +import eu.intelcomp.catalogue.domain.Job; +import eu.intelcomp.catalogue.domain.JobFilters; +import eu.intelcomp.catalogue.domain.JobInfo; +import eu.intelcomp.catalogue.domain.User; import gr.athenarc.catalogue.exception.ResourceException; -import gr.athenarc.catalogue.exception.ResourceNotFoundException; -import org.slf4j.LoggerFactory; -import org.slf4j.Logger;; import org.json.simple.JSONObject; -import org.springframework.beans.factory.annotation.Autowired; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.*; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.client.HttpClientErrorException; +import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; import java.util.List; @@ -30,7 +31,6 @@ public class ExternalApiJobService implements JobService { private final ExternalJobServiceProperties properties; RestTemplate restTemplate = new RestTemplate(); - @Autowired public ExternalApiJobService(ExternalJobServiceProperties properties) { this.properties = properties; } @@ -40,10 +40,12 @@ public JobInfo add(Job job, Authentication authentication) { if (job == null || job.getServiceArguments() == null || job.getJobArguments() == null) { throw new ResourceException("Incomplete Job information", HttpStatus.BAD_REQUEST); } - if (job.getServiceArguments().getProcessId() == null || "".equals(job.getServiceArguments().getProcessId())) { + if (!StringUtils.hasText(job.getServiceArguments().getProcessId())) { throw new ResourceException("'processId' cannot be empty", HttpStatus.BAD_REQUEST); } - job.getServiceArguments().setInfraId("k8s"); + if (!StringUtils.hasText(job.getServiceArguments().getInfraId())) { + job.getServiceArguments().setInfraId("k8s"); + } job.getServiceArguments().setUser(User.of(authentication).getSub()); HttpEntity request = new HttpEntity<>(job, createHeaders()); String url = String.join("/", properties.getApiUrl(), EXECUTE_JOB); diff --git a/src/main/java/eu/intelcomp/catalogue/service/FileService.java b/src/main/java/eu/intelcomp/catalogue/service/FileService.java new file mode 100644 index 0000000..01e9850 --- /dev/null +++ b/src/main/java/eu/intelcomp/catalogue/service/FileService.java @@ -0,0 +1,14 @@ +package eu.intelcomp.catalogue.service; + +import org.springframework.core.io.Resource; +import org.springframework.web.multipart.MultipartFile; + +public interface FileService { + + Resource get(String filepath); + + void save(MultipartFile file, String filepath); + + void delete(String filepath); + +} diff --git a/src/main/java/eu/intelcomp/catalogue/service/FileSystemFileService.java b/src/main/java/eu/intelcomp/catalogue/service/FileSystemFileService.java new file mode 100644 index 0000000..18a1ae1 --- /dev/null +++ b/src/main/java/eu/intelcomp/catalogue/service/FileSystemFileService.java @@ -0,0 +1,56 @@ +package eu.intelcomp.catalogue.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.InputStreamResource; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Objects; + +@Service +public class FileSystemFileService implements FileService { + + private static final Logger logger = LoggerFactory.getLogger(FileSystemFileService.class); + + @Override + public Resource get(String filepath) { + Path path = Paths.get(filepath); + Resource resource = null; + try { + resource = new InputStreamResource(new FileInputStream(path.toFile())); + } catch (FileNotFoundException e) { + logger.error("Could not find file: {}", filepath); + } + return resource; + } + + @Override + public void save(MultipartFile file, String filepath) { + try { + String dir = filepath.substring(0, filepath.length() - Objects.requireNonNull(file.getOriginalFilename()).length()); + Files.createDirectory(Paths.get(dir)); + File f = new File(filepath); + file.transferTo(f); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void delete(String filepath) { + try { + Files.deleteIfExists(Paths.get(filepath)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/eu/intelcomp/catalogue/service/JobProperties.java b/src/main/java/eu/intelcomp/catalogue/service/JobProperties.java new file mode 100644 index 0000000..7e322d8 --- /dev/null +++ b/src/main/java/eu/intelcomp/catalogue/service/JobProperties.java @@ -0,0 +1,81 @@ +package eu.intelcomp.catalogue.service; + + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "jobs") +public class JobProperties { + + /** + * Job data properties. + */ + private Data data = new Data(); + + public Data getData() { + return data; + } + + public void setData(Data data) { + this.data = data; + } + + public static class Data { + + /** + * Data directories. + */ + Directories directories = new Directories(); + + public Directories getDirectories() { + return directories; + } + + public void setDirectories(Directories directories) { + this.directories = directories; + } + } + + public static class Directories { + + /** + * Should contain the absolute path of the base directory. + */ + String base = "/workdir"; + + /** + * Should contain the relative path of the input directory. + */ + String inputRelativePath = "ui"; + + /** + * Should contain the relative path of the output directory. + */ + String outputRelativePath = "output"; + + public String getBase() { + return base; + } + + public void setBase(String base) { + this.base = base; + } + + public String getInputRelativePath() { + return inputRelativePath; + } + + public void setInputRelativePath(String inputRelativePath) { + this.inputRelativePath = inputRelativePath; + } + + public String getOutputRelativePath() { + return outputRelativePath; + } + + public void setOutputRelativePath(String outputRelativePath) { + this.outputRelativePath = outputRelativePath; + } + } +} diff --git a/src/main/java/eu/intelcomp/catalogue/service/WorkflowDatasetService.java b/src/main/java/eu/intelcomp/catalogue/service/WorkflowDatasetService.java new file mode 100644 index 0000000..1e4c3f9 --- /dev/null +++ b/src/main/java/eu/intelcomp/catalogue/service/WorkflowDatasetService.java @@ -0,0 +1,81 @@ +package eu.intelcomp.catalogue.service; + +import eu.intelcomp.catalogue.controller.WorkflowDatasetController; +import eu.intelcomp.catalogue.domain.Dataset; +import eu.intelcomp.catalogue.domain.User; +import eu.openminted.registry.core.domain.FacetFilter; +import eu.openminted.registry.core.domain.Paging; +import gr.athenarc.catalogue.service.GenericItemService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +import java.lang.reflect.InvocationTargetException; +import java.util.Date; + +@Service +public class WorkflowDatasetService implements DatasetService { + + private static final Logger logger = LoggerFactory.getLogger(WorkflowDatasetController.class); + private static final String RESOURCE_TYPE = "dataset"; + private final GenericItemService genericItemService; + + public WorkflowDatasetService(GenericItemService genericItemService) { + this.genericItemService = genericItemService; + } + + @Override + public Dataset get(String id) { + return this.genericItemService.get(RESOURCE_TYPE, id); + } + + @Override + public Paging browse(FacetFilter filter) { + filter.setResourceType(RESOURCE_TYPE); + return this.genericItemService.getResults(filter); + } + + @Override + public Dataset add(String id, Dataset dataset) { + Date now = new Date(); + String userId = User.getId(SecurityContextHolder.getContext().getAuthentication()); + + dataset.setId(id); + dataset.setOwner(userId); + + dataset.setCreationDate(now); + dataset.setCreatedBy(userId); + + dataset.setModificationDate(now); + dataset.setModifiedBy(userId); + + return genericItemService.add(RESOURCE_TYPE, dataset); + } + + @Override + public Dataset update(String id, Dataset dataset) { + Dataset existing = get(id); + + dataset.setCreatedBy(existing.getCreatedBy()); + dataset.setCreationDate(existing.getCreationDate()); + + Date now = new Date(); + String userId = User.getId(SecurityContextHolder.getContext().getAuthentication()); + dataset.setModificationDate(now); + dataset.setModifiedBy(userId); + + try { + return genericItemService.update(RESOURCE_TYPE, id, dataset); + } catch (NoSuchFieldException | InvocationTargetException | NoSuchMethodException e) { + logger.error("Could not update dataset : {}", e.getMessage(), e); + } + return null; + } + + @Override + public void delete(String id) { + genericItemService.delete(RESOURCE_TYPE, id); + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..b307769 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,64 @@ +fqdn=localhost +server.port=8180 +server.servlet.contextPath=/api + + +## Spring OAuth2 ## +# Google +#spring.security.oauth2.client.registration.google.client-id= +#spring.security.oauth2.client.registration.google.client-secret= +# Custom +spring.security.oauth2.client.registration.custom.client-id= +spring.security.oauth2.client.registration.custom.client-secret= +spring.security.oauth2.client.registration.custom.client-name= +spring.security.oauth2.client.registration.custom.authorization-grant-type=authorization_code +spring.security.oauth2.client.registration.custom.scope=openid,email,profile +spring.security.oauth2.client.provider.custom.issuer-uri= + + +## Catalogue Lib ## +#catalogue-lib.jaxb.generated-classes-package-name=eu.intelcomp.xsd2java +catalogue-lib.validation.enabled=true + + +## Intelcomp Properties ## +intelcomp.admins=admin@email.com, ... +intelcomp.frontBaseUrl=http://localhost:4200 +intelcomp.loginRedirect=${intelcomp.frontBaseUrl} +intelcomp.logoutRedirect=${intelcomp.frontBaseUrl} + +openapi.info.contact.email=contact@athenarc.gr +openapi.info.contact.name=Contact Name +openapi.info.description=[Description] +openapi.info.termsOfService=[Terms] +openapi.info.title=Data Catalogue API +openapi.info.version=2.0.0 + + +## Job Service (Optional) ## +job-service.api-url=http://localhost:19000 +job-service.authorization.client-id= +job-service.authorization.client-secret= +job-service.authorization.grant-type=client_credentials +job-service.authorization.url=https://[keycloak-enpoint]/auth/realms/[default]/protocol/openid-connect/token + + +## Redis ## +spring.redis.host=localhost +spring.redis.password= +spring.redis.port=6379 + + +## Registry Properties ## +registry.host=http://${fqdn}:${server.port}/${server.servlet.contextPath}/ + +jdbc.driverClassName=org.postgresql.Driver +jdbc.url=jdbc:postgresql://${fqdn}:5432/registry +jdbc.username= +jdbc.password= + +jms.host=tcp://${fqdn}:61616 +jms.prefix= + +elasticsearch.url=${fqdn} +elasticsearch.port=9200 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 0baa02f..9294095 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,70 +1,89 @@ -springdoc: - swagger-ui: - version: 3 - disable-swagger-default-url: true +management: + endpoints: + web: + exposure: + include: openapi, swagger-ui spring: - main: - allow-circular-references: true - allow-bean-definition-overriding: true - flyway: -# enabled:false - locations: classpath:migrations - baseline-on-migrate: true - batch: - job: - enabled: false - jms: - cache: - enabled: false - - security: - oauth2: - client: - registration: - google: - client-id: - client-secret: + batch: + job: + enabled: false + flyway: +# enabled: false + baseline-on-migrate: true + locations: classpath:migrations + http: + multipart: + max-file-size: 18MB + max-request-size: 20MB + jms: + cache: + enabled: false + main: + allow-bean-definition-overriding: false - cite: - client-id: - client-secret: - scope: - - openid - - email - - profile + jackson: +# serialization: +# fail-on-empty-beans: false + jackson: + default-property-inclusion: always + deserialization: + fail-on-unknown-properties: false + mapper: + auto-detect-fields: true + visibility: + all: any - provider: - cite: - issuer-uri: +## Springdoc ## +openapi: + info: + license: + name: Apache License, Version 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0 - redis: - host: - port: - password: +springdoc: + group-configs[0]: + group: catalogue + display-name: Data Catalogue + packages-to-scan: + - eu.intelcomp + - gr.athenarc.catalogue.controller + group-configs[1]: + group: catalogue-lib + display-name: Catalogue Library + packages-to-scan: + - gr.athenarc.catalogue + group-configs[2]: + group: registry + display-name: Registry Core + packages-to-scan: + - eu.openminted -server: - port: 8080 - servlet: - contextPath: /intelcomp + swagger-ui: + doc-expansion: none + operations-sorter: method + path: /swagger-ui.html + syntax-highlight: + activated: false + theme: obsidian + tags-sorter: alpha + version: 3 +## Logging ## logging: - level: - org.springframework.security.: DEBUG - org.springframework.security.web.util.: INFO - org.springframework.web.client.RestTemplate: DEBUG - org.reflections.Reflections: WARN + level: + root: INFO -intelcomp: - frontBaseUrl: http://localhost:4200 - loginRedirect: ${intelcomp.frontBaseUrl} - logoutRedirect: ${intelcomp.frontBaseUrl} - admins: - - test@test.com +## Registry Properties ## +hibernate: + dialect: org.hibernate.dialect.PostgreSQLDialect + show_sql: false + format_sql: false + hbm2ddl.auto: update -job-service: - authorization: - url: - grant-type: - client-id: - client-secret: +## Elastic Properties ## +elastic: + index.max_result_window: 10000 + aggregation: + topHitsSize: 100 + bucketSize: 100 diff --git a/src/main/resources/registry.properties b/src/main/resources/registry.properties deleted file mode 100644 index 9685e36..0000000 --- a/src/main/resources/registry.properties +++ /dev/null @@ -1,29 +0,0 @@ -## Spring Properties ## -############################################################# -spring.jms.cache.enabled=false - -#spring.flyway.enabled=false -spring.flyway.locations=classpath:migrations -spring.flyway.baseline-on-migrate=true - -spring.batch.job.enabled=false - -spring.jackson.default-property-inclusion=always - -## Elastic Properties ## -############################################################# -elastic.aggregation.topHitsSize=100 -elastic.aggregation.bucketSize=100 -elastic.index.max_result_window=10000 - -## Registry DB Properties ## -############################################################# -jdbc.driverClassName=org.postgresql.Driver -hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect -hibernate.show_sql=false -hibernate.format_sql=false -hibernate.hbm2ddl.auto=update - -## Registry Properties ## -############################################################# -registry.host= diff --git a/src/main/resources/resourceTypes/ai_model.json b/src/main/resources/resourceTypes/ai_model.json new file mode 100644 index 0000000..cc6fb8c --- /dev/null +++ b/src/main/resources/resourceTypes/ai_model.json @@ -0,0 +1,120 @@ +{ + "indexFields": [ + { + "defaultValue": null, + "label": null, + "multivalued": false, + "name": "resource_internal_id", + "path": "//*[local-name()='model']/*[local-name()='id']/text()", + "primaryKey": true, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": null, + "multivalued": false, + "name": "ai_model_id", + "path": "//*[local-name()='model']/*[local-name()='id']/text()", + "primaryKey": true, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": "Resource Type", + "multivalued": false, + "name": "resourceType", + "path": "//*[local-name()='model']/*[local-name()='resourceType']/text()", + "primaryKey": false, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": "Version", + "multivalued": false, + "name": "version", + "path": "//*[local-name()='model']/*[local-name()='version']/text()", + "primaryKey": false, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": "Publisher", + "multivalued": false, + "name": "publisher", + "path": "//*[local-name()='model']/*[local-name()='publisher']/text()", + "primaryKey": false, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": "Rights", + "multivalued": true, + "name": "rights", + "path": "//*[local-name()='model']/*[local-name()='rightsList']/*[local-name()='rights']/text()", + "primaryKey": false, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": "Formats", + "multivalued": true, + "name": "formats", + "path": "//*[local-name()='model']/*[local-name()='formats']/*[local-name()='format']/text()", + "primaryKey": false, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": "Subjects", + "multivalued": true, + "name": "subjects", + "path": "//*[local-name()='model']/*[local-name()='subjects']/*[local-name()='subject']/text()", + "primaryKey": false, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": "Titles", + "multivalued": true, + "name": "titles", + "path": "//*[local-name()='model']/*[local-name()='titles']/*[local-name()='title']/text()", + "primaryKey": false, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": "Identifier", + "multivalued": false, + "name": "identifier", + "path": "//*[local-name()='model']/*[local-name()='identifier']/text()", + "primaryKey": true, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": "Datasets", + "multivalued": true, + "name": "datasets", + "path": "//*[local-name()='model']/*[local-name()='relatedIdentifiers']/*[local-name()='relatedIdentifier'][contains(@relationType, 'Requires') and contains(@resourceTypeGeneral, 'Dataset') and contains(@relatedIdentifierType, 'internal')]/text()", + "primaryKey": false, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": "Models", + "multivalued": true, + "name": "models", + "path": "//*[local-name()='model']/*[local-name()='relatedIdentifiers']/*[local-name()='relatedIdentifier'][@relationType='Requires'][@resourceTypeGeneral='Model']/text()", + "primaryKey": false, + "type": "java.lang.String" + } + ], + "indexMapperClass": "eu.openminted.registry.core.index.DefaultIndexMapper", + "aliasGroup": "resourceTypes", + "name": "ai_model", + "payloadType": "xml", + "properties": { + "class": "eu.intelcomp.xsd2java.Model" + }, + "schemaUrl": "https://raw.githubusercontent.com/IntelCompH2020/intelcomp-catalogue/develop/src/main/resources/schema/model.xsd" +} diff --git a/src/main/resources/resourceTypes/dataset.json b/src/main/resources/resourceTypes/dataset.json new file mode 100644 index 0000000..48ce2bd --- /dev/null +++ b/src/main/resources/resourceTypes/dataset.json @@ -0,0 +1,68 @@ +{ + "indexFields": [ + { + "multivalued": false, + "name": "resource_internal_id", + "path": "$.id", + "type": "java.lang.String", + "primaryKey": true + }, + { + "multivalued": false, + "name": "name", + "label": "Dataset Name", + "path": "$.name", + "type": "java.lang.String" + }, + { + "multivalued": false, + "name": "owner", + "label": "Owner", + "path": "$.owner", + "type": "java.lang.String" + }, + { + "multivalued": false, + "name": "createdBy", + "label": "Created By", + "path": "$.createdBy", + "type": "java.lang.String" + }, + { + "multivalued": false, + "name": "creationDate", + "label": "Creation Date", + "path": "$.creationDate", + "type": "java.util.Date" + }, + { + "multivalued": false, + "name": "modifiedBy", + "label": "Modified By", + "path": "$.modifiedBy", + "type": "java.lang.String" + }, + { + "multivalued": false, + "name": "modificationDate", + "label": "Modification Date", + "path": "$.modificationDate", + "type": "java.util.Date" + }, + { + "multivalued": true, + "name": "usages", + "label": "Usages", + "path": "$.usages", + "type": "java.lang.String" + } + ], + "indexMapperClass": "eu.openminted.registry.core.index.DefaultIndexMapper", + "name": "dataset", + "aliasGroup": "resourceTypes", + "payloadType": "json", + "properties": { + "class": "eu.intelcomp.catalogue.domain.Dataset" + }, + "schema": "{\n\t\"$schema\": \"https://json-schema.org/draft/2019-09/schema\"\n}" +} \ No newline at end of file diff --git a/src/main/resources/resourceTypes/dataset_instance.json b/src/main/resources/resourceTypes/dataset_instance.json index 5a7c566..ca25593 100644 --- a/src/main/resources/resourceTypes/dataset_instance.json +++ b/src/main/resources/resourceTypes/dataset_instance.json @@ -1,5 +1,23 @@ { "indexFields": [ + { + "defaultValue": null, + "label": null, + "multivalued": false, + "name": "resource_internal_id", + "path": "//*[local-name()='dataset_instance']/*[local-name()='id']/text()", + "primaryKey": true, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": null, + "multivalued": false, + "name": "dataset_instance_id", + "path": "//*[local-name()='dataset_instance']/*[local-name()='id']/text()", + "primaryKey": true, + "type": "java.lang.String" + }, { "defaultValue": null, "label": "Type", @@ -80,5 +98,5 @@ "properties": { "class": "eu.intelcomp.xsd2java.DatasetInstance" }, - "schema": "\n\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n The following metadata elements are based on the DataCite schema\n \n \n \n Version number of the resource. If the primary resource has changed the version number increases.\n Register a new identifier for a major version change. Individual stewards need to determine which are major vs. minor versions. May be used in conjunction with properties 11 and 12 (AlternateIdentifier and RelatedIdentifier) to indicate various information updates. May be used in conjunction with property 17 (Description) to indicate the nature and file/record range of version.\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n The main researchers involved working on the data, or the authors of the publication in priority order. May be a corporate/institutional or personal name.\n Format: Family, Given.\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n The institution or person responsible for collecting, creating, or otherwise contributing to the developement of the dataset.\n The personal name format should be: Family, Given.\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n Different dates relevant to the work.\n YYYY,YYYY-MM-DD, YYYY-MM-DDThh:mm:ssTZD or any other format or level of granularity described in W3CDTF. Use RKMS-ISO8601 standard for depicting date ranges.\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n Unstructures size information about the resource.\n \n \n \n \n \n \n \n \n \n The type of date. Use RKMS‐ISO8601 standard for depicting date ranges.To indicate the end of an embargo period, use Available. To indicate the start of an embargo period, use Submitted or Accepted, as appropriate.\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n The type of contributor of the resource.\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + "schema": "\n\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n The following metadata elements are based on the DataCite schema\n \n \n \n Version number of the resource. If the primary resource has changed the version number increases.\n Register a new identifier for a major version change. Individual stewards need to determine which are major vs. minor versions. May be used in conjunction with properties 11 and 12 (AlternateIdentifier and RelatedIdentifier) to indicate various information updates. May be used in conjunction with property 17 (Description) to indicate the nature and file/record range of version.\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n The main researchers involved working on the data, or the authors of the publication in priority order. May be a corporate/institutional or personal name.\n Format: Family, Given.\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n The institution or person responsible for collecting, creating, or otherwise contributing to the developement of the dataset.\n The personal name format should be: Family, Given.\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n Different dates relevant to the work.\n YYYY,YYYY-MM-DD, YYYY-MM-DDThh:mm:ssTZD or any other format or level of granularity described in W3CDTF. Use RKMS-ISO8601 standard for depicting date ranges.\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n Unstructures size information about the resource.\n \n \n \n \n \n \n \n \n \n The type of date. Use RKMS‐ISO8601 standard for depicting date ranges.To indicate the end of an embargo period, use Available. To indicate the start of an embargo period, use Submitted or Accepted, as appropriate.\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n The type of contributor of the resource.\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n" } diff --git a/src/main/resources/resourceTypes/dataset_type.json b/src/main/resources/resourceTypes/dataset_type.json index 8397c87..9e7be0f 100644 --- a/src/main/resources/resourceTypes/dataset_type.json +++ b/src/main/resources/resourceTypes/dataset_type.json @@ -1,5 +1,14 @@ { "indexFields": [ + { + "defaultValue": null, + "label": null, + "multivalued": false, + "name": "resource_internal_id", + "path": "//*[local-name()='dataset_type']/*[local-name()='id']/text()", + "primaryKey": true, + "type": "java.lang.String" + }, { "defaultValue": null, "label": null, diff --git a/src/main/resources/resourceTypes/model.json b/src/main/resources/resourceTypes/model.json new file mode 100644 index 0000000..a8411be --- /dev/null +++ b/src/main/resources/resourceTypes/model.json @@ -0,0 +1,116 @@ +{ + "indexFields": [ + { + "multivalued": false, + "name": "resource_internal_id", + "path": "$.id", + "type": "java.lang.String", + "primaryKey": true + }, + { + "multivalued": false, + "name": "model_id", + "path": "$.id", + "type": "java.lang.String", + "primaryKey": true + }, + { + "multivalued": false, + "name": "name", + "path": "$.name", + "type": "java.lang.String" + }, + { + "multivalued": false, + "name": "description", + "path": "$.description", + "type": "java.lang.String" + }, + { + "multivalued": false, + "name": "resourceType", + "path": "$.resourceType", + "type": "java.lang.String" + }, + { + "multivalued": false, + "name": "type", + "path": "$.type", + "type": "java.lang.String" + }, + { + "multivalued": false, + "name": "subType", + "path": "$.subType", + "type": "java.lang.String" + }, + { + "multivalued": false, + "name": "creationDate", + "path": "$.creationDate", + "type": "java.util.Date" + }, + { + "multivalued": false, + "name": "modificationDate", + "path": "$.modificationDate", + "type": "java.util.Date" + }, + { + "multivalued": true, + "name": "allSectionIds", + "path": "$.sections[*]..subSections[*].id", + "type": "java.lang.String" + }, + { + "multivalued": true, + "name": "allSectionNames", + "path": "$.sections[*]..subSections[*].name", + "type": "java.lang.String" + }, + { + "multivalued": true, + "name": "sectionIds", + "path": "$.sections[*].id", + "type": "java.lang.String" + }, + { + "multivalued": true, + "name": "sectionNames", + "path": "$.sections[*].name", + "type": "java.lang.String" + }, + { + "multivalued": true, + "name": "subSectionsIds", + "path": "$.sections[*].subSections[*].id", + "type": "java.lang.String" + }, + { + "multivalued": true, + "name": "subSectionsNames", + "path": "$.sections[*].subSections[*].name", + "type": "java.lang.String" + }, + { + "multivalued": true, + "name": "fields", + "path": "$..fields[*].id", + "type": "java.lang.String" + }, + { + "multivalued": true, + "name": "fieldNames", + "path": "$..fields[*].name", + "type": "java.lang.String" + } + ], + "indexMapperClass": "eu.openminted.registry.core.index.DefaultIndexMapper", + "name": "model", + "aliasGroup": "resourceTypes", + "payloadType": "json", + "properties": { + "class": "gr.athenarc.catalogue.ui.domain.Model" + }, + "schema": "{\n\t\"$schema\": \"https://json-schema.org/draft/2019-09/schema\"\n}" +} diff --git a/src/main/resources/resourceTypes/tool.json b/src/main/resources/resourceTypes/tool.json new file mode 100644 index 0000000..e8ed8b6 --- /dev/null +++ b/src/main/resources/resourceTypes/tool.json @@ -0,0 +1,120 @@ +{ + "indexFields": [ + { + "defaultValue": null, + "label": null, + "multivalued": false, + "name": "resource_internal_id", + "path": "//*[local-name()='tool']/*[local-name()='id']/text()", + "primaryKey": true, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": null, + "multivalued": false, + "name": "tool_id", + "path": "//*[local-name()='tool']/*[local-name()='id']/text()", + "primaryKey": true, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": "Resource Type", + "multivalued": false, + "name": "resourceType", + "path": "//*[local-name()='tool']/*[local-name()='resourceType']/text()", + "primaryKey": false, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": "Version", + "multivalued": false, + "name": "version", + "path": "//*[local-name()='tool']/*[local-name()='version']/text()", + "primaryKey": false, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": "Publisher", + "multivalued": false, + "name": "publisher", + "path": "//*[local-name()='tool']/*[local-name()='publisher']/text()", + "primaryKey": false, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": "Rights", + "multivalued": true, + "name": "rights", + "path": "//*[local-name()='tool']/*[local-name()='rightsList']/*[local-name()='rights']/text()", + "primaryKey": false, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": "Formats", + "multivalued": true, + "name": "formats", + "path": "//*[local-name()='tool']/*[local-name()='formats']/*[local-name()='format']/text()", + "primaryKey": false, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": "Subjects", + "multivalued": true, + "name": "subjects", + "path": "//*[local-name()='tool']/*[local-name()='subjects']/*[local-name()='subject']/text()", + "primaryKey": false, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": "Titles", + "multivalued": true, + "name": "titles", + "path": "//*[local-name()='tool']/*[local-name()='titles']/*[local-name()='title']/text()", + "primaryKey": false, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": "Identifier", + "multivalued": false, + "name": "identifier", + "path": "//*[local-name()='tool']/*[local-name()='identifier']/text()", + "primaryKey": true, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": "Datasets", + "multivalued": true, + "name": "datasets", + "path": "//*[local-name()='tool']/*[local-name()='relatedIdentifiers']/*[local-name()='relatedIdentifier'][contains(@relationType, 'Requires') and contains(@resourceTypeGeneral, 'Dataset') and contains(@relatedIdentifierType, 'internal')]/text()", + "primaryKey": false, + "type": "java.lang.String" + }, + { + "defaultValue": null, + "label": "Models", + "multivalued": true, + "name": "models", + "path": "//*[local-name()='tool']/*[local-name()='relatedIdentifiers']/*[local-name()='relatedIdentifier'][@relationType='Requires'][@resourceTypeGeneral='Model']/text()", + "primaryKey": false, + "type": "java.lang.String" + } + ], + "indexMapperClass": "eu.openminted.registry.core.index.DefaultIndexMapper", + "aliasGroup": "resourceTypes", + "name": "tool", + "payloadType": "xml", + "properties": { + "class": "eu.intelcomp.xsd2java.Tool" + }, + "schemaUrl": "https://raw.githubusercontent.com/IntelCompH2020/intelcomp-catalogue/develop/src/main/resources/schema/tool.xsd" +} diff --git a/src/main/resources/schema/dataset_instance.xsd b/src/main/resources/schema/dataset_instance.xsd index 9985d84..312d688 100644 --- a/src/main/resources/schema/dataset_instance.xsd +++ b/src/main/resources/schema/dataset_instance.xsd @@ -1,30 +1,32 @@ - + - - + + + - - + + - + @@ -33,7 +35,7 @@ Changes in v0.0.3 - + @@ -110,7 +112,7 @@ Changes in v0.0.3 - + @@ -127,7 +129,7 @@ Changes in v0.0.3 - + @@ -148,49 +150,4 @@ Changes in v0.0.3 - - - The type of date. Use RKMS‐ISO8601 standard for depicting date ranges.To indicate the end of an embargo period, use Available. To indicate the start of an embargo period, use Submitted or Accepted, as appropriate. - - - - - - - - - - - - - - - - The type of contributor of the resource. - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/schema/dataset_type.xsd b/src/main/resources/schema/dataset_type.xsd index c8be6c2..4f7e943 100644 --- a/src/main/resources/schema/dataset_type.xsd +++ b/src/main/resources/schema/dataset_type.xsd @@ -1,4 +1,4 @@ - + - - + + + @@ -35,7 +36,7 @@ Changes in v0.0.3 - + @@ -81,7 +82,7 @@ Changes in v0.0.3 - + @@ -114,8 +115,8 @@ Changes in v0.0.3 - - + + @@ -166,7 +167,7 @@ Changes in v0.0.3 - + @@ -186,8 +187,8 @@ Changes in v0.0.3 - - + + @@ -248,7 +249,7 @@ Changes in v0.0.3 - + @@ -287,127 +288,4 @@ Changes in v0.0.3 - - - - - - - - - - - The general type of a resource. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The type of the description. - - - - - - - - - - - - The type of the entity. - - - - - - - - - - The type of the RelatedIdentifier. - - - - - - - - - - - - - - - - - - - - - - - - - Description of the relationship of the resource being registered (A) and the related resource (B). - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/schema/imports.xsd b/src/main/resources/schema/imports.xsd new file mode 100644 index 0000000..8f97a1d --- /dev/null +++ b/src/main/resources/schema/imports.xsd @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Uniquely identifies a creator or contributor, according to various identifier schemes. + + + + + + + + + + + + + + + + + + + + + + + + + + + + Uniquely identifies an affiliation, according to various identifier schemes. + + + + + + + + + + + + + + + + + + + + + + + + + The general type of a resource. + + + + + + + + + + + + + + + + + + + + + The type of the description. + + + + + + + + + + + + + The type of the entity. + + + + + + + + + + The type of the RelatedIdentifier. + + + + + + + + + + + + + + + + + + + + + + + + + + Description of the relationship of the resource being registered (A) and the related resource (B). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The type of date. Use RKMS‐ISO8601 standard for depicting date ranges.To indicate the end of an embargo period, use Available. To indicate the start of an embargo period, use Submitted or Accepted, as appropriate. + + + + + + + + + + + + + + + + The type of contributor of the resource. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/schema/model.xsd b/src/main/resources/schema/model.xsd new file mode 100644 index 0000000..06790c4 --- /dev/null +++ b/src/main/resources/schema/model.xsd @@ -0,0 +1,546 @@ + + + + + + + + + Root element of a single record. This wrapper element is for XML implementation only and is not defined in the DataCite DOI standard. + Note: This is the case for all wrapper elements within this schema. + + No content in this wrapper element. + + + + + + + + A persistent identifier that identifies a resource. + + + + + + + + + + + + + + + The main researchers involved working on the data, or the authors of the publication in priority order. May be a corporate/institutional or personal name. + Format: Family, Given. + Personal names can be further specified using givenName and familyName. + + + + + + + + + + + + + + + + + + + + + + + + + + A name or title by which a resource is known. + + + + + + + + + + + + + + + The name of the entity that holds, archives, publishes prints, distributes, releases, issues, or produces the resource. This property will be used to formulate the citation, so consider the prominence of the role. + In the case of datasets, "publish" is understood to mean making the data available to the community of researchers. + + + + + + + + + + + Year when the data is made publicly available. If an embargo period has been in effect, use the date when the embargo period ends. + In the case of datasets, "publish" is understood to mean making the data available on a specific date to the community of researchers. If there is no standard publication year value, use the date that would be preferred from a citation perspective. + YYYY + + + + + + + + The type of a resource. You may enter an additional free text description. + The format is open, but the preferred format is a single term of some detail so that a pair can be formed with the sub-property. + + + + + + + + + Subject, keywords, classification codes, or key phrases describing the resource. + + + + + + + + + + + + + + + + + + + + + The institution or person responsible for collecting, creating, or otherwise contributing to the developement of the dataset. + The personal name format should be: Family, Given. + + + + + + + + + + + + + + + + + + + + + + + + + + + Different dates relevant to the work. + YYYY,YYYY-MM-DD, YYYY-MM-DDThh:mm:ssTZD or any other format or level of granularity described in W3CDTF. Use RKMS-ISO8601 standard for depicting date ranges. + + + + + + + + + + + + + + + + Primary language of the resource. Allowed values are taken from IETF BCP 47, ISO 639-1 language codes. + + + + + + + + An identifier or identifiers other than the primary Identifier applied to the resource being registered. This may be any alphanumeric string which is unique within its domain of issue. May be used for local identifiers. AlternateIdentifier should be used for another identifier of the same instance (same location, same file). + + + + + + + + + + + + + + + + + + Identifiers of related resources. Use this property to indicate subsets of properties, as appropriate. + + + + + + + + + + + + + + + + + + + + + + + Unstructures size information about the resource. + + + + + + + + + + + Technical format of the resource. + Use file extension or MIME type where possible. + + + + + + + + Version number of the resource. If the primary resource has changed the version number increases. + Register a new identifier for a major version change. Individual stewards need to determine which are major vs. minor versions. May be used in conjunction with properties 11 and 12 (AlternateIdentifier and RelatedIdentifier) to indicate various information updates. May be used in conjunction with property 17 (Description) to indicate the nature and file/record range of version. + + + + + + + + + Any rights information for this resource. Provide a rights management statement for the resource or reference a service providing such information. Include embargo information if applicable. + Use the complete title of a license and include version information if applicable. + + + + + + + + + + + + + + + + + + + + + + All additional information that does not fit in any of the other categories. May be used for technical information. It is a best practice to supply a description. + + + + + + + + + + + + + + + + + + + Information about financial support (funding) for the resource being registered. + + + + + + Name of the funding provider. + + + + + + + + Uniquely identifies a funding entity, according to various types. + + + + + + + + + + + + + The code assigned by the funder to a sponsored award (grant). + + + + + + + + + + + + The human-readable title of the award (grant). + + + + + + + + + + + + + + Information about a resource related to the one being registered e.g. a journal or book of which the article or chapter is part. + + + + + + The identifier for the related item. + + + + + + + The type of the Identifier for the related item e.g. DOI. + + + + + The name of the scheme. + + + + + The URI of the relatedMetadataScheme. + + + + + The type of the relatedMetadataScheme, linked with the schemeURI. + + + + + + + + + + + + + The institution or person responsible for creating the + related resource. To supply multiple creators, repeat this property. + + + + + + + + + + + + + + + + + + + + + + + + + Title of the related item. + + + + + + + + + + + + + + + The year when the item was or will be made publicly available. + + + + + + + + Volume of the related item. + + + + + Issue number or name of the related item. + + + + + Number of the related item e.g. report number of article number. + + + + + + + + + + + + First page of the related item e.g. of the chapter, article, or conference paper. + + + + + Last page of the related item e.g. of the chapter, article, or conference paper. + + + + + The name of the entity that holds, archives, publishes prints, distributes, releases, issues, or produces the resource. This property will be used to formulate the citation, so consider the prominence of the role. + + + + + Edition or version of the related item. + + + + + + + + + The institution or person responsible for collecting, + managing, distributing, or otherwise contributing to the development of + the resource. + + + + + + + + + + + + + + + + + The type of contributor of the resource. + + + + + + + + + + + The type of the related item, e.g. journal article, book or chapter. + + + + + Description of the relationship of the resource being registered (A) and the related resource (B). + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/schema/tool.xsd b/src/main/resources/schema/tool.xsd new file mode 100644 index 0000000..762cebb --- /dev/null +++ b/src/main/resources/schema/tool.xsd @@ -0,0 +1,546 @@ + + + + + + + + + Root element of a single record. This wrapper element is for XML implementation only and is not defined in the DataCite DOI standard. + Note: This is the case for all wrapper elements within this schema. + + No content in this wrapper element. + + + + + + + + A persistent identifier that identifies a resource. + + + + + + + + + + + + + + + The main researchers involved working on the data, or the authors of the publication in priority order. May be a corporate/institutional or personal name. + Format: Family, Given. + Personal names can be further specified using givenName and familyName. + + + + + + + + + + + + + + + + + + + + + + + + + + A name or title by which a resource is known. + + + + + + + + + + + + + + + The name of the entity that holds, archives, publishes prints, distributes, releases, issues, or produces the resource. This property will be used to formulate the citation, so consider the prominence of the role. + In the case of datasets, "publish" is understood to mean making the data available to the community of researchers. + + + + + + + + + + + Year when the data is made publicly available. If an embargo period has been in effect, use the date when the embargo period ends. + In the case of datasets, "publish" is understood to mean making the data available on a specific date to the community of researchers. If there is no standard publication year value, use the date that would be preferred from a citation perspective. + YYYY + + + + + + + + The type of a resource. You may enter an additional free text description. + The format is open, but the preferred format is a single term of some detail so that a pair can be formed with the sub-property. + + + + + + + + + Subject, keywords, classification codes, or key phrases describing the resource. + + + + + + + + + + + + + + + + + + + + + The institution or person responsible for collecting, creating, or otherwise contributing to the developement of the dataset. + The personal name format should be: Family, Given. + + + + + + + + + + + + + + + + + + + + + + + + + + + Different dates relevant to the work. + YYYY,YYYY-MM-DD, YYYY-MM-DDThh:mm:ssTZD or any other format or level of granularity described in W3CDTF. Use RKMS-ISO8601 standard for depicting date ranges. + + + + + + + + + + + + + + + + Primary language of the resource. Allowed values are taken from IETF BCP 47, ISO 639-1 language codes. + + + + + + + + An identifier or identifiers other than the primary Identifier applied to the resource being registered. This may be any alphanumeric string which is unique within its domain of issue. May be used for local identifiers. AlternateIdentifier should be used for another identifier of the same instance (same location, same file). + + + + + + + + + + + + + + + + + + Identifiers of related resources. Use this property to indicate subsets of properties, as appropriate. + + + + + + + + + + + + + + + + + + + + + + + Unstructures size information about the resource. + + + + + + + + + + + Technical format of the resource. + Use file extension or MIME type where possible. + + + + + + + + Version number of the resource. If the primary resource has changed the version number increases. + Register a new identifier for a major version change. Individual stewards need to determine which are major vs. minor versions. May be used in conjunction with properties 11 and 12 (AlternateIdentifier and RelatedIdentifier) to indicate various information updates. May be used in conjunction with property 17 (Description) to indicate the nature and file/record range of version. + + + + + + + + + Any rights information for this resource. Provide a rights management statement for the resource or reference a service providing such information. Include embargo information if applicable. + Use the complete title of a license and include version information if applicable. + + + + + + + + + + + + + + + + + + + + + + All additional information that does not fit in any of the other categories. May be used for technical information. It is a best practice to supply a description. + + + + + + + + + + + + + + + + + + + Information about financial support (funding) for the resource being registered. + + + + + + Name of the funding provider. + + + + + + + + Uniquely identifies a funding entity, according to various types. + + + + + + + + + + + + + The code assigned by the funder to a sponsored award (grant). + + + + + + + + + + + + The human-readable title of the award (grant). + + + + + + + + + + + + + + Information about a resource related to the one being registered e.g. a journal or book of which the article or chapter is part. + + + + + + The identifier for the related item. + + + + + + + The type of the Identifier for the related item e.g. DOI. + + + + + The name of the scheme. + + + + + The URI of the relatedMetadataScheme. + + + + + The type of the relatedMetadataScheme, linked with the schemeURI. + + + + + + + + + + + + + The institution or person responsible for creating the + related resource. To supply multiple creators, repeat this property. + + + + + + + + + + + + + + + + + + + + + + + + + Title of the related item. + + + + + + + + + + + + + + + The year when the item was or will be made publicly available. + + + + + + + + Volume of the related item. + + + + + Issue number or name of the related item. + + + + + Number of the related item e.g. report number of article number. + + + + + + + + + + + + First page of the related item e.g. of the chapter, article, or conference paper. + + + + + Last page of the related item e.g. of the chapter, article, or conference paper. + + + + + The name of the entity that holds, archives, publishes prints, distributes, releases, issues, or produces the resource. This property will be used to formulate the citation, so consider the prominence of the role. + + + + + Edition or version of the related item. + + + + + + + + + The institution or person responsible for collecting, + managing, distributing, or otherwise contributing to the development of + the resource. + + + + + + + + + + + + + + + + + The type of contributor of the resource. + + + + + + + + + + + The type of the related item, e.g. journal article, book or chapter. + + + + + Description of the relationship of the resource being registered (A) and the related resource (B). + + + + + + + + + + + \ No newline at end of file