From 0f0163dc2d61cb75028e70f89a10bd92f04a82ee Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Wed, 15 Feb 2023 17:14:02 +0200 Subject: [PATCH 01/30] Update the "getRepositoriesByCountry"-endpoint to return up to 10_000 repositories. --- .../eu/dnetlib/repo/manager/service/RepositoryServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/dnetlib/repo/manager/service/RepositoryServiceImpl.java b/src/main/java/eu/dnetlib/repo/manager/service/RepositoryServiceImpl.java index 00eeeda..effafa5 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/RepositoryServiceImpl.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/RepositoryServiceImpl.java @@ -228,7 +228,7 @@ public class RepositoryServiceImpl implements RepositoryService { Boolean managed) { logger.debug("Getting repositories by country!"); int page = 0; - int size = 100; + int size = 10_000; // Include all repositories. Some countries have more than a thousand. String filterKey = "UNKNOWN"; if (mode.equalsIgnoreCase("repository")) From 8f5aed3aab53366b03597c333afd9810d9b9ea00 Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Wed, 15 Feb 2023 18:03:20 +0200 Subject: [PATCH 02/30] Update application.yml --- src/main/resources/application.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 409f18f..93e95cb 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -14,10 +14,11 @@ spring: driverClassName: ${services.provide.db.driverClassName} services: + openaireServicesBaseUrl: https://beta.services.openaire.eu provide: dev-machine: 88.197.53.71 # VM-71 aai: - baseURL: https://aai.openaire.eu + baseURL: https://beta.aai.openaire.eu oidc: domain: .openaire.eu # use empty value for local, otherwise: ".openaire.eu" id: XX @@ -27,10 +28,10 @@ services: webURL: http://localhost:4200/join registry: coid: XX + username: XX password: XX production: false url: ${services.provide.aai.baseURL}/registry/ - username: provide_openaire adminEmail: XX analyticsURL: https://analytics.openaire.eu/addsite.php? baseUrl: https://dev-openaire.d4science.org/openaire @@ -41,9 +42,9 @@ services: url: https://beta.broker.openaire.eu clients: dsm: https://dev-openaire.d4science.org/openaire - search: https://beta.services.openaire.eu/search/v2/api + search: ${services.openaireServicesBaseUrl}/search/v2/api usageEvents: http://beta.lbs.openaire.eu:8080/ajax/summary - usagestats: https://beta.services.openaire.eu/usagestats + usagestats: ${services.openaireServicesBaseUrl}/usagestats db: driverClassName: org.postgresql.Driver password: dnetPwd @@ -66,12 +67,12 @@ services: password: XX port: 6379 topic_types: - url: https://beta.services.openaire.eu/provision/mvc/vocabularies/dnet:topic_types.json + url: ${services.openaireServicesBaseUrl}/provision/mvc/vocabularies/dnet:topic_types.json usageStatisticsDiagramsBaseURL: https://beta.openaire.eu/stats3/ - usageStatisticsNumbersBaseURL: https://beta.services.openaire.eu/usagestats/datasources/ + usageStatisticsNumbersBaseURL: ${services.openaireServicesBaseUrl}/usagestats/datasources/ usagestats: adminEmail: XX - sushiliteEndpoint: https://beta.services.openaire.eu/usagestats/sushilite/ + sushiliteEndpoint: ${services.openaireServicesBaseUrl}/usagestats/sushilite/ validator: results: url: https://beta.provide.openaire.eu/compatibility/browseHistory/ From c006c7baa0bdfdcfb07548712ed45f5aa918dcff Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Wed, 15 Feb 2023 18:31:13 +0200 Subject: [PATCH 03/30] - Show error-messages for Broker's errors. - Code polishing. --- .../dnetlib/repo/manager/service/BrokerServiceImpl.java | 8 ++++++++ .../repo/manager/service/DashboardServiceImpl.java | 8 ++++---- .../repo/manager/service/RepositoryServiceImpl.java | 2 -- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/java/eu/dnetlib/repo/manager/service/BrokerServiceImpl.java b/src/main/java/eu/dnetlib/repo/manager/service/BrokerServiceImpl.java index d4506d2..de2d52e 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/BrokerServiceImpl.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/BrokerServiceImpl.java @@ -114,6 +114,7 @@ public class BrokerServiceImpl implements BrokerService { new ParameterizedTypeReference>() { }); } catch (RestClientException e) { + logger.error(e.getMessage()); throw new BrokerException(e); } @@ -146,6 +147,7 @@ public class BrokerServiceImpl implements BrokerService { new ParameterizedTypeReference() {} ); } catch (RestClientException e) { + logger.error(e.getMessage()); throw new BrokerException(e); } return resp.getBody(); @@ -195,6 +197,7 @@ public class BrokerServiceImpl implements BrokerService { new ParameterizedTypeReference() { }); } catch (RestClientException e) { + logger.error(e.getMessage()); throw new BrokerException(e); } return resp.getBody(); @@ -219,6 +222,7 @@ public class BrokerServiceImpl implements BrokerService { new ParameterizedTypeReference>>() { }); } catch (RestClientException e) { + logger.error(e.getMessage()); throw new BrokerException(e); } return resp.getBody(); @@ -252,6 +256,7 @@ public class BrokerServiceImpl implements BrokerService { new ParameterizedTypeReference() { }); } catch (RestClientException e) { + logger.error(e.getMessage()); throw new BrokerException(e); } @@ -274,6 +279,7 @@ public class BrokerServiceImpl implements BrokerService { new ParameterizedTypeReference() { }); } catch (RestClientException e) { + logger.error(e.getMessage()); throw new BrokerException(e); } return new ResponseEntity<>("OK", HttpStatus.OK); @@ -296,6 +302,7 @@ public class BrokerServiceImpl implements BrokerService { new ParameterizedTypeReference() { }); } catch (RestClientException e) { + logger.error(e.getMessage()); throw new BrokerException(e); } return resp.getBody(); @@ -325,6 +332,7 @@ public class BrokerServiceImpl implements BrokerService { new ParameterizedTypeReference() { }); } catch (RestClientException e) { + logger.error(e.getMessage()); throw new BrokerException(e); } return resp.getBody(); diff --git a/src/main/java/eu/dnetlib/repo/manager/service/DashboardServiceImpl.java b/src/main/java/eu/dnetlib/repo/manager/service/DashboardServiceImpl.java index 38f15cc..c84bb03 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/DashboardServiceImpl.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/DashboardServiceImpl.java @@ -1,7 +1,7 @@ package eu.dnetlib.repo.manager.service; import eu.dnetlib.enabling.datasources.common.AggregationInfo; -import eu.dnetlib.repo.manager.domain.MetricsInfo; +import eu.dnetlib.repo.manager.domain.MetricsNumbers; import eu.dnetlib.repo.manager.domain.RepositorySnippet; import eu.dnetlib.repo.manager.domain.RepositorySummaryInfo; import eu.dnetlib.repo.manager.domain.broker.BrowseEntry; @@ -68,9 +68,9 @@ public class DashboardServiceImpl implements DashboardService { logger.warn("Could not find repo aggregations, after " + (end - start) + "ms!"); try { - MetricsInfo metricsInfo = repositoryService.getMetricsInfoForRepository(repoId); - repositorySummaryInfo.setTotalDownloads(metricsInfo.getMetricsNumbers().getTotalDownloads()); - repositorySummaryInfo.setTotalViews(metricsInfo.getMetricsNumbers().getTotalViews()); + MetricsNumbers metricsNumbers = repositoryService.getMetricsInfoForRepository(repoId).getMetricsNumbers(); + repositorySummaryInfo.setTotalDownloads(metricsNumbers.getTotalDownloads()); + repositorySummaryInfo.setTotalViews(metricsNumbers.getTotalViews()); } catch (RepositoryServiceException e) { logger.error("Exception getting metrics info for repository: {}, {} ", repoId, repoOfficialName, e); } diff --git a/src/main/java/eu/dnetlib/repo/manager/service/RepositoryServiceImpl.java b/src/main/java/eu/dnetlib/repo/manager/service/RepositoryServiceImpl.java index effafa5..e05c9cc 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/RepositoryServiceImpl.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/RepositoryServiceImpl.java @@ -776,12 +776,10 @@ public class RepositoryServiceImpl implements RepositoryService { @Override public MetricsInfo getMetricsInfoForRepository(String repoId) throws RepositoryServiceException { try { - MetricsInfo metricsInfo = new MetricsInfo(); metricsInfo.setDiagramsBaseURL(this.usageStatisticsDiagramsBaseURL); metricsInfo.setMetricsNumbers(getMetricsNumbers(getOpenAIREId(repoId))); return metricsInfo; - } catch (Exception e) { logger.error("Error while getting metrics info for repository: ", e); throw new RepositoryServiceException("General error", RepositoryServiceException.ErrorCode.GENERAL_ERROR); From b67e98976d789d809fa5b9ee38a86b9a0a44aa90 Mon Sep 17 00:00:00 2001 From: Konstantinos Spyrou Date: Fri, 17 Feb 2023 16:17:37 +0200 Subject: [PATCH 04/30] Improved role functionality --- .../config/AaiSecurityConfiguration.java | 3 +- .../service/RepositoryServiceImpl.java | 34 +++++-- .../service/aai/registry/RegistryCalls.java | 1 + .../security/AaiRoleMappingService.java | 92 +++++++++---------- .../security/AuthorizationServiceImpl.java | 18 ++-- .../service/security/RoleMappingService.java | 43 ++------- 6 files changed, 85 insertions(+), 106 deletions(-) diff --git a/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java b/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java index 0a02ce5..0782850 100644 --- a/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java +++ b/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java @@ -67,8 +67,9 @@ public class AaiSecurityConfiguration extends WebSecurityConfigurerAdapter { .anyRequest().authenticated() .and() .logout().logoutUrl("/openid_logout") + .clearAuthentication(true) .invalidateHttpSession(true) - .deleteCookies("openAIRESession") + .deleteCookies() .logoutSuccessUrl(logoutSuccessUrl) .and() .addFilterBefore(openIdConnectAuthenticationFilter(), AbstractPreAuthenticatedProcessingFilter.class) diff --git a/src/main/java/eu/dnetlib/repo/manager/service/RepositoryServiceImpl.java b/src/main/java/eu/dnetlib/repo/manager/service/RepositoryServiceImpl.java index effafa5..65383f9 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/RepositoryServiceImpl.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/RepositoryServiceImpl.java @@ -28,6 +28,7 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.*; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @@ -38,6 +39,7 @@ import javax.annotation.PostConstruct; import java.sql.Timestamp; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; @Service("repositoryService") public class RepositoryServiceImpl implements RepositoryService { @@ -284,14 +286,14 @@ public class RepositoryServiceImpl implements RepositoryService { public List getRepositoriesOfUser(String page, String size) { logger.debug("Retrieving repositories of authenticated user : {}", ((OIDCAuthenticationToken) SecurityContextHolder.getContext().getAuthentication()).getUserInfo().getEmail()); - Collection repoIds = roleMappingService.getRepoIdsByRoleIds(authorizationService.getUserRoles()); + Collection repoIds = roleMappingService.getRepositoryIds(authorizationService.getUserRoles()); return getRepositories(new ArrayList<>(repoIds)); } @Override public List getRepositoriesOfUser(String userEmail, String page, String size) { logger.debug("Retrieving repositories of authenticated user : {}", userEmail); - Collection repoIds = roleMappingService.getRepoIdsByRoleIds(authorizationService.getUserRolesByEmail(userEmail)); + Collection repoIds = roleMappingService.getRepositoryIds(authorizationService.getUserRolesByEmail(userEmail)); return getRepositories(new ArrayList<>(repoIds)); } @@ -304,12 +306,7 @@ public class RepositoryServiceImpl implements RepositoryService { public List getRepositoriesSnippetsOfUser(String userEmail, String page, String size) { int from = Integer.parseInt(page) * Integer.parseInt(size); int to = from + Integer.parseInt(size); - List repoIds = new ArrayList<>(); - if (userEmail != null && !"".equals(userEmail)) { - repoIds.addAll(roleMappingService.getRepoIdsByRoleIds(authorizationService.getUserRolesByEmail(userEmail))); - } else { - repoIds.addAll(roleMappingService.getRepoIdsByRoleIds(authorizationService.getUserRoles())); - } + List repoIds = getRepoIdsOfUser(userEmail); if (repoIds.size() < from) { return Collections.emptyList(); @@ -534,8 +531,8 @@ public class RepositoryServiceImpl implements RepositoryService { emailUtils.sendUserRegisterInterfaceEmail(repo, comment, repositoryInterface, desiredCompatibilityLevel, authentication); String prevCompatibilityLevel = repositoryInterface.getCompatibility(); - if ( (desiredCompatibilityLevel != null) - && ((prevCompatibilityLevel == null) || ! prevCompatibilityLevel.equals(desiredCompatibilityLevel))) { + if ((desiredCompatibilityLevel != null) + && ((prevCompatibilityLevel == null) || !prevCompatibilityLevel.equals(desiredCompatibilityLevel))) { InterfaceComplianceRequest request = new InterfaceComplianceRequest(repoId, repositoryInterface.getId(), desiredCompatibilityLevel); interfaceComplianceService.create(request); } @@ -957,10 +954,27 @@ public class RepositoryServiceImpl implements RepositoryService { return repositories; } + private List getRepoIdsOfUser(String userEmail) { + List repoIds; + if (userEmail != null && !"".equals(userEmail)) { + repoIds = new ArrayList<>(roleMappingService.getRepositoryIds(authorizationService.getUserRolesByEmail(userEmail))); + } else { + Collection authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities(); + repoIds = authorities + .stream() + .map(a -> roleMappingService.authorityToRepositoryId((GrantedAuthority) a)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + return repoIds; + } + + @Deprecated private String getRepositoryType(String typology) { return invertedDataSourceClass.get(typology); } + @Deprecated private List getRoleIdsFromUserRoles(String userEmail) { List coPersonId = registryCalls.getCoPersonIdsByEmail(userEmail); JsonArray roles; diff --git a/src/main/java/eu/dnetlib/repo/manager/service/aai/registry/RegistryCalls.java b/src/main/java/eu/dnetlib/repo/manager/service/aai/registry/RegistryCalls.java index c7d4743..12d2cfc 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/aai/registry/RegistryCalls.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/aai/registry/RegistryCalls.java @@ -165,6 +165,7 @@ public class RegistryCalls implements AaiRegistryService { @Override public JsonArray getRoles(Integer coPersonId) { Map params = new HashMap<>(); + params.put("coid", coid); params.put("copersonid", coPersonId.toString()); JsonElement response = httpUtils.get("co_person_roles.json", params); return (response != null) ? response.getAsJsonObject().get("CoPersonRoles").getAsJsonArray() : new JsonArray(); diff --git a/src/main/java/eu/dnetlib/repo/manager/service/security/AaiRoleMappingService.java b/src/main/java/eu/dnetlib/repo/manager/service/security/AaiRoleMappingService.java index 96c91d6..b9de706 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/security/AaiRoleMappingService.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/security/AaiRoleMappingService.java @@ -7,6 +7,8 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Service; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; import java.net.URLEncoder; import java.util.Collection; import java.util.Objects; @@ -20,21 +22,8 @@ public class AaiRoleMappingService implements RoleMappingService { @Value("${services.provide.aai.registry.production:true}") private boolean production; - - private String createRepoRoleName(String prefix, String repoId) { - return prefix + "." + repoId.replace(":", "$"); - } - @Override - public String getRepoNameWithoutType(String fullName, String prefix) { - if (fullName != null && prefix != null && fullName.startsWith(prefix)) { - return fullName.substring(prefix.length()); - } - return null; - } - - @Override - public String getRepoIdByRoleId(String roleId) { + public String getRepositoryId(String roleId) { if (!roleActive(roleId)) { return null; } @@ -42,43 +31,46 @@ public class AaiRoleMappingService implements RoleMappingService { } @Override - public Collection getRepoIdsByRoleIds(Collection roleIds) { + public Collection getRepositoryIds(Collection roleIds) { return roleIds .stream() - //.filter(this::roleActive) // implicitly executed in the next statement - .map(this::getRepoIdByRoleId) + .map(this::getRepositoryId) .filter(Objects::nonNull) .collect(Collectors.toList()); } @Override - public String getRoleIdByRepoId(String repoId) { - String roleId = ""; + public String getRole(String repoId) { + String role = null; String prefix = (production ? "" : "beta.") + "datasource"; if (repoId != null) { - roleId = createRepoRoleName(prefix, repoId); - return roleId; - } else { - return null; + role = createRole(prefix, repoId); } - + return role; } @Override - public Collection getRoleIdsByRepoIds(Collection repoIds) { + public Collection getRoles(Collection repoIds) { return repoIds .stream() - .map(this::getRoleIdByRepoId) + .map(this::getRole) .filter(Objects::nonNull) .collect(Collectors.toList()); } @Override - public String convertAuthorityIdToRepoId(String authorityId) { - String repo = ""; - if (authorityId != null && roleActive(authorityId)) { - repo = authorityId - .replaceFirst(".*datasource\\.", "") + public String authorityToRepositoryId(GrantedAuthority authority) { + String repo = null; + String auth = null; + try { + auth = URLDecoder.decode(authority.getAuthority(), "UTF-8").toLowerCase(); + } catch (UnsupportedEncodingException e) { + logger.error("", e); + } + + if (auth != null && roleActive(auth)) { + repo = auth + .replaceFirst(".*datasource\\_", "") .replace("$", ":") .toLowerCase(); } @@ -86,12 +78,26 @@ public class AaiRoleMappingService implements RoleMappingService { } @Override - public String convertAuthorityToRepoId(GrantedAuthority authority) { - return convertAuthorityIdToRepoId(authority.toString()); + public GrantedAuthority repositoryIdToAuthority(String repoId) { + String role = null; + try { + role = URLEncoder.encode(convertRepoIdToAuthorityId(repoId), "UTF-8"); + } catch (UnsupportedEncodingException e) { + logger.error("", e); + } + return new SimpleGrantedAuthority(role); } - @Override - public String convertRepoIdToAuthorityId(String repoId) { + private String createRole(String prefix, String repoId) { + return prefix + "." + repoId.replace(":", "$"); + } + + private boolean roleActive(String roleId) { + return (production && !roleId.toLowerCase().startsWith("beta")) + || (!production && roleId.toLowerCase().startsWith("beta")); + } + + private String convertRepoIdToAuthorityId(String repoId) { StringBuilder roleBuilder = new StringBuilder(); String role = ""; if (repoId != null) { @@ -102,20 +108,4 @@ public class AaiRoleMappingService implements RoleMappingService { } return role; } - - @Override - public String convertRepoIdToEncodedAuthorityId(String repoId) { - return URLEncoder.encode(convertRepoIdToAuthorityId(repoId)); - } - - @Override - public SimpleGrantedAuthority convertRepoIdToAuthority(String repoId) { - String role = convertRepoIdToEncodedAuthorityId(repoId); - return new SimpleGrantedAuthority(role); - } - - private boolean roleActive(String roleId) { - return (production && !roleId.toLowerCase().startsWith("beta.")) - || (!production && roleId.toLowerCase().startsWith("beta.")); - } } diff --git a/src/main/java/eu/dnetlib/repo/manager/service/security/AuthorizationServiceImpl.java b/src/main/java/eu/dnetlib/repo/manager/service/security/AuthorizationServiceImpl.java index e1f66f9..7c43427 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/security/AuthorizationServiceImpl.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/security/AuthorizationServiceImpl.java @@ -56,9 +56,9 @@ public class AuthorizationServiceImpl implements AuthorizationService { @Override public boolean isMemberOf(String repoId) { - String repoRole = roleMappingService.convertRepoIdToEncodedAuthorityId(repoId); + String repoAuthority = roleMappingService.repositoryIdToAuthority(repoId).getAuthority(); return SecurityContextHolder.getContext().getAuthentication().getAuthorities() - .stream().anyMatch(authority -> authority.toString().equals(repoRole)); + .stream().anyMatch(authority -> authority.toString().equals(repoAuthority)); } @Override @@ -74,7 +74,7 @@ public class AuthorizationServiceImpl implements AuthorizationService { public List getAdminsOfRepo(String repoId) { // find couId by role name - String role = roleMappingService.getRoleIdByRepoId(repoId); + String role = roleMappingService.getRole(repoId); Integer couId = aaiRegistryService.getCouId(role); return aaiRegistryService.getUsers(couId); } @@ -82,7 +82,7 @@ public class AuthorizationServiceImpl implements AuthorizationService { @Override public void addAdmin(String resourceId, String email) throws ResourceNotFoundException { - String role = roleMappingService.getRoleIdByRepoId(resourceId); + String role = roleMappingService.getRole(resourceId); Integer couId = aaiRegistryService.getCouId(role); if (couId == null) { throw new ResourceNotFoundException("Cannot find CouId for role: " + role); @@ -94,14 +94,14 @@ public class AuthorizationServiceImpl implements AuthorizationService { // Add role to user current authorities for (String userId : aaiRegistryService.getUserIdentifiersByEmail(email)) { - authoritiesUpdater.addRole(userId, roleMappingService.convertRepoIdToAuthority(resourceId)); + authoritiesUpdater.addRole(userId, roleMappingService.repositoryIdToAuthority(resourceId)); } } } @Override public void removeAdmin(String resourceId, String email) throws ResourceNotFoundException { - String role = roleMappingService.getRoleIdByRepoId(resourceId); + String role = roleMappingService.getRole(resourceId); Integer couId = aaiRegistryService.getCouId(role); if (couId == null) { throw new ResourceNotFoundException("Cannot find CouId for role: " + role); @@ -115,7 +115,7 @@ public class AuthorizationServiceImpl implements AuthorizationService { // Remove role from user current authorities for (String userId : aaiRegistryService.getUserIdentifiersByEmail(email)) { - authoritiesUpdater.removeRole(userId, roleMappingService.convertRepoIdToAuthority(resourceId)); + authoritiesUpdater.removeRole(userId, roleMappingService.repositoryIdToAuthority(resourceId)); } } else { logger.error("Cannot find RoleId for role: {}", role); @@ -126,7 +126,7 @@ public class AuthorizationServiceImpl implements AuthorizationService { @Override public void createAndAssignRoleToAuthenticatedUser(String resourceId, String roleDescription) { // Create new role - String newRoleName = roleMappingService.getRoleIdByRepoId(resourceId); + String newRoleName = roleMappingService.getRole(resourceId); Role newRole = new Role(newRoleName, roleDescription); Integer couId; @@ -148,7 +148,7 @@ public class AuthorizationServiceImpl implements AuthorizationService { aaiRegistryService.assignMemberRole(coPersonId, couId); // Add role to current user authorities - authoritiesUpdater.addRole(roleMappingService.convertRepoIdToAuthority(resourceId)); + authoritiesUpdater.addRole(roleMappingService.repositoryIdToAuthority(resourceId)); } } diff --git a/src/main/java/eu/dnetlib/repo/manager/service/security/RoleMappingService.java b/src/main/java/eu/dnetlib/repo/manager/service/security/RoleMappingService.java index 67439bf..c1a77c4 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/security/RoleMappingService.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/security/RoleMappingService.java @@ -7,68 +7,41 @@ import java.util.Collection; public interface RoleMappingService { - /** - * @param fullName - * @param prefix - * @return - */ - String getRepoNameWithoutType(String fullName, String prefix); - /** * @param roleId Role Id * @return Converts {@param roleId} to a repo Id. */ - String getRepoIdByRoleId(String roleId); + String getRepositoryId(String roleId); /** - * * @param roleIds Collection of roles * @return Converts {@param roleIds} to a repo Ids. */ - Collection getRepoIdsByRoleIds(Collection roleIds); + Collection getRepositoryIds(Collection roleIds); /** * @param repoId Repository Id * @return Converts {@param repoId} to a role Id. */ - String getRoleIdByRepoId(String repoId); + String getRole(String repoId); /** * @param repoIds Collection of Repository Ids * @return Converts {@param repoIds} to role Ids. */ - Collection getRoleIdsByRepoIds(Collection repoIds); + Collection getRoles(Collection repoIds); /** - * @param authorityId Authority Id - * @return Converts {@param authorityId} to repo Id. + * @param authority {@link GrantedAuthority} + * @return Converts {@param authority} to repository Id. */ - String convertAuthorityIdToRepoId(String authorityId); - - /** - * @param authority Granted authority - * @return Converts {@param authority} to repo Id. - */ - String convertAuthorityToRepoId(GrantedAuthority authority); - - /** - * @param repoId Repository Id - * @return - */ - String convertRepoIdToAuthorityId(String repoId); - - /** - * @param repoId Repository Id - * @return Converts {@param repoId} to {@link String} role id url encoded ($ -> %24) - * // TODO: remove role encoding and perform url decoding when mapping authorities. (Must be performed in all OpenAIRE projects because of Redis) - */ - String convertRepoIdToEncodedAuthorityId(String repoId); + String authorityToRepositoryId(GrantedAuthority authority); /** * @param repoId Repository Id * @return Converts {@param repoId} to {@link SimpleGrantedAuthority} with the role url encoded ($ -> %24) * // TODO: remove role encoding and perform url decoding when mapping authorities. (Must be performed in all OpenAIRE projects because of Redis) */ - SimpleGrantedAuthority convertRepoIdToAuthority(String repoId); + GrantedAuthority repositoryIdToAuthority(String repoId); } From 0ad86025b3c73dd4a457b8f4ed53727d2abafcc2 Mon Sep 17 00:00:00 2001 From: Konstantinos Spyrou Date: Fri, 17 Feb 2023 17:35:18 +0200 Subject: [PATCH 05/30] throw error when assigning role to a user who is not found in the aai registry --- .../manager/service/security/AuthorizationServiceImpl.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/eu/dnetlib/repo/manager/service/security/AuthorizationServiceImpl.java b/src/main/java/eu/dnetlib/repo/manager/service/security/AuthorizationServiceImpl.java index 7c43427..d5d8f03 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/security/AuthorizationServiceImpl.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/security/AuthorizationServiceImpl.java @@ -88,6 +88,9 @@ public class AuthorizationServiceImpl implements AuthorizationService { throw new ResourceNotFoundException("Cannot find CouId for role: " + role); } List coPersonIds = aaiRegistryService.getCoPersonIdsByEmail(email); + if (coPersonIds.isEmpty()) { + throw new ResourceNotFoundException("User with email '%s' could not be found.."); + } for (Integer coPersonId : coPersonIds) { assert coPersonId != null; aaiRegistryService.assignMemberRole(coPersonId, couId); From e9fedc90d45394673a0503f8a4348587d8be3bea Mon Sep 17 00:00:00 2001 From: Konstantinos Spyrou Date: Fri, 17 Feb 2023 20:40:51 +0200 Subject: [PATCH 06/30] experimental: temp save role if assign fails --- .../repo/manager/domain/PendingUserRole.java | 53 +++++++++++++++++++ .../repository/PendingUserRoleRepository.java | 9 ++++ .../service/PendingUserRoleService.java | 38 +++++++++++++ .../security/AuthorizationServiceImpl.java | 14 ++++- 4 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 src/main/java/eu/dnetlib/repo/manager/domain/PendingUserRole.java create mode 100644 src/main/java/eu/dnetlib/repo/manager/repository/PendingUserRoleRepository.java create mode 100644 src/main/java/eu/dnetlib/repo/manager/service/PendingUserRoleService.java diff --git a/src/main/java/eu/dnetlib/repo/manager/domain/PendingUserRole.java b/src/main/java/eu/dnetlib/repo/manager/domain/PendingUserRole.java new file mode 100644 index 0000000..65dd507 --- /dev/null +++ b/src/main/java/eu/dnetlib/repo/manager/domain/PendingUserRole.java @@ -0,0 +1,53 @@ +package eu.dnetlib.repo.manager.domain; + +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +public class PendingUserRole { + @Id + long id; + int coPersonId; + int couId; + + public PendingUserRole() { + // no-arg constructor + } + + public PendingUserRole(int coPersonId, int couId) { + this.coPersonId = coPersonId; + this.couId = couId; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public int getCoPersonId() { + return coPersonId; + } + + public void setCoPersonId(int coPersonId) { + this.coPersonId = coPersonId; + } + + public int getCouId() { + return couId; + } + + public void setCouId(int couId) { + this.couId = couId; + } + + @Override + public String toString() { + return "PendingUserRole{" + + "coPersonId=" + coPersonId + + ", couId=" + couId + + '}'; + } +} diff --git a/src/main/java/eu/dnetlib/repo/manager/repository/PendingUserRoleRepository.java b/src/main/java/eu/dnetlib/repo/manager/repository/PendingUserRoleRepository.java new file mode 100644 index 0000000..6f1247a --- /dev/null +++ b/src/main/java/eu/dnetlib/repo/manager/repository/PendingUserRoleRepository.java @@ -0,0 +1,9 @@ +package eu.dnetlib.repo.manager.repository; + +import eu.dnetlib.repo.manager.domain.PendingUserRole; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PendingUserRoleRepository extends CrudRepository { +} diff --git a/src/main/java/eu/dnetlib/repo/manager/service/PendingUserRoleService.java b/src/main/java/eu/dnetlib/repo/manager/service/PendingUserRoleService.java new file mode 100644 index 0000000..4b907c1 --- /dev/null +++ b/src/main/java/eu/dnetlib/repo/manager/service/PendingUserRoleService.java @@ -0,0 +1,38 @@ +package eu.dnetlib.repo.manager.service; + +import eu.dnetlib.repo.manager.domain.PendingUserRole; +import eu.dnetlib.repo.manager.repository.PendingUserRoleRepository; +import eu.dnetlib.repo.manager.service.aai.registry.AaiRegistryService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +@Service +public class PendingUserRoleService { + + private static final Logger logger = LoggerFactory.getLogger(PendingUserRoleService.class); + private final PendingUserRoleRepository pendingUserRoleRepository; + private final AaiRegistryService aaiRegistryService; + + public PendingUserRoleService(PendingUserRoleRepository pendingUserRoleRepository, + AaiRegistryService aaiRegistryService) { + this.pendingUserRoleRepository = pendingUserRoleRepository; + this.aaiRegistryService = aaiRegistryService; + } + + @Scheduled(fixedRate = 3_600_000) + public void assignRoles() { + Iterable roles = pendingUserRoleRepository.findAll(); + for (PendingUserRole role : roles) { + logger.debug("Attempt to assign role: {}", role); + try { + aaiRegistryService.assignMemberRole(role.getCoPersonId(), role.getCouId()); + pendingUserRoleRepository.deleteById(role.getId()); + } catch (Exception e) { + logger.warn("Could not assign role to user. Pending Role: {}\n", role, e); + } + } + } + +} diff --git a/src/main/java/eu/dnetlib/repo/manager/service/security/AuthorizationServiceImpl.java b/src/main/java/eu/dnetlib/repo/manager/service/security/AuthorizationServiceImpl.java index d5d8f03..8305236 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/security/AuthorizationServiceImpl.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/security/AuthorizationServiceImpl.java @@ -1,9 +1,11 @@ package eu.dnetlib.repo.manager.service.security; import com.google.gson.JsonElement; +import eu.dnetlib.repo.manager.domain.PendingUserRole; import eu.dnetlib.repo.manager.domain.dto.Role; import eu.dnetlib.repo.manager.domain.dto.User; import eu.dnetlib.repo.manager.exception.ResourceNotFoundException; +import eu.dnetlib.repo.manager.repository.PendingUserRoleRepository; import eu.dnetlib.repo.manager.service.aai.registry.AaiRegistryService; import org.mitre.openid.connect.model.OIDCAuthenticationToken; import org.mitre.openid.connect.model.UserInfo; @@ -30,13 +32,16 @@ public class AuthorizationServiceImpl implements AuthorizationService { private final RoleMappingService roleMappingService; private final AaiRegistryService aaiRegistryService; private final AuthoritiesUpdater authoritiesUpdater; + private final PendingUserRoleRepository pendingUserRoleRepository; @Autowired AuthorizationServiceImpl(RoleMappingService roleMappingService, AaiRegistryService aaiRegistryService, - AuthoritiesUpdater authoritiesUpdater) { + AuthoritiesUpdater authoritiesUpdater, + PendingUserRoleRepository pendingUserRoleRepository) { this.roleMappingService = roleMappingService; this.aaiRegistryService = aaiRegistryService; this.authoritiesUpdater = authoritiesUpdater; + this.pendingUserRoleRepository = pendingUserRoleRepository; } private String mapType(String type) { @@ -148,7 +153,12 @@ public class AuthorizationServiceImpl implements AuthorizationService { // Assign new role to the current authenticated user Integer coPersonId = aaiRegistryService.getCoPersonIdByIdentifier(); if (couId != null) { - aaiRegistryService.assignMemberRole(coPersonId, couId); + + try { + aaiRegistryService.assignMemberRole(coPersonId, couId); + } catch (Exception e) { + pendingUserRoleRepository.save(new PendingUserRole(coPersonId, couId)); + } // Add role to current user authorities authoritiesUpdater.addRole(roleMappingService.repositoryIdToAuthority(resourceId)); From 59fc344730e75829e193f9177e9c31aa1c000d1b Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Thu, 2 Mar 2023 16:15:54 +0200 Subject: [PATCH 07/30] - Use the production AAI url, as the "beta" one has issues. - Polish the application.yml --- src/main/resources/application.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 93e95cb..f48c834 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -14,11 +14,11 @@ spring: driverClassName: ${services.provide.db.driverClassName} services: - openaireServicesBaseUrl: https://beta.services.openaire.eu + openaireServicesBaseUrl: https://beta.services.openaire.eu # dev-openaire.d4science.org provide: dev-machine: 88.197.53.71 # VM-71 aai: - baseURL: https://beta.aai.openaire.eu + baseURL: https://aai.openaire.eu oidc: domain: .openaire.eu # use empty value for local, otherwise: ".openaire.eu" id: XX @@ -34,14 +34,14 @@ services: url: ${services.provide.aai.baseURL}/registry/ adminEmail: XX analyticsURL: https://analytics.openaire.eu/addsite.php? - baseUrl: https://dev-openaire.d4science.org/openaire + baseUrl: ${services.openaireServicesBaseUrl}/openaire broker: api: api/ openaire: openaireBroker port: 443 url: https://beta.broker.openaire.eu clients: - dsm: https://dev-openaire.d4science.org/openaire + dsm: ${services.provide.baseUrl} search: ${services.openaireServicesBaseUrl}/search/v2/api usageEvents: http://beta.lbs.openaire.eu:8080/ajax/summary usagestats: ${services.openaireServicesBaseUrl}/usagestats @@ -67,7 +67,8 @@ services: password: XX port: 6379 topic_types: - url: ${services.openaireServicesBaseUrl}/provision/mvc/vocabularies/dnet:topic_types.json + url: ${services.openaireServicesBaseUrl}/provision/mvc/vocabularies/dnet:topic_types.json # TODO - Check this! The requested json file does not exist in the DEV-url below) + # https://dev-openaire.d4science.org/provision/mvc/vocabularies/ usageStatisticsDiagramsBaseURL: https://beta.openaire.eu/stats3/ usageStatisticsNumbersBaseURL: ${services.openaireServicesBaseUrl}/usagestats/datasources/ usagestats: From a0fd5f67a7eba744d963ba2b2ca26658b72c04b9 Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Wed, 8 Mar 2023 13:57:29 +0200 Subject: [PATCH 08/30] Fix the "NoSuchBeanDefinitionException: No bean named 'transactionManager' available", when trying to run the scheduled task -during initialization- for assigning pending user roles. --- .../eu/dnetlib/repo/manager/Application.java | 26 ++++++++++--------- .../manager/components/ScheduledTasks.java | 20 ++++++++++++++ .../service/PendingUserRoleService.java | 2 -- 3 files changed, 34 insertions(+), 14 deletions(-) create mode 100644 src/main/java/eu/dnetlib/repo/manager/components/ScheduledTasks.java diff --git a/src/main/java/eu/dnetlib/repo/manager/Application.java b/src/main/java/eu/dnetlib/repo/manager/Application.java index 0eeb9c8..b934bfd 100644 --- a/src/main/java/eu/dnetlib/repo/manager/Application.java +++ b/src/main/java/eu/dnetlib/repo/manager/Application.java @@ -1,12 +1,14 @@ -package eu.dnetlib.repo.manager; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class Application { - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } -} +package eu.dnetlib.repo.manager; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; + +@SpringBootApplication +@EnableScheduling +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/src/main/java/eu/dnetlib/repo/manager/components/ScheduledTasks.java b/src/main/java/eu/dnetlib/repo/manager/components/ScheduledTasks.java new file mode 100644 index 0000000..c4688c1 --- /dev/null +++ b/src/main/java/eu/dnetlib/repo/manager/components/ScheduledTasks.java @@ -0,0 +1,20 @@ +package eu.dnetlib.repo.manager.components; + + +import eu.dnetlib.repo.manager.service.PendingUserRoleService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Component +public class ScheduledTasks { + + @Autowired + PendingUserRoleService pendingUserRoleService; + + @Scheduled(fixedRate = 3_600_000) + public void assignPendingRoles() { + pendingUserRoleService.assignRoles(); + } + +} diff --git a/src/main/java/eu/dnetlib/repo/manager/service/PendingUserRoleService.java b/src/main/java/eu/dnetlib/repo/manager/service/PendingUserRoleService.java index 4b907c1..cf7a99a 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/PendingUserRoleService.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/PendingUserRoleService.java @@ -5,7 +5,6 @@ import eu.dnetlib.repo.manager.repository.PendingUserRoleRepository; import eu.dnetlib.repo.manager.service.aai.registry.AaiRegistryService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @Service @@ -21,7 +20,6 @@ public class PendingUserRoleService { this.aaiRegistryService = aaiRegistryService; } - @Scheduled(fixedRate = 3_600_000) public void assignRoles() { Iterable roles = pendingUserRoleRepository.findAll(); for (PendingUserRole role : roles) { From d2973871a83473bf2004fdbe11f421c1c9fc0540 Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Wed, 8 Mar 2023 15:26:26 +0200 Subject: [PATCH 09/30] Inform the developer about the possibility of a "RestClientException" being thrown by various methods of "HttpUtils" and "RegistryCalls" classes. --- .../service/aai/registry/RegistryCalls.java | 43 ++++++++++--------- .../service/aai/registry/utils/HttpUtils.java | 9 ++-- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/main/java/eu/dnetlib/repo/manager/service/aai/registry/RegistryCalls.java b/src/main/java/eu/dnetlib/repo/manager/service/aai/registry/RegistryCalls.java index 12d2cfc..224267d 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/aai/registry/RegistryCalls.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/aai/registry/RegistryCalls.java @@ -15,6 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClientException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; @@ -55,7 +56,7 @@ public class RegistryCalls implements AaiRegistryService { } @Override - public List getUserIdentifiersByCoPersonId(Integer coPersonId) { + public List getUserIdentifiersByCoPersonId(Integer coPersonId) throws RestClientException { List ids = new ArrayList<>(); Map params = new HashMap<>(); params.put("copersonid", coPersonId.toString()); @@ -86,7 +87,7 @@ public class RegistryCalls implements AaiRegistryService { } @Override - public List getCoPersonIdsByEmail(String email) { + public List getCoPersonIdsByEmail(String email) throws RestClientException { List coPersonIds = new ArrayList<>(); Map params = new HashMap<>(); params.put("mail", email); @@ -113,7 +114,7 @@ public class RegistryCalls implements AaiRegistryService { } } - public Integer getCoPersonIdByIdentifier(String sub) { + public Integer getCoPersonIdByIdentifier(String sub) throws RestClientException { Map params = new HashMap<>(); params.put("search.identifier", sub); params.put("coid", coid); @@ -122,7 +123,7 @@ public class RegistryCalls implements AaiRegistryService { } @Override - public JsonArray getCous(String name) { + public JsonArray getCous(String name) throws RestClientException { Map params = new HashMap<>(); if (name != null) { try { @@ -163,7 +164,7 @@ public class RegistryCalls implements AaiRegistryService { } @Override - public JsonArray getRoles(Integer coPersonId) { + public JsonArray getRoles(Integer coPersonId) throws RestClientException { Map params = new HashMap<>(); params.put("coid", coid); params.put("copersonid", coPersonId.toString()); @@ -204,7 +205,7 @@ public class RegistryCalls implements AaiRegistryService { } @Override - public JsonArray getUserGroups(Integer coPersonId) { + public JsonArray getUserGroups(Integer coPersonId) throws RestClientException { Map params = new HashMap<>(); params.put("copersonid", coPersonId.toString()); JsonElement response = httpUtils.get("co_groups.json", params); @@ -212,7 +213,7 @@ public class RegistryCalls implements AaiRegistryService { } @Override - public JsonObject getUserAdminGroup(Integer coPersonId, Integer couId) { + public JsonObject getUserAdminGroup(Integer coPersonId, Integer couId) throws RestClientException { Map params = new HashMap<>(); params.put("copersonid", coPersonId.toString()); JsonElement response = httpUtils.get("co_groups.json", params); @@ -229,7 +230,7 @@ public class RegistryCalls implements AaiRegistryService { } @Override - public JsonArray getCouGroups(Integer couId) { + public JsonArray getCouGroups(Integer couId) throws RestClientException { Map params = new HashMap<>(); params.put("couid", couId.toString()); JsonElement response = httpUtils.get("co_groups.json", params); @@ -248,7 +249,7 @@ public class RegistryCalls implements AaiRegistryService { } @Override - public JsonArray getGroupMembers(Integer coGroupId) { + public JsonArray getGroupMembers(Integer coGroupId) throws RestClientException { Map params = new HashMap<>(); params.put("cogroupid", coGroupId.toString()); JsonElement response = httpUtils.get("co_group_members.json", params); @@ -257,7 +258,7 @@ public class RegistryCalls implements AaiRegistryService { @Override - public JsonArray getUserEmailByCouId(Integer couId, boolean admin) { + public JsonArray getUserEmailByCouId(Integer couId, boolean admin) throws RestClientException { Map params = new HashMap<>(); if (couId == null) { throw new IllegalArgumentException("Provided 'couId' is null"); @@ -288,7 +289,7 @@ public class RegistryCalls implements AaiRegistryService { } @Override - public JsonArray getUsersByCouId(Integer couId) { + public JsonArray getUsersByCouId(Integer couId) throws RestClientException { Map params = new HashMap<>(); params.put("couid", couId.toString()); JsonElement response = httpUtils.get("co_person_roles.json", params); @@ -321,7 +322,7 @@ public class RegistryCalls implements AaiRegistryService { } @Override - public JsonArray getUserNamesByCouId(Integer couId, boolean admin) { + public JsonArray getUserNamesByCouId(Integer couId, boolean admin) throws RestClientException { Map params = new HashMap<>(); params.put("couid", couId.toString()); if (admin) { @@ -341,7 +342,7 @@ public class RegistryCalls implements AaiRegistryService { } @Override - public JsonArray getUserIdByCouId(Integer couId, boolean admin) { + public JsonArray getUserIdByCouId(Integer couId, boolean admin) throws RestClientException { Map params = new HashMap<>(); params.put("couid", couId.toString()); if (admin) { @@ -361,26 +362,26 @@ public class RegistryCalls implements AaiRegistryService { } @Override - public void assignMemberRole(Integer coPersonId, Integer couId) { + public void assignMemberRole(Integer coPersonId, Integer couId) throws RestClientException { httpUtils.post("co_person_roles.json", jsonUtils.coPersonRoles(coPersonId, couId, "Active")); } @Override - public void removeMemberRole(Integer coPersonId, Integer couId, Integer id) { + public void removeMemberRole(Integer coPersonId, Integer couId, Integer id) throws RestClientException { if (id != null) { httpUtils.put("co_person_roles/" + id + ".json", jsonUtils.coPersonRoles(coPersonId, couId, "Deleted")); } } @Override - public Integer createRole(Role role) { + public Integer createRole(Role role) throws RestClientException { JsonElement element = httpUtils.post("cous.json", jsonUtils.createNewCou(role)); return element.getAsJsonObject().get("Id").getAsInt(); } @Override - public String getUserEmail(Integer coPersonId) { + public String getUserEmail(Integer coPersonId) throws RestClientException { Map params = new HashMap<>(); params.put("copersonid", coPersonId.toString()); JsonElement response = httpUtils.get("email_addresses.json", params); @@ -389,7 +390,7 @@ public class RegistryCalls implements AaiRegistryService { } @Override - public String getUserNames(Integer coPersonId) { + public String getUserNames(Integer coPersonId) throws RestClientException { Map params = new HashMap<>(); params.put("copersonid", coPersonId.toString()); JsonElement response = httpUtils.get("names.json", params); @@ -402,7 +403,7 @@ public class RegistryCalls implements AaiRegistryService { } @Override - public String getUserId(Integer coPersonId) { + public String getUserId(Integer coPersonId) throws RestClientException { Map params = new HashMap<>(); params.put("copersonid", coPersonId.toString()); JsonElement response = httpUtils.get("identifiers.json", params); @@ -411,7 +412,7 @@ public class RegistryCalls implements AaiRegistryService { } @Override - public void assignAdminRole(Integer coPersonId, Integer couId) { + public void assignAdminRole(Integer coPersonId, Integer couId) throws RestClientException { JsonObject group = getCouAdminGroup(couId); if (group != null) { httpUtils.post("co_group_members.json", jsonUtils.coGroupMembers(group.get("Id").getAsInt(), coPersonId, true)); @@ -419,7 +420,7 @@ public class RegistryCalls implements AaiRegistryService { } @Override - public void removeAdminRole(Integer coPersonId, Integer couId) { + public void removeAdminRole(Integer coPersonId, Integer couId) throws RestClientException { JsonObject adminGroup = this.getCouAdminGroup(couId); JsonArray admins = this.getGroupMembers(adminGroup.get("Id").getAsInt()); Integer id = null; diff --git a/src/main/java/eu/dnetlib/repo/manager/service/aai/registry/utils/HttpUtils.java b/src/main/java/eu/dnetlib/repo/manager/service/aai/registry/utils/HttpUtils.java index b294717..07b2f72 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/aai/registry/utils/HttpUtils.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/aai/registry/utils/HttpUtils.java @@ -10,6 +10,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; +import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; @@ -32,7 +33,7 @@ public class HttpUtils { @Value("${services.provide.aai.registry.password}") private String password; - public JsonElement post(String path, JsonObject body) { + public JsonElement post(String path, JsonObject body) throws RestClientException { RestTemplate restTemplate = new RestTemplate(); HttpHeaders headers = createHeaders(user, password); headers.setContentType(MediaType.APPLICATION_JSON); @@ -41,7 +42,7 @@ public class HttpUtils { return getResponseEntityAsJsonElement(responseEntity); } - public JsonElement put(String path, JsonObject body) { + public JsonElement put(String path, JsonObject body) throws RestClientException { RestTemplate restTemplate = new RestTemplate(); HttpHeaders headers = createHeaders(user, password); headers.setContentType(MediaType.APPLICATION_JSON); @@ -50,7 +51,7 @@ public class HttpUtils { return getResponseEntityAsJsonElement(responseEntity); } - public JsonElement get(String path, Map params) { + public JsonElement get(String path, Map params) throws RestClientException { RestTemplate restTemplate = new RestTemplate(); String url = createUrl(registryUrl + path, params); ResponseEntity responseEntity = restTemplate.exchange @@ -58,7 +59,7 @@ public class HttpUtils { return getResponseEntityAsJsonElement(responseEntity); } - public JsonElement delete(String path) { + public JsonElement delete(String path) throws RestClientException { RestTemplate restTemplate = new RestTemplate(); String url = registryUrl + path; ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.DELETE, new HttpEntity<>(createHeaders(user, password)), String.class); From 7cab17c133c7f0a70b051f14bd04a0dc93b149f8 Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Wed, 22 Mar 2023 13:35:20 +0200 Subject: [PATCH 10/30] Move Sushilite Service in its own package. Later, additional Sushilite Services will be added. --- .../dnetlib/repo/manager/controllers/SushiliteController.java | 2 +- .../repo/manager/service/{ => sushilite}/SushiliteService.java | 2 +- .../manager/service/{ => sushilite}/SushiliteServiceImpl.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/main/java/eu/dnetlib/repo/manager/service/{ => sushilite}/SushiliteService.java (94%) rename src/main/java/eu/dnetlib/repo/manager/service/{ => sushilite}/SushiliteServiceImpl.java (99%) diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteController.java b/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteController.java index 7d7aa4e..7c3b292 100644 --- a/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteController.java +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteController.java @@ -1,6 +1,6 @@ package eu.dnetlib.repo.manager.controllers; -import eu.dnetlib.repo.manager.service.SushiliteServiceImpl; +import eu.dnetlib.repo.manager.service.sushilite.SushiliteServiceImpl; import eu.dnetlib.usagestats.sushilite.domain.ReportResponseWrapper; import io.swagger.annotations.Api; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/main/java/eu/dnetlib/repo/manager/service/SushiliteService.java b/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteService.java similarity index 94% rename from src/main/java/eu/dnetlib/repo/manager/service/SushiliteService.java rename to src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteService.java index 15ea811..fe247ff 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/SushiliteService.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteService.java @@ -1,4 +1,4 @@ -package eu.dnetlib.repo.manager.service; +package eu.dnetlib.repo.manager.service.sushilite; import eu.dnetlib.usagestats.sushilite.domain.ReportResponseWrapper; import org.json.JSONException; diff --git a/src/main/java/eu/dnetlib/repo/manager/service/SushiliteServiceImpl.java b/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteServiceImpl.java similarity index 99% rename from src/main/java/eu/dnetlib/repo/manager/service/SushiliteServiceImpl.java rename to src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteServiceImpl.java index ba41dda..bd00c7c 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/SushiliteServiceImpl.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteServiceImpl.java @@ -1,4 +1,4 @@ -package eu.dnetlib.repo.manager.service; +package eu.dnetlib.repo.manager.service.sushilite; import eu.dnetlib.usagestats.sushilite.domain.Customer; import eu.dnetlib.usagestats.sushilite.domain.ReportItem; From 97e85c2a75736785e6aef96146937aa0ff528c9f Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Tue, 28 Mar 2023 15:08:20 +0300 Subject: [PATCH 11/30] Add suchilite-R5 support. --- ...penaire-usage-stats-sushilite-r5-1.0.0.jar | Bin 0 -> 53323 bytes pom.xml | 11 ++ .../controllers/SushiliteR5Controller.java | 54 +++++++ .../service/sushilite/SushiliteR5Service.java | 28 ++++ .../sushilite/SushiliteR5ServiceImpl.java | 132 ++++++++++++++++++ 5 files changed, 225 insertions(+) create mode 100644 libs/openaire-usage-stats-sushilite-r5-1.0.0.jar create mode 100644 src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteR5Controller.java create mode 100644 src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteR5Service.java create mode 100644 src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteR5ServiceImpl.java diff --git a/libs/openaire-usage-stats-sushilite-r5-1.0.0.jar b/libs/openaire-usage-stats-sushilite-r5-1.0.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..d4d094bdeaa1684856a87ca78e4a66900d1eae70 GIT binary patch literal 53323 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

Y{IkMz!@L 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 BDWCuV literal 0 HcmV?d00001 diff --git a/pom.xml b/pom.xml index 6e4c908..23f28eb 100644 --- a/pom.xml +++ b/pom.xml @@ -118,6 +118,13 @@ + + eu.dnetlib + dnet-openaire-usage-stats-sushilite-r5 + 1.0.0 + system + ${project.basedir}/libs/openaire-usage-stats-sushilite-r5-1.0.0.jar + eu.dnetlib dnet-openaire-usage-stats-sushilite @@ -421,6 +428,10 @@ false + + libs + file:///${project.basedir}/libs + diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteR5Controller.java b/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteR5Controller.java new file mode 100644 index 0000000..e57ff35 --- /dev/null +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteR5Controller.java @@ -0,0 +1,54 @@ +package eu.dnetlib.repo.manager.controllers; + +import eu.dnetlib.repo.manager.service.sushilite.SushiliteR5Service; +import eu.dnetlib.repo.manager.service.sushilite.SushiliteR5ServiceImpl; +import io.swagger.annotations.Api; +import org.json.JSONException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + + +@RestController +@RequestMapping(value = "/sushiliteR5") +@Api(description = "Sushi-Lite R5 API", tags = {"sushiliteR5"}) +public class SushiliteR5Controller { + + + private static final Logger logger = LoggerFactory.getLogger(SushiliteR5Controller.class); + + + @Autowired + private SushiliteR5ServiceImpl sushiliteR5Service; + + @RequestMapping(value = "/getReportResults", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + @PreAuthorize("hasAuthority('REGISTERED_USER')") + public ResponseEntity getReportResults(@RequestParam(value = "Report") String report, + @RequestParam(value = "Release", required=false, defaultValue="5") String release, + @RequestParam(value = "RequestorID",required=false, defaultValue="anonymous") String requestorID, + @RequestParam(value = "BeginDate",required=false, defaultValue="") String beginDate, + @RequestParam(value = "EndDate",required=false, defaultValue="") String endDate, + @RequestParam(value = "RepositoryIdentifier", required=false, defaultValue="") String repositoryIdentifier, + @RequestParam(value = "DatasetIdentifier", required=false, defaultValue="") String datasetIdentifier, + @RequestParam(value = "ItemIdentifier",required=false, defaultValue="") String itemIdentifier, + @RequestParam(value = "MetricType",required=false) List metricTypes, + @RequestParam(value = "dataType",required=false, defaultValue="") String dataType, + @RequestParam(value = "Granularity", required = false, defaultValue ="Monthly") String granularity, + @RequestParam(value = "Pretty",required=false, defaultValue="") String pretty) { + + try { + return sushiliteR5Service.getReportResults(report, release, requestorID, beginDate, endDate, repositoryIdentifier, datasetIdentifier, itemIdentifier, metricTypes, dataType, granularity, pretty); + } catch (JSONException je) { + logger.error("", je); + return ResponseEntity.internalServerError().build(); + } + } + +} diff --git a/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteR5Service.java b/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteR5Service.java new file mode 100644 index 0000000..44a4c80 --- /dev/null +++ b/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteR5Service.java @@ -0,0 +1,28 @@ +package eu.dnetlib.repo.manager.service.sushilite; + +import eu.dnetlib.usagestats.sushilite.domain.ReportResponseWrapper; +import org.json.JSONException; +import org.springframework.http.ResponseEntity; + +import java.util.List; + +public interface SushiliteR5Service { + + + ResponseEntity getReportResults(String Report, + String release, + String requestorID, + String beginDate, + String endDate, + String repositoryIdentifier, + String datasetIdentifier, + String itemIdentifier, + List metricTypes, + String dataType, + String granularity, + String pretty) throws JSONException; + + + + +} diff --git a/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteR5ServiceImpl.java b/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteR5ServiceImpl.java new file mode 100644 index 0000000..223d016 --- /dev/null +++ b/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteR5ServiceImpl.java @@ -0,0 +1,132 @@ +package eu.dnetlib.repo.manager.service.sushilite; + +//import eu.dnetlib.usagestats.sushilite.domain.COUNTER_Item_Report; +import org.json.JSONException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.net.URI; +import java.util.List; + +@Service("sushiliteR5Service") +public class SushiliteR5ServiceImpl implements SushiliteR5Service { + + private static final Logger logger = LoggerFactory.getLogger(SushiliteR5ServiceImpl.class); + + + @Value("${services.provide.usagestats.sushiliteR5Endpoint}") + private String usagestatsSushiliteR5Endpoint; + + + public ResponseEntity getReportResults(String report, + String release, + String requestorID, + String beginDate, + String endDate, + String repositoryIdentifier, + String datasetIdentifier, + String itemIdentifier, + List metricTypes, + String dataType, + String granularity, + String pretty) throws JSONException + { + //build the uri params + UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(this.usagestatsSushiliteR5Endpoint + "reports/" + report.toLowerCase()) + .queryParam("Report", report) + .queryParam("Release", release) + .queryParam("RequestorID", requestorID) + .queryParam("BeginDate", beginDate) + .queryParam("EndDate", endDate) + .queryParam("RepositoryIdentifier", repositoryIdentifier) + .queryParam("DatasetIdentifier", datasetIdentifier) + .queryParam("ItemIdentifier", itemIdentifier); + if ( metricTypes != null ) { + for ( String metricType : metricTypes ) + builder.queryParam("MetricType", metricType); + } + builder.queryParam("DataType", dataType) + .queryParam("Granularity", granularity) + .queryParam("Pretty", pretty); + + URI uri = builder.build().encode().toUri(); + + RestTemplate restTemplate = new RestTemplate(); + restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter()); + + ResponseEntity resp; + try { + resp = restTemplate.exchange( + uri, + HttpMethod.GET, + null, + new ParameterizedTypeReference() {}); + } catch (RestClientException rce) { + String errorMsg = "Failed to get a response from sushiliteR5!"; + logger.error(errorMsg + " URI was:\n" + uri + "\n" + rce.getMessage()); + return ResponseEntity.internalServerError().body(errorMsg); + } + + HttpStatus httpStatus = resp.getStatusCode(); + if ( httpStatus != HttpStatus.OK ) { + logger.warn("Sushi cannot give us data! Responded status: " + httpStatus); + return ResponseEntity.status(httpStatus).build(); + } + + Object reportResult = resp.getBody(); + if ( reportResult == null ) { + logger.error("The \"reportResponseWrapper\" for sushi was null!"); + return ResponseEntity.internalServerError().build(); + } + + logger.trace(reportResult.toString()); + + return ResponseEntity.ok(reportResult); + + + + // TODO - Depending on the "report-type", map the "object" to the right Report-type class. + // TODO - This will be useful, in case we add preprocessing steps, like pagination. +/* try { + switch ( report ) { + case "PR": + + break; + case "PR_P1": + + break; + case "IR": + COUNTER_Item_Report counterItemReport = (COUNTER_Item_Report) resp.getBody(); + if ( counterItemReport == null ) { + logger.error("The \"reportResponseWrapper\" for sushi was null!"); + return ResponseEntity.internalServerError().build(); + } else { + logger.debug(counterItemReport.toString()); + return ResponseEntity.ok(counterItemReport); + } + case "DSR": + + break; + default: + String errorMsg = "Invalid report type was given: " + report; + logger.error(errorMsg); + return ResponseEntity.badRequest().body(errorMsg); + } + } catch (ClassCastException cce) { + logger.error("The report object could not be mapped to the repo-type-object of \"" + report + "\"!", cce); + return ResponseEntity.internalServerError().build(); + }*/ + + } + +} From 54ebb351610d7b764e59722ebb4fcf175ed1eb4b Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Wed, 29 Mar 2023 12:04:34 +0300 Subject: [PATCH 12/30] Detect the "504 Gateway Time-out" error from Sushilite-R5 and provide a mention to it, in the response-body. --- .../manager/service/sushilite/SushiliteR5ServiceImpl.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteR5ServiceImpl.java b/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteR5ServiceImpl.java index 223d016..f46c7ee 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteR5ServiceImpl.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteR5ServiceImpl.java @@ -73,7 +73,11 @@ public class SushiliteR5ServiceImpl implements SushiliteR5Service { new ParameterizedTypeReference() {}); } catch (RestClientException rce) { String errorMsg = "Failed to get a response from sushiliteR5!"; - logger.error(errorMsg + " URI was:\n" + uri + "\n" + rce.getMessage()); + String message = rce.getMessage(); + if ( (message != null) && message.contains("504 Gateway Time-out") ) // In this case the parsing-exception is thrown before we get to check the response code.. + errorMsg += " Reason: 504 Gateway Time-out"; + + logger.error(errorMsg + " URI was:\n" + uri + "\n" + message); return ResponseEntity.internalServerError().body(errorMsg); } From 0ce67b6355cb77f7587be7517884d397a860c76b Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Wed, 29 Mar 2023 12:08:43 +0300 Subject: [PATCH 13/30] Avoid assigning an empty list, in case the "reportItems" is null, inside "SushiliteServiceImpl.getReportResults()". --- .../manager/service/sushilite/SushiliteServiceImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteServiceImpl.java b/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteServiceImpl.java index bd00c7c..d38c306 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteServiceImpl.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteServiceImpl.java @@ -93,8 +93,8 @@ public class SushiliteServiceImpl implements SushiliteService { List requestedItemList = new ArrayList<>(); Customer customer = reportResponseWrapper.getReportResponse().getReportWrapper().getReport().getCustomer(); - List allReportItems = customer.getReportItems(); + List allReportItems = customer.getReportItems(); if ( allReportItems != null ) { try { int totalItems = allReportItems.size(); @@ -111,9 +111,9 @@ public class SushiliteServiceImpl implements SushiliteService { logger.error("Exception on getReportResults - trying to cast strings to integers", e); throw e; } - } - customer.setReportItems(requestedItemList); // Setting the reportItems to the "customer"-reference, updates the "reportResponseWrapper" object. + customer.setReportItems(requestedItemList); // Setting the reportItems to the "customer"-reference, updates the "reportResponseWrapper" object. + } return reportResponseWrapper; } From dfc4a4690ea57d940ba2e01d471c18ad0fc50a8a Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Wed, 29 Mar 2023 12:11:19 +0300 Subject: [PATCH 14/30] Update Json and Gson dependencies. --- pom.xml | 6 +++--- .../eu/dnetlib/repo/manager/service/RepositoryService.java | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 23f28eb..d9a5b89 100644 --- a/pom.xml +++ b/pom.xml @@ -173,12 +173,12 @@ org.aksw.gson gson-utils-core - 1.0.0 + 1.0.1 org.json json - 20080701 + 20230227 @@ -241,7 +241,7 @@ com.google.code.gson gson - 2.6.2 + 2.10.1 diff --git a/src/main/java/eu/dnetlib/repo/manager/service/RepositoryService.java b/src/main/java/eu/dnetlib/repo/manager/service/RepositoryService.java index 369f21d..cff079b 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/RepositoryService.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/RepositoryService.java @@ -4,7 +4,6 @@ import eu.dnetlib.api.functionality.ValidatorServiceException; import eu.dnetlib.repo.manager.domain.*; import eu.dnetlib.repo.manager.exception.RepositoryServiceException; import eu.dnetlib.repo.manager.exception.ResourceNotFoundException; -import org.json.JSONException; import org.springframework.security.core.Authentication; import java.util.List; From 311442854a86627e69f1002312717ff755d846ad Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Wed, 29 Mar 2023 15:15:53 +0300 Subject: [PATCH 15/30] Add missing "sushiliteR5Endpoint" property. --- src/main/resources/application.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f48c834..23faeb0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -74,6 +74,7 @@ services: usagestats: adminEmail: XX sushiliteEndpoint: ${services.openaireServicesBaseUrl}/usagestats/sushilite/ + sushiliteR5Endpoint: ${services.openaireServicesBaseUrl}/usagestats_r5/sushilite/r5/ validator: results: url: https://beta.provide.openaire.eu/compatibility/browseHistory/ From ab854a662452c1b3636a25d1c554dffc609427e4 Mon Sep 17 00:00:00 2001 From: Konstantinos Spyrou Date: Tue, 4 Apr 2023 18:43:48 +0300 Subject: [PATCH 16/30] changed application type and replaced counters with gauges --- .../manager/controllers/PrometheusController.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/PrometheusController.java b/src/main/java/eu/dnetlib/repo/manager/controllers/PrometheusController.java index 28102ea..b644641 100644 --- a/src/main/java/eu/dnetlib/repo/manager/controllers/PrometheusController.java +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/PrometheusController.java @@ -23,8 +23,8 @@ import java.io.File; @RestController -@RequestMapping("/actuator/prometheus") -public class PrometheusController { // TODO: remove this with migration to Spring Boot 2 +@RequestMapping(value = "/actuator/prometheus", produces = "application/openmetrics-text; version=1.0.0; charset=utf-8") +public class PrometheusController { private static final Logger logger = LoggerFactory.getLogger(PrometheusController.class); private final PiWikService piWikService; @@ -36,12 +36,12 @@ public class PrometheusController { // TODO: remove this with migration to Sprin this.repositoryService = repositoryService; } - @RequestMapping(method = RequestMethod.GET, path = "", produces = MediaType.TEXT_PLAIN_VALUE) + @RequestMapping(method = RequestMethod.GET) public String getPiwikMetrics() { PrometheusMeterRegistry registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); - registry.counter("provide_repositories_registered").increment(repositoryService.getTotalRegisteredRepositories()); - registry.counter("provide_usagecounts_repositories_registered").increment(piWikService.getTotal()); - registry.counter("provide_usagecounts_repositories_validated").increment(piWikService.getValidated(true)); + registry.gauge("provide_repositories_registered", repositoryService.getTotalRegisteredRepositories()); + registry.gauge("provide_usagecounts_repositories_registered", piWikService.getTotal()); + registry.gauge("provide_usagecounts_repositories_validated", piWikService.getValidated(true)); return registry.scrape(TextFormat.CONTENT_TYPE_OPENMETRICS_100); } From 8f6426eaf65f30a6bca2b0f5c68a31e030c11b07 Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Thu, 1 Jun 2023 20:48:01 +0300 Subject: [PATCH 17/30] - Upgrade Spring and other dependencies. - Migrate to SpringDoc, since SpringFox is dead. --- README.md | 2 +- pom.xml | 58 ++++++++++++++----- .../config/AaiSecurityConfiguration.java | 1 + .../manager/config/OpenAPIConfiguration.java | 23 ++++++++ .../repo/manager/config/SwaggerConfig.java | 47 --------------- .../manager/controllers/BrokerController.java | 10 ++-- .../controllers/DashboardController.java | 4 +- .../controllers/MonitorController.java | 20 +++---- .../manager/controllers/PiWikController.java | 32 +++++----- .../controllers/RepositoryController.java | 4 +- .../manager/controllers/StatsController.java | 4 +- .../controllers/SushiliteController.java | 4 +- .../controllers/SushiliteR5Controller.java | 4 +- .../manager/controllers/UserController.java | 4 +- .../controllers/ValidatorController.java | 18 +++--- src/main/resources/application.yml | 14 ++++- 16 files changed, 131 insertions(+), 118 deletions(-) create mode 100644 src/main/java/eu/dnetlib/repo/manager/config/OpenAPIConfiguration.java delete mode 100644 src/main/java/eu/dnetlib/repo/manager/config/SwaggerConfig.java diff --git a/README.md b/README.md index 41091f1..b96ac5b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Provide +# uoa-repository-manager-service (Provide backend) [...] diff --git a/pom.xml b/pom.xml index d9a5b89..d100753 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 2.5.4 + 2.7.12 4.0.0 @@ -16,13 +16,14 @@ jar - 2.5.4 + 2.7.12 org.springframework.boot spring-boot-starter-web + ${spring.boot.version} org.springframework.boot @@ -33,35 +34,44 @@ org.springframework.boot spring-boot-starter-log4j2 + ${spring.boot.version} org.springframework.boot spring-boot-starter-data-jdbc + ${spring.boot.version} org.springframework.boot spring-boot-starter-data-jpa + ${spring.boot.version} org.springframework.boot spring-boot-actuator-autoconfigure + ${spring.boot.version} org.springframework.boot spring-boot-starter-data-redis + ${spring.boot.version} org.springframework.boot spring-boot-starter-validation + ${spring.boot.version} org.springframework.boot spring-boot-starter-test + ${spring.boot.version} test + com.h2database h2 + 2.1.214 @@ -144,13 +154,13 @@ cglib cglib-nodep - 2.2 + 2.2.2 commons-io commons-io - 2.4 + 2.12.0 @@ -158,10 +168,12 @@ oai4j [0.6b1,) + + xalan xalan - 2.7.2 + 2.7.3 @@ -170,11 +182,14 @@ 1.19.3 + + org.aksw.gson gson-utils-core 1.0.1 + org.json json @@ -188,14 +203,26 @@ - io.springfox - springfox-swagger2 - 2.7.0 + org.springdoc + springdoc-openapi-webmvc-core + 1.7.0 - io.springfox - springfox-swagger-ui - 2.7.0 + org.springdoc + springdoc-openapi-ui + 1.7.0 + + + org.springdoc + springdoc-openapi-security + 1.7.0 + + + + + org.webjars + webjars-locator-core + 0.52 @@ -208,13 +235,13 @@ commons-fileupload commons-fileupload - 1.4 + 1.5 org.mitre openid-connect-client - 1.3.0 + 1.3.4 org.slf4j @@ -225,13 +252,14 @@ org.springframework.session spring-session-data-redis + 2.7.1 redis.clients jedis - ${jedis.version} + ${jedis.version} @@ -281,7 +309,7 @@ javax.activation activation - 1.1-rev-1 + 1.1.1 diff --git a/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java b/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java index 0782850..65934fe 100644 --- a/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java +++ b/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java @@ -64,6 +64,7 @@ public class AaiSecurityConfiguration extends WebSecurityConfigurerAdapter { .authorizeRequests() .regexMatchers("/actuator/.*").permitAll() .regexMatchers("/metrics").permitAll() + .antMatchers("/v3/api-docs/**","/swagger-ui/**").permitAll() .anyRequest().authenticated() .and() .logout().logoutUrl("/openid_logout") diff --git a/src/main/java/eu/dnetlib/repo/manager/config/OpenAPIConfiguration.java b/src/main/java/eu/dnetlib/repo/manager/config/OpenAPIConfiguration.java new file mode 100644 index 0000000..809b286 --- /dev/null +++ b/src/main/java/eu/dnetlib/repo/manager/config/OpenAPIConfiguration.java @@ -0,0 +1,23 @@ +package eu.dnetlib.repo.manager.config; + + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.info.Contact; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.info.License; + +@OpenAPIDefinition( + info = @Info( + title = "Repository Manager Dashboard API Documentation", + description = "Repository Manager Dashboard API Documentation", + version = "1.0", + termsOfService = "urn:tos", + license = @License( + name = "Apache 2.0", + url = "https://www.apache.org/licenses/LICENSE-2.0.html" + ), + contact = @Contact(name = "", url = "", email = "") + ) +) +public class OpenAPIConfiguration { +} diff --git a/src/main/java/eu/dnetlib/repo/manager/config/SwaggerConfig.java b/src/main/java/eu/dnetlib/repo/manager/config/SwaggerConfig.java deleted file mode 100644 index 9e4893b..0000000 --- a/src/main/java/eu/dnetlib/repo/manager/config/SwaggerConfig.java +++ /dev/null @@ -1,47 +0,0 @@ -package eu.dnetlib.repo.manager.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import springfox.documentation.builders.PathSelectors; -import springfox.documentation.builders.RequestHandlerSelectors; -import springfox.documentation.service.ApiInfo; -import springfox.documentation.service.Contact; -import springfox.documentation.service.VendorExtension; -import springfox.documentation.spi.DocumentationType; -import springfox.documentation.spring.web.plugins.Docket; -import springfox.documentation.swagger2.annotations.EnableSwagger2; - -import java.util.ArrayList; - - -/** - * Created by panagiotis on 16/11/2017. - */ -@Configuration -@EnableSwagger2 -@EnableWebMvc -public class SwaggerConfig { - - @Bean - public Docket productApi() { - return new Docket(DocumentationType.SWAGGER_2) - .select() - .apis(RequestHandlerSelectors.any()) - .paths(PathSelectors.any()) - .build() - .pathMapping("/") - .apiInfo(getApiInfo()); - } - - private ApiInfo getApiInfo() { - return new ApiInfo("Repository Manager Dashboard API Documentation", - "Repository Manager Dashboard API Documentation", - "1.0", - "urn:tos", - new Contact("", "", ""), - "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", - new ArrayList()); - } - -} diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/BrokerController.java b/src/main/java/eu/dnetlib/repo/manager/controllers/BrokerController.java index 187da9c..f96af01 100644 --- a/src/main/java/eu/dnetlib/repo/manager/controllers/BrokerController.java +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/BrokerController.java @@ -4,8 +4,8 @@ import eu.dnetlib.repo.manager.domain.Term; import eu.dnetlib.repo.manager.domain.broker.*; import eu.dnetlib.repo.manager.exception.BrokerException; import eu.dnetlib.repo.manager.service.BrokerServiceImpl; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import org.json.JSONException; import org.mitre.openid.connect.model.OIDCAuthenticationToken; import org.springframework.beans.factory.annotation.Autowired; @@ -21,7 +21,7 @@ import java.util.Map; @RestController @RequestMapping(value = "/broker") -@Api(description = "Broker API", tags = {"broker"}) +@Tag(name="broker", description="Broker API") public class BrokerController{ @Autowired @@ -33,9 +33,9 @@ public class BrokerController{ @PreAuthorize("hasAuthority('REGISTERED_USER')") public DatasourcesBroker getDatasourcesOfUser( @RequestParam("includeShared") - @ApiParam(value = "Include shared datasources", required = true , defaultValue = "false") String includeShared, + @Parameter(description = "Include shared datasources (default = false)", required = true) String includeShared, @RequestParam("includeByOthers") - @ApiParam(value = "Include datasources of other", required = true,defaultValue = "false") String includeByOthers) throws JSONException { + @Parameter(description = "Include datasources of other (default = false)", required = true) String includeByOthers) throws JSONException { return brokerService.getDatasourcesOfUser(((OIDCAuthenticationToken) SecurityContextHolder.getContext().getAuthentication()).getUserInfo().getEmail(), includeShared, includeByOthers); } diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/DashboardController.java b/src/main/java/eu/dnetlib/repo/manager/controllers/DashboardController.java index 3fd6650..5a1d7fb 100644 --- a/src/main/java/eu/dnetlib/repo/manager/controllers/DashboardController.java +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/DashboardController.java @@ -8,7 +8,7 @@ import eu.dnetlib.repo.manager.domain.UsageSummary; import eu.dnetlib.repo.manager.exception.BrokerException; import eu.dnetlib.repo.manager.exception.RepositoryServiceException; import eu.dnetlib.repo.manager.service.*; -import io.swagger.annotations.Api; +import io.swagger.v3.oas.annotations.tags.Tag; import org.json.JSONException; import org.mitre.openid.connect.model.OIDCAuthenticationToken; import org.springframework.beans.factory.annotation.Autowired; @@ -21,7 +21,7 @@ import java.util.List; @RestController @RequestMapping(value = "/dashboard") -@Api(description = "Dashboard API", tags = {"dashboard"}) +@Tag(name="dashboard", description = "Dashboard API") public class DashboardController { @Autowired diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/MonitorController.java b/src/main/java/eu/dnetlib/repo/manager/controllers/MonitorController.java index 69b04db..db24ca6 100644 --- a/src/main/java/eu/dnetlib/repo/manager/controllers/MonitorController.java +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/MonitorController.java @@ -4,8 +4,8 @@ import eu.dnetlib.api.functionality.ValidatorServiceException; import eu.dnetlib.domain.functionality.validator.StoredJob; import eu.dnetlib.repo.manager.domain.JobsOfUser; import eu.dnetlib.repo.manager.service.MonitorServiceImpl; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import org.json.JSONException; import org.mitre.openid.connect.model.OIDCAuthenticationToken; import org.slf4j.Logger; @@ -18,7 +18,7 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping(value = "/monitor") -@Api(description = "Monitor API", tags = {"monitor"}) +@Tag(name="monitor", description="Monitor API") public class MonitorController { private static final Logger logger = LoggerFactory.getLogger(MonitorController.class); @@ -31,13 +31,13 @@ public class MonitorController { @ResponseBody @PreAuthorize("hasAuthority('REGISTERED_USER')") public JobsOfUser getJobsOfUser(@RequestParam(value = "jobType", required = false) - @ApiParam(value = "Equals to filter job type on validation history page") String jobType, - @RequestParam("offset") @ApiParam(value = "Page number", required = true) String offset, - @RequestParam(value = "limit", required = false,defaultValue = "10") @ApiParam(value = "Null value") String limit, - @RequestParam(value = "dateFrom", required = false) @ApiParam(value = "Null value") String dateFrom, - @RequestParam(value = "dateTo", required = false) @ApiParam(value = "Null value") String dateTo, - @RequestParam("validationStatus") @ApiParam(value = "Equals to filter validation jobs", required = false) String validationStatus, - @RequestParam("includeJobsTotal") @ApiParam(value = "Always true", required = true) String includeJobsTotal) throws JSONException, ValidatorServiceException { + @Parameter(description = "Equals to filter job type on validation history page") String jobType, + @RequestParam("offset") @Parameter(name = "Page number", required = true) String offset, + @RequestParam(value = "limit", required = false,defaultValue = "10") @Parameter(description = "Null value") String limit, + @RequestParam(value = "dateFrom", required = false) @Parameter(description = "Null value") String dateFrom, + @RequestParam(value = "dateTo", required = false) @Parameter(description = "Null value") String dateTo, + @RequestParam("validationStatus") @Parameter(description = "Equals to filter validation jobs", required = false) String validationStatus, + @RequestParam("includeJobsTotal") @Parameter(description = "Always true", required = true) String includeJobsTotal) throws JSONException, ValidatorServiceException { return monitorService.getJobsOfUser(((OIDCAuthenticationToken) SecurityContextHolder.getContext().getAuthentication()).getUserInfo().getEmail(), jobType, offset, limit, dateFrom, dateTo, validationStatus, includeJobsTotal); } diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/PiWikController.java b/src/main/java/eu/dnetlib/repo/manager/controllers/PiWikController.java index d7edf52..a27ecc5 100644 --- a/src/main/java/eu/dnetlib/repo/manager/controllers/PiWikController.java +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/PiWikController.java @@ -6,9 +6,9 @@ import eu.dnetlib.repo.manager.domain.OrderByType; import eu.dnetlib.repo.manager.domain.Paging; import eu.dnetlib.repo.manager.exception.RepositoryServiceException; import eu.dnetlib.repo.manager.service.PiWikServiceImpl; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiImplicitParam; -import io.swagger.annotations.ApiImplicitParams; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -31,7 +31,7 @@ import java.util.List; @RestController @RequestMapping(value = "/piwik") -@Api(description = "Piwik API", tags = {"piwik"}) +@Tag(name="piwik", description = "Piwik API") public class PiWikController { private static final Logger logger = LoggerFactory.getLogger(PiWikController.class); @@ -53,12 +53,12 @@ public class PiWikController { } @RequestMapping(value = "/getPiwikSitesForRepos" , method = RequestMethod.GET,produces = MediaType.APPLICATION_JSON_VALUE) - @ApiImplicitParams({ - @ApiImplicitParam(name = "from", dataType = "number", paramType = "query"), - @ApiImplicitParam(name = "quantity", dataType = "number", paramType = "query"), - @ApiImplicitParam(name = "order", dataType = "eu.dnetlib.repo.manager.domain.OrderByType", paramType = "query"), - @ApiImplicitParam(name = "orderField", dataType = "eu.dnetlib.repo.manager.domain.OrderByField", paramType = "query"), - @ApiImplicitParam(name = "searchField", dataType = "string", paramType = "query"), + @Parameters({ + @Parameter(name = "from", description = "number"), + @Parameter(name = "quantity", description = "number"), + @Parameter(name = "order", description = "eu.dnetlib.repo.manager.domain.OrderByType"), + @Parameter(name = "orderField", description = "eu.dnetlib.repo.manager.domain.OrderByField"), + @Parameter(name = "searchField", description = "string") }) public Paging getPiwikSitesForRepos( @RequestParam(value = "from",required=false,defaultValue = "0") int from, @@ -77,12 +77,12 @@ public class PiWikController { return results; } - @ApiImplicitParams({ - @ApiImplicitParam(name = "from", dataType = "number", paramType = "query"), - @ApiImplicitParam(name = "quantity", dataType = "number", paramType = "query"), - @ApiImplicitParam(name = "order", dataType = "eu.dnetlib.repo.manager.domain.OrderByType", paramType = "query"), - @ApiImplicitParam(name = "searchField", dataType = "eu.dnetlib.repo.manager.domain.OrderByField", paramType = "query"), - @ApiImplicitParam(name = "orderField", dataType = "string", paramType = "query"), + @Parameters({ + @Parameter(name = "from", description = "number"), + @Parameter(name = "quantity", description = "number"), + @Parameter(name = "order", description = "eu.dnetlib.repo.manager.domain.OrderByType"), + @Parameter(name = "searchField", description = "eu.dnetlib.repo.manager.domain.OrderByField"), + @Parameter(name = "orderField", description = "string") }) @RequestMapping(value = "/getPiwikSitesForRepos/csv" , method = RequestMethod.GET,produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) @ResponseBody diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/RepositoryController.java b/src/main/java/eu/dnetlib/repo/manager/controllers/RepositoryController.java index 7712547..5702eb2 100644 --- a/src/main/java/eu/dnetlib/repo/manager/controllers/RepositoryController.java +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/RepositoryController.java @@ -10,7 +10,7 @@ import eu.dnetlib.repo.manager.service.AggregationService; import eu.dnetlib.repo.manager.service.RepositoryService; import eu.dnetlib.repo.manager.service.security.AuthorizationService; import eu.dnetlib.repo.manager.utils.JsonUtils; -import io.swagger.annotations.Api; +import io.swagger.v3.oas.annotations.tags.Tag; import org.json.JSONException; import org.mitre.openid.connect.model.OIDCAuthenticationToken; import org.slf4j.Logger; @@ -32,7 +32,7 @@ import java.util.Map; @RestController @RequestMapping(value = "/repositories") -@Api(description = "Repository API", tags = {"repositories"}) +@Tag(name="repositories", description="Repository API") public class RepositoryController { private static final Logger logger = LoggerFactory.getLogger(RepositoryController.class); diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/StatsController.java b/src/main/java/eu/dnetlib/repo/manager/controllers/StatsController.java index 1cfd6a2..916b066 100644 --- a/src/main/java/eu/dnetlib/repo/manager/controllers/StatsController.java +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/StatsController.java @@ -1,7 +1,7 @@ package eu.dnetlib.repo.manager.controllers; import eu.dnetlib.repo.manager.service.StatsServiceImpl; -import io.swagger.annotations.Api; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; @@ -13,7 +13,7 @@ import java.util.Map; @RestController @RequestMapping(value = "/stats") -@Api(description = "Stats API", tags = {"statistics"}) +@Tag(name="statistics", description = "Stats API") public class StatsController { @Autowired diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteController.java b/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteController.java index 7c3b292..039c335 100644 --- a/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteController.java +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteController.java @@ -2,7 +2,7 @@ package eu.dnetlib.repo.manager.controllers; import eu.dnetlib.repo.manager.service.sushilite.SushiliteServiceImpl; import eu.dnetlib.usagestats.sushilite.domain.ReportResponseWrapper; -import io.swagger.annotations.Api; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; @@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping(value = "/sushilite") -@Api(description = "Sushi-Lite API", tags = {"sushilite"}) +@Tag(name="sushilite", description = "Sushi-Lite API") public class SushiliteController { diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteR5Controller.java b/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteR5Controller.java index e57ff35..4288a36 100644 --- a/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteR5Controller.java +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteR5Controller.java @@ -2,7 +2,7 @@ package eu.dnetlib.repo.manager.controllers; import eu.dnetlib.repo.manager.service.sushilite.SushiliteR5Service; import eu.dnetlib.repo.manager.service.sushilite.SushiliteR5ServiceImpl; -import io.swagger.annotations.Api; +import io.swagger.v3.oas.annotations.tags.Tag; import org.json.JSONException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,7 +17,7 @@ import java.util.List; @RestController @RequestMapping(value = "/sushiliteR5") -@Api(description = "Sushi-Lite R5 API", tags = {"sushiliteR5"}) +@Tag(name="sushiliteR5", description = "Sushi-Lite R5 API") public class SushiliteR5Controller { diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/UserController.java b/src/main/java/eu/dnetlib/repo/manager/controllers/UserController.java index 08e2119..f93de82 100644 --- a/src/main/java/eu/dnetlib/repo/manager/controllers/UserController.java +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/UserController.java @@ -1,7 +1,7 @@ package eu.dnetlib.repo.manager.controllers; import eu.dnetlib.repo.manager.service.UserServiceImpl; -import io.swagger.annotations.Api; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping(value = "/user") -@Api(description = "User API", tags = {"user"}) +@Tag(name="user", description = "User API") public class UserController { @Autowired diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/ValidatorController.java b/src/main/java/eu/dnetlib/repo/manager/controllers/ValidatorController.java index f94d860..df23d7f 100644 --- a/src/main/java/eu/dnetlib/repo/manager/controllers/ValidatorController.java +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/ValidatorController.java @@ -9,8 +9,8 @@ import eu.dnetlib.repo.manager.exception.ResourceNotFoundException; import eu.dnetlib.repo.manager.exception.ValidationServiceException; import eu.dnetlib.repo.manager.service.EmailUtils; import eu.dnetlib.repo.manager.service.ValidatorServiceImpl; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import org.json.JSONException; import org.mitre.openid.connect.model.OIDCAuthenticationToken; import org.springframework.beans.factory.annotation.Autowired; @@ -25,7 +25,7 @@ import java.util.List; @RestController @RequestMapping(value = "/validator") -@Api(description = "Validator API", tags = {"validator"}) +@Tag(name="validator", description = "Validator API") public class ValidatorController { @Autowired @@ -80,12 +80,12 @@ public class ValidatorController { @ResponseBody @PreAuthorize("hasAuthority('REGISTERED_USER')") public List getStoredJobsNew(@RequestParam(value = "jobType", required = false) - @ApiParam(value = "Equals to filter job type on validation history page") String jobType, - @RequestParam("offset") @ApiParam(value = "Page number", required = true) String offset, - @RequestParam(value = "limit", required = false,defaultValue = "10") @ApiParam(value = "Null value") String limit, - @RequestParam(value = "dateFrom", required = false) @ApiParam(value = "Null value") String dateFrom, - @RequestParam(value = "dateTo", required = false) @ApiParam(value = "Null value") String dateTo, - @RequestParam("validationStatus") @ApiParam(value = "Equals to filter validation jobs", required = true) String validationStatus + @Parameter(description = "Equals to filter job type on validation history page") String jobType, + @RequestParam("offset") @Parameter(description = "Page number", required = true) String offset, + @RequestParam(value = "limit", required = false,defaultValue = "10") @Parameter(description = "Null value") String limit, + @RequestParam(value = "dateFrom", required = false) @Parameter(description = "Null value") String dateFrom, + @RequestParam(value = "dateTo", required = false) @Parameter(description = "Null value") String dateTo, + @RequestParam("validationStatus") @Parameter(description = "Equals to filter validation jobs", required = true) String validationStatus ) throws ValidatorServiceException { return validatorService.getStoredJobsNew(((OIDCAuthenticationToken) SecurityContextHolder.getContext().getAuthentication()).getUserInfo().getEmail(), jobType, offset, limit, dateFrom, dateTo, validationStatus); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 23faeb0..84a7eb8 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,6 +1,14 @@ -springdoc.swagger-ui: - disable-swagger-default-url: true - version: 3 +server: + port: 8480 + servlet: + context-path: /uoa-repository-manager-service + +springdoc: + swagger-ui: + disable-swagger-default-url: true + path: /swagger-ui.html + api-docs: + path: /api-docs spring: jpa: From dc469df9d667ce6781e6f52f62651e9c948cea8c Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Fri, 2 Jun 2023 13:00:25 +0300 Subject: [PATCH 18/30] Add JPA configuration. This fixes a missing Bean error. --- .../eu/dnetlib/repo/manager/Application.java | 2 ++ .../dnetlib/repo/manager/config/JPAConfig.java | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 src/main/java/eu/dnetlib/repo/manager/config/JPAConfig.java diff --git a/src/main/java/eu/dnetlib/repo/manager/Application.java b/src/main/java/eu/dnetlib/repo/manager/Application.java index b934bfd..1b74a64 100644 --- a/src/main/java/eu/dnetlib/repo/manager/Application.java +++ b/src/main/java/eu/dnetlib/repo/manager/Application.java @@ -2,9 +2,11 @@ package eu.dnetlib.repo.manager; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication +@EnableJpaRepositories @EnableScheduling public class Application { diff --git a/src/main/java/eu/dnetlib/repo/manager/config/JPAConfig.java b/src/main/java/eu/dnetlib/repo/manager/config/JPAConfig.java new file mode 100644 index 0000000..2036ee2 --- /dev/null +++ b/src/main/java/eu/dnetlib/repo/manager/config/JPAConfig.java @@ -0,0 +1,17 @@ +package eu.dnetlib.repo.manager.config; + + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; + +@Configuration +public class JPAConfig { + + @Bean + public PlatformTransactionManager transactionManager() { + return new JpaTransactionManager(); + } + +} From a2a387be90a57e5b707c844cba590ca47c5e6604 Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Fri, 2 Jun 2023 14:23:54 +0300 Subject: [PATCH 19/30] - Fix a security config for Swagger. - Add the "displayRequestDuration" for "Try it out" requests in Swagger. - Code polishing. --- .../repo/manager/config/AaiSecurityConfiguration.java | 2 +- .../dnetlib/repo/manager/controllers/PiWikController.java | 6 +++--- .../repo/manager/controllers/SushiliteController.java | 1 - .../repo/manager/controllers/SushiliteR5Controller.java | 2 -- .../repo/manager/service/sushilite/SushiliteR5Service.java | 1 - src/main/resources/application.yml | 1 + src/main/resources/log4j2.xml | 3 --- 7 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java b/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java index 65934fe..563eb4b 100644 --- a/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java +++ b/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java @@ -64,7 +64,7 @@ public class AaiSecurityConfiguration extends WebSecurityConfigurerAdapter { .authorizeRequests() .regexMatchers("/actuator/.*").permitAll() .regexMatchers("/metrics").permitAll() - .antMatchers("/v3/api-docs/**","/swagger-ui/**").permitAll() + .antMatchers("/api-docs/**","/swagger-ui/**").permitAll() .anyRequest().authenticated() .and() .logout().logoutUrl("/openid_logout") diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/PiWikController.java b/src/main/java/eu/dnetlib/repo/manager/controllers/PiWikController.java index a27ecc5..985c60e 100644 --- a/src/main/java/eu/dnetlib/repo/manager/controllers/PiWikController.java +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/PiWikController.java @@ -61,9 +61,9 @@ public class PiWikController { @Parameter(name = "searchField", description = "string") }) public Paging getPiwikSitesForRepos( - @RequestParam(value = "from",required=false,defaultValue = "0") int from, - @RequestParam(value = "quantity",required=false,defaultValue = "100") int quantity, - @RequestParam(value = "order",required=false,defaultValue = "DSC") OrderByType orderType, + @RequestParam(value = "from", required=false, defaultValue = "0") int from, + @RequestParam(value = "quantity", required=false, defaultValue = "100") int quantity, + @RequestParam(value = "order", required=false, defaultValue = "DSC") OrderByType orderType, @RequestParam(value = "orderField", required = false, defaultValue = "REPOSITORY_NAME") OrderByField orderField, @RequestParam(value = "searchField", required = false, defaultValue = "") String searchField diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteController.java b/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteController.java index 039c335..5d6d49d 100644 --- a/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteController.java +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteController.java @@ -13,7 +13,6 @@ import org.springframework.web.bind.annotation.*; @Tag(name="sushilite", description = "Sushi-Lite API") public class SushiliteController { - @Autowired private SushiliteServiceImpl sushiliteService; diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteR5Controller.java b/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteR5Controller.java index 4288a36..b1533f2 100644 --- a/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteR5Controller.java +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteR5Controller.java @@ -1,6 +1,5 @@ package eu.dnetlib.repo.manager.controllers; -import eu.dnetlib.repo.manager.service.sushilite.SushiliteR5Service; import eu.dnetlib.repo.manager.service.sushilite.SushiliteR5ServiceImpl; import io.swagger.v3.oas.annotations.tags.Tag; import org.json.JSONException; @@ -42,7 +41,6 @@ public class SushiliteR5Controller { @RequestParam(value = "dataType",required=false, defaultValue="") String dataType, @RequestParam(value = "Granularity", required = false, defaultValue ="Monthly") String granularity, @RequestParam(value = "Pretty",required=false, defaultValue="") String pretty) { - try { return sushiliteR5Service.getReportResults(report, release, requestorID, beginDate, endDate, repositoryIdentifier, datasetIdentifier, itemIdentifier, metricTypes, dataType, granularity, pretty); } catch (JSONException je) { diff --git a/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteR5Service.java b/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteR5Service.java index 44a4c80..662d123 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteR5Service.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteR5Service.java @@ -1,6 +1,5 @@ package eu.dnetlib.repo.manager.service.sushilite; -import eu.dnetlib.usagestats.sushilite.domain.ReportResponseWrapper; import org.json.JSONException; import org.springframework.http.ResponseEntity; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 84a7eb8..5b3a5d5 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -7,6 +7,7 @@ springdoc: swagger-ui: disable-swagger-default-url: true path: /swagger-ui.html + displayRequestDuration: true api-docs: path: /api-docs diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 2e6c355..45fb4a8 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -15,8 +15,5 @@ - - - From eb42185991c807e1ead284314e00c19c9a0e3ebc Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Tue, 6 Jun 2023 19:27:02 +0300 Subject: [PATCH 20/30] - Update dependencies. - Add notes for swagger-UI in README. --- README.md | 5 +++++ pom.xml | 40 ++++++++++++++++++---------------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index b96ac5b..681a2de 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,8 @@ - Provide all not-set or redacted configurations, inside the **src/main/resources/application.properties** file. - Build the app with: `mvn clean install` - Run the app with: `java -jar ./target/uoa-repository-manager-service.jar` + + +### Notes for Swagger-UI: +- In order to request data from most endpoints, you have to be a "REGISTERED_USER", otherwise you will get a 403 error code. +- In order to be a registered user, you have to run the [UI-service](https://code-repo.d4science.org/MaDgIK/uoa-repository-manager-ui) , in the same machine, at the same time and login through a browser, using the following url: http://localhost:8480/uoa-repository-manager-service/openid_connect_login diff --git a/pom.xml b/pom.xml index d100753..c682233 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,7 @@ org.apache.solr solr-solrj - 9.1.1 + 9.2.1 @@ -95,7 +95,6 @@ log4j log4j - javax.servlet javax.servlet-api @@ -154,7 +153,7 @@ cglib cglib-nodep - 2.2.2 + 3.3.0 @@ -179,7 +178,7 @@ com.sun.jersey jersey-client - 1.19.3 + 1.19.4 @@ -199,7 +198,7 @@ com.sun.mail javax.mail - 1.6.0 + 1.6.2 @@ -218,18 +217,11 @@ 1.7.0 - + - org.webjars - webjars-locator-core - 0.52 - - - - - postgresql + org.postgresql postgresql - 9.1-901.jdbc3 + 42.6.0 @@ -262,10 +254,13 @@ ${jedis.version} + org.apache.commons commons-pool2 + 2.11.1 + com.google.code.gson gson @@ -275,14 +270,14 @@ com.thetransactioncompany cors-filter - 2.5 + 2.10 javax.xml.ws jaxws-api - 2.3.0 + 2.3.1 @@ -293,18 +288,18 @@ javax.xml.bind jaxb-api - 2.3.0 + 2.3.1 com.sun.xml.bind jaxb-impl - 2.3.0 + 2.3.8 com.sun.xml.bind jaxb-core - 2.3.0 + 2.3.0.1 javax.activation @@ -332,7 +327,7 @@ org.springframework.session spring-session-bom - 2021.0.2 + 2021.2.1 pom import @@ -366,12 +361,13 @@ org.springframework.boot spring-boot-maven-plugin + ${spring.boot.version} cz.habarta.typescript-generator typescript-generator-maven-plugin - 2.16.538 + 2.37.1128 java to typeScript From 3cb8e7a5f613de7ebb84dae8b67914ee3311298b Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Wed, 5 Jul 2023 13:16:48 +0300 Subject: [PATCH 21/30] - Update dependencies. - Code polishing. --- pom.xml | 17 +++++++++-------- .../sushilite/SushiliteR5ServiceImpl.java | 3 +-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pom.xml b/pom.xml index c682233..d09032b 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.12 + 2.7.13 4.0.0 @@ -16,7 +16,7 @@ jar - 2.7.12 + 2.7.13 @@ -159,7 +159,7 @@ commons-io commons-io - 2.12.0 + 2.13.0 @@ -175,10 +175,11 @@ 2.7.3 + - com.sun.jersey + org.glassfish.jersey.core jersey-client - 1.19.4 + 2.40 @@ -192,7 +193,7 @@ org.json json - 20230227 + 20230618 @@ -244,7 +245,7 @@ org.springframework.session spring-session-data-redis - 2.7.1 + 2.7.2 @@ -327,7 +328,7 @@ org.springframework.session spring-session-bom - 2021.2.1 + 2021.2.2 pom import diff --git a/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteR5ServiceImpl.java b/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteR5ServiceImpl.java index f46c7ee..868d17e 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteR5ServiceImpl.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/sushilite/SushiliteR5ServiceImpl.java @@ -70,7 +70,7 @@ public class SushiliteR5ServiceImpl implements SushiliteR5Service { uri, HttpMethod.GET, null, - new ParameterizedTypeReference() {}); + new ParameterizedTypeReference() {}); // THis "Object" must be a valid json-object. } catch (RestClientException rce) { String errorMsg = "Failed to get a response from sushiliteR5!"; String message = rce.getMessage(); @@ -92,7 +92,6 @@ public class SushiliteR5ServiceImpl implements SushiliteR5Service { logger.error("The \"reportResponseWrapper\" for sushi was null!"); return ResponseEntity.internalServerError().build(); } - logger.trace(reportResult.toString()); return ResponseEntity.ok(reportResult); From 64fdff095d834da7da89dc2693e3ecaf4f196c95 Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Mon, 25 Sep 2023 15:01:31 +0300 Subject: [PATCH 22/30] - Resolve a dependency-conflict. - Update dependencies. --- pom.xml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index d09032b..c1b2772 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.13 + 2.7.16 4.0.0 @@ -16,7 +16,7 @@ jar - 2.7.13 + 2.7.16 @@ -71,19 +71,25 @@ com.h2database h2 - 2.1.214 + 2.2.224 org.apache.solr solr-solrj - 9.2.1 + 9.3.0 eu.dnetlib.dhp dnet-exporter-api [3.3.3-SNAPSHOT, ) + + + eu.dnetlib + dnet-index-solr-common + + @@ -245,7 +251,7 @@ org.springframework.session spring-session-data-redis - 2.7.2 + 2.7.3 From 842468a175ea1ec8e884505a875d967ffc797a0b Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Wed, 27 Sep 2023 12:24:22 +0300 Subject: [PATCH 23/30] Improve performance and error-handling in "InterfaceComplianceService.cleanUp()". --- .../service/InterfaceComplianceService.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/main/java/eu/dnetlib/repo/manager/service/InterfaceComplianceService.java b/src/main/java/eu/dnetlib/repo/manager/service/InterfaceComplianceService.java index 2a59c31..30a46fc 100644 --- a/src/main/java/eu/dnetlib/repo/manager/service/InterfaceComplianceService.java +++ b/src/main/java/eu/dnetlib/repo/manager/service/InterfaceComplianceService.java @@ -39,19 +39,26 @@ public class InterfaceComplianceService { @Scheduled(cron = "0 0 0 * * *") // every day at 00:00 public void cleanUp() { Set requests = getOutdated(); - for (InterfaceComplianceRequest request : requests) { + for ( InterfaceComplianceRequest request : requests ) + { + String repoId = request.getRepositoryId(); + String interfaceId = request.getInterfaceId(); try { - Map repositoryInterfaceMap = repositoryService.getRepositoryInterface(request.getRepositoryId()) + Map repositoryInterfaceMap = repositoryService.getRepositoryInterface(repoId) .stream() .collect(Collectors.toMap(ApiDetails::getId, i -> i)); - Repository repo = repositoryService.getRepositoryById(request.getRepositoryId()); - RepositoryInterface iFace = repositoryInterfaceMap.get(request.getInterfaceId()); - List repositoryAdmins = authorizationService.getAdminsOfRepo(request.getRepositoryId()); + RepositoryInterface iFace = repositoryInterfaceMap.get(interfaceId); + if ( iFace == null ) { + logger.error("The repository-interface \"" + interfaceId + "\" does not exist! | the request had the \"repoId\": \"" + repoId + "\""); + continue; + } + Repository repo = repositoryService.getRepositoryById(repoId); + List repositoryAdmins = authorizationService.getAdminsOfRepo(repoId); emailUtils.sendUserUpdateInterfaceComplianceFailure(repositoryAdmins.stream().map(User::getEmail).collect(Collectors.toList()), repo, iFace, request); emailUtils.sendAdminUpdateInterfaceComplianceFailure(repo, iFace, request); } catch (ResourceNotFoundException e) { - logger.error("Error", e); - } + logger.error("Error for request with \"repoId\": \"" + repoId + "\" and \"interfaceId\": \"" + interfaceId + "\"", e); + } // Continue to the next request. } repository.deleteAll(requests); } From 9da5fa5c1e11316fbebeb49e7480caa65cf07431 Mon Sep 17 00:00:00 2001 From: TheQuaker Date: Fri, 29 Sep 2023 15:36:55 +0300 Subject: [PATCH 24/30] Fix typo for DataType param --- .../dnetlib/repo/manager/controllers/SushiliteR5Controller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteR5Controller.java b/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteR5Controller.java index b1533f2..4ba447e 100644 --- a/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteR5Controller.java +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/SushiliteR5Controller.java @@ -38,7 +38,7 @@ public class SushiliteR5Controller { @RequestParam(value = "DatasetIdentifier", required=false, defaultValue="") String datasetIdentifier, @RequestParam(value = "ItemIdentifier",required=false, defaultValue="") String itemIdentifier, @RequestParam(value = "MetricType",required=false) List metricTypes, - @RequestParam(value = "dataType",required=false, defaultValue="") String dataType, + @RequestParam(value = "DataType",required=false, defaultValue="") String dataType, @RequestParam(value = "Granularity", required = false, defaultValue ="Monthly") String granularity, @RequestParam(value = "Pretty",required=false, defaultValue="") String pretty) { try { From 75e97ece26a44ec0519e0246b3b47c667ec5f9dc Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Wed, 4 Oct 2023 12:29:52 +0300 Subject: [PATCH 25/30] Add info about the Swagger-UI-url in the README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 681a2de..b43d7f1 100644 --- a/README.md +++ b/README.md @@ -10,5 +10,6 @@ ### Notes for Swagger-UI: +- Access it through this url: http://localhost:8480/uoa-repository-manager-service/swagger-ui/index.html - In order to request data from most endpoints, you have to be a "REGISTERED_USER", otherwise you will get a 403 error code. - In order to be a registered user, you have to run the [UI-service](https://code-repo.d4science.org/MaDgIK/uoa-repository-manager-ui) , in the same machine, at the same time and login through a browser, using the following url: http://localhost:8480/uoa-repository-manager-service/openid_connect_login From 513860516312cb507a22f5df25725ba3f121c789 Mon Sep 17 00:00:00 2001 From: Konstantinos Spyrou Date: Wed, 11 Oct 2023 13:00:48 +0300 Subject: [PATCH 26/30] added method to get if piwik is validated --- .../repo/manager/controllers/PiWikController.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/PiWikController.java b/src/main/java/eu/dnetlib/repo/manager/controllers/PiWikController.java index 985c60e..7bb96e9 100644 --- a/src/main/java/eu/dnetlib/repo/manager/controllers/PiWikController.java +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/PiWikController.java @@ -39,6 +39,16 @@ public class PiWikController { @Autowired private PiWikServiceImpl piWikService; + @RequestMapping(value = "/validated", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public Boolean isPiwikValidated(@RequestParam("repositoryId") String repositoryId) { + PiwikInfo info = piWikService.getPiwikSiteForRepo(repositoryId); + if (info != null) { + return info.isValidated(); + } + return false; + } + @RequestMapping(value = "/getPiwikSiteForRepo/{repositoryId}" , method = RequestMethod.GET,produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody @PreAuthorize("hasAnyAuthority('SUPER_ADMINISTRATOR', 'CONTENT_PROVIDER_DASHBOARD_ADMINISTRATOR') or @authorizationService.isMemberOf(#repositoryId) or (@repositoryService.getRepositoryById(#repositoryId).registeredby==null and hasAuthority('REGISTERED_USER'))") From 4b5132358ac459086c87d5833131d4c80b27488d Mon Sep 17 00:00:00 2001 From: Konstantinos Spyrou Date: Wed, 11 Oct 2023 14:45:53 +0300 Subject: [PATCH 27/30] created repository metrics calls and excluded them from authentication --- .../config/AaiSecurityConfiguration.java | 1 + .../manager/controllers/PiWikController.java | 10 ------ .../controllers/RepositoryController.java | 32 ++++++++++++++++++- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java b/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java index 563eb4b..0124dee 100644 --- a/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java +++ b/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java @@ -63,6 +63,7 @@ public class AaiSecurityConfiguration extends WebSecurityConfigurerAdapter { .csrf().disable() .authorizeRequests() .regexMatchers("/actuator/.*").permitAll() + .regexMatchers("/repository/.*/metrics/?.*").permitAll() .regexMatchers("/metrics").permitAll() .antMatchers("/api-docs/**","/swagger-ui/**").permitAll() .anyRequest().authenticated() diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/PiWikController.java b/src/main/java/eu/dnetlib/repo/manager/controllers/PiWikController.java index 7bb96e9..985c60e 100644 --- a/src/main/java/eu/dnetlib/repo/manager/controllers/PiWikController.java +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/PiWikController.java @@ -39,16 +39,6 @@ public class PiWikController { @Autowired private PiWikServiceImpl piWikService; - @RequestMapping(value = "/validated", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public Boolean isPiwikValidated(@RequestParam("repositoryId") String repositoryId) { - PiwikInfo info = piWikService.getPiwikSiteForRepo(repositoryId); - if (info != null) { - return info.isValidated(); - } - return false; - } - @RequestMapping(value = "/getPiwikSiteForRepo/{repositoryId}" , method = RequestMethod.GET,produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody @PreAuthorize("hasAnyAuthority('SUPER_ADMINISTRATOR', 'CONTENT_PROVIDER_DASHBOARD_ADMINISTRATOR') or @authorizationService.isMemberOf(#repositoryId) or (@repositoryService.getRepositoryById(#repositoryId).registeredby==null and hasAuthority('REGISTERED_USER'))") diff --git a/src/main/java/eu/dnetlib/repo/manager/controllers/RepositoryController.java b/src/main/java/eu/dnetlib/repo/manager/controllers/RepositoryController.java index 5702eb2..d36a915 100644 --- a/src/main/java/eu/dnetlib/repo/manager/controllers/RepositoryController.java +++ b/src/main/java/eu/dnetlib/repo/manager/controllers/RepositoryController.java @@ -1,5 +1,6 @@ package eu.dnetlib.repo.manager.controllers; +import eu.dnetlib.domain.data.PiwikInfo; import eu.dnetlib.enabling.datasources.common.AggregationInfo; import eu.dnetlib.repo.manager.domain.*; import eu.dnetlib.repo.manager.domain.dto.RepositoryTerms; @@ -7,6 +8,8 @@ import eu.dnetlib.repo.manager.domain.dto.User; import eu.dnetlib.repo.manager.exception.RepositoryServiceException; import eu.dnetlib.repo.manager.exception.ResourceNotFoundException; import eu.dnetlib.repo.manager.service.AggregationService; +import eu.dnetlib.repo.manager.service.PiWikService; +import eu.dnetlib.repo.manager.service.PiWikServiceImpl; import eu.dnetlib.repo.manager.service.RepositoryService; import eu.dnetlib.repo.manager.service.security.AuthorizationService; import eu.dnetlib.repo.manager.utils.JsonUtils; @@ -43,12 +46,17 @@ public class RepositoryController { private final AuthorizationService authorizationService; + private final PiWikService piWikService; + @Autowired RepositoryController(RepositoryService repositoryService, - AggregationService aggregationService, AuthorizationService authorizationService) { + AggregationService aggregationService, + AuthorizationService authorizationService, + PiWikService piWikService) { this.repositoryService = repositoryService; this.aggregationService = aggregationService; this.authorizationService = authorizationService; + this.piWikService = piWikService; } @RequestMapping(value = "/countries", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @@ -295,4 +303,26 @@ public class RepositoryController { authorizationService.removeAdmin(id, email); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Returns whether the Piwik Site of a repository is enabled and validated. + */ + @RequestMapping(value = "{repositoryId}/metrics/valid", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + public boolean getMetricsEnabledAndValidated(@PathVariable("repositoryId") String repositoryId) { + PiwikInfo info = piWikService.getPiwikSiteForRepo(repositoryId); + return info != null && info.isValidated(); + } + + /** + * Returns repository Metrics. + */ + @RequestMapping(value = "{repositoryId}/metrics", method = RequestMethod.GET, + produces = MediaType.APPLICATION_JSON_VALUE) + public MetricsInfo getMetricsInfo(@PathVariable("repositoryId") String id) throws RepositoryServiceException { + return repositoryService.getMetricsInfoForRepository(id); + } + } From bd315848e040e3c2784f66cdade92baec031c09a Mon Sep 17 00:00:00 2001 From: Konstantinos Spyrou Date: Wed, 11 Oct 2023 14:53:44 +0300 Subject: [PATCH 28/30] fixed typo --- .../dnetlib/repo/manager/config/AaiSecurityConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java b/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java index 0124dee..407d6b4 100644 --- a/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java +++ b/src/main/java/eu/dnetlib/repo/manager/config/AaiSecurityConfiguration.java @@ -63,7 +63,7 @@ public class AaiSecurityConfiguration extends WebSecurityConfigurerAdapter { .csrf().disable() .authorizeRequests() .regexMatchers("/actuator/.*").permitAll() - .regexMatchers("/repository/.*/metrics/?.*").permitAll() + .regexMatchers("/repositories/.*/metrics/?.*").permitAll() .regexMatchers("/metrics").permitAll() .antMatchers("/api-docs/**","/swagger-ui/**").permitAll() .anyRequest().authenticated() From a72c5b2fb81f46b77fcdb099bdd70d80ce0129c5 Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Tue, 31 Oct 2023 13:56:59 +0200 Subject: [PATCH 29/30] - Update dependencies. - Fix a file-extension in README. --- README.md | 2 +- pom.xml | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index b43d7f1..169b22b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ## Install and run: - Run **git clone** and then **cd uoa-repository-manager-service**. -- Provide all not-set or redacted configurations, inside the **src/main/resources/application.properties** file. +- Provide all not-set or redacted configurations, inside the **src/main/resources/application.yml** file. - Build the app with: `mvn clean install` - Run the app with: `java -jar ./target/uoa-repository-manager-service.jar` diff --git a/pom.xml b/pom.xml index c1b2772..27eafc4 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.16 + 2.7.17 4.0.0 @@ -16,7 +16,7 @@ jar - 2.7.16 + 2.7.17 @@ -77,7 +77,7 @@ org.apache.solr solr-solrj - 9.3.0 + 9.4.0 @@ -165,7 +165,7 @@ commons-io commons-io - 2.13.0 + 2.15.0 @@ -185,7 +185,7 @@ org.glassfish.jersey.core jersey-client - 2.40 + 2.41 @@ -199,7 +199,7 @@ org.json json - 20230618 + 20231013 @@ -251,7 +251,7 @@ org.springframework.session spring-session-data-redis - 2.7.3 + 2.7.4 @@ -265,7 +265,7 @@ org.apache.commons commons-pool2 - 2.11.1 + 2.12.0 @@ -277,7 +277,7 @@ com.thetransactioncompany cors-filter - 2.10 + 3.0 @@ -300,7 +300,7 @@ com.sun.xml.bind jaxb-impl - 2.3.8 + 2.3.9 @@ -334,7 +334,7 @@ org.springframework.session spring-session-bom - 2021.2.2 + 2021.2.3 pom import @@ -348,7 +348,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.7.0 + 3.11.0 1.8 1.8 @@ -358,12 +358,12 @@ org.apache.maven.plugins maven-war-plugin - 2.6 + 3.4.0 false - - --> + + --> org.springframework.boot From 0b8e4c4729bbcba6a4ad78a5f16bc4d63ed1b409 Mon Sep 17 00:00:00 2001 From: LSmyrnaios Date: Thu, 2 Nov 2023 14:39:10 +0200 Subject: [PATCH 30/30] Update the Sushilite-R5 dependency from Nexus. --- .../openaire-usage-stats-sushilite-r5-1.0.0.jar | Bin 53323 -> 0 bytes pom.xml | 9 +++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) delete mode 100644 libs/openaire-usage-stats-sushilite-r5-1.0.0.jar diff --git a/libs/openaire-usage-stats-sushilite-r5-1.0.0.jar b/libs/openaire-usage-stats-sushilite-r5-1.0.0.jar deleted file mode 100644 index d4d094bdeaa1684856a87ca78e4a66900d1eae70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53323 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

Y{IkMz!@L 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 BDWCuV diff --git a/pom.xml b/pom.xml index 27eafc4..cfa3754 100644 --- a/pom.xml +++ b/pom.xml @@ -145,6 +145,11 @@ dnet-openaire-usage-stats-sushilite 1.0.0-SNAPSHOT + + eu.dnetlib + dnet-openaire-usage-stats-sushilite-r5 + 1.1.0 + eu.dnetlib.dhp dnet-openaire-broker-common @@ -459,10 +464,10 @@ false - +