diff --git a/.github/workflows/ga-publication.yml b/.github/workflows/ga-publication.yml index fb7e7f5a4d..4f266b64bb 100755 --- a/.github/workflows/ga-publication.yml +++ b/.github/workflows/ga-publication.yml @@ -46,7 +46,7 @@ jobs: - name: "Configure GA Repository" uses: actions/setup-java@v1 with: - java-version: 11 + java-version: 17 server-id: maven-central server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index e782e3fc85..7642c0021a 100755 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -72,10 +72,10 @@ jobs: --token "$ENTANDO_BOT_TOKEN" \ ; #~ JDK - - name: "Set up JDK 11" + - name: "Set up JDK 17" uses: actions/setup-java@v1 with: - java-version: 11 + java-version: 17 #~ MAVEN CACHE - name: "Cache Maven packages" uses: actions/cache@v2 @@ -119,10 +119,10 @@ jobs: # ${{ secrets.ENTANDO_OPT_PPL_INSTALL_CMD }} # ~/ppl-run checkout-branch pr --lcd "$LOCAL_CLONE_DIR" # #~ JDK -# - name: "Set up JDK 11" +# - name: "Set up JDK 17" # uses: actions/setup-java@v1 # with: -# java-version: 11 +# java-version: 17 # #~ MAVEN CACHE # - name: "Cache Maven packages" # id: maven-cache diff --git a/.github/workflows/publication.yml b/.github/workflows/publication.yml index bcbc107a53..2dd993b034 100755 --- a/.github/workflows/publication.yml +++ b/.github/workflows/publication.yml @@ -10,17 +10,12 @@ env: ENTANDO_BOT_TOKEN: ${{ secrets.ENTANDO_BOT_TOKEN }} PR_CHECKER_PATH: ".github/pr-title-checker-config.json" - JDK_VERSION: 11 + JDK_VERSION: 17 BUILD_COMMANDS: mvn clean install; - DOCKER_EAP_IMAGE_BASE_NAME: entando/entando-de-app-eap DOCKER_TOMCAT_IMAGE_BASE_NAME: entando/entando-de-app-tomcat - DOCKER_WILDFLY_IMAGE_BASE_NAME: entando/entando-de-app-wildfly - DOCKER_EAP-WILDFLY_IMAGE_ARCHITECTURE: linux/amd64 DOCKER_TOMCAT_IMAGE_ARCHITECTURE: linux/amd64,linux/arm64 DOCKER_IMAGE_CONTEXT: . - DOCKER_EAP_IMAGE_FILE: Dockerfile.eap DOCKER_TOMCAT_IMAGE_FILE: Dockerfile.tomcat - DOCKER_WILDFLY_IMAGE_FILE: Dockerfile.wildfly DOCKER_IMAGE_PUSH: true jobs: @@ -58,23 +53,6 @@ jobs: - name: Build with Maven run: ${{ env.BUILD_COMMANDS }} - - name: Docker meta-eap - id: meta-eap - uses: docker/metadata-action@v4 - with: - images: | - ${{ env.DOCKER_EAP_IMAGE_BASE_NAME }} - tags: | - type=schedule - type=ref,event=branch - type=ref,event=pr,value={{base_ref}} - type=ref,event=tag - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - type=sha - type=raw,event=pr,value={{base_ref}} - - name: Docker meta-tomcat id: meta-tomcat uses: docker/metadata-action@v4 @@ -92,23 +70,6 @@ jobs: type=sha type=raw,event=pr,value={{base_ref}} - - name: Docker meta-wildfly - id: meta-wildfly - uses: docker/metadata-action@v4 - with: - images: | - ${{ env.DOCKER_WILDFLY_IMAGE_BASE_NAME }} - tags: | - type=schedule - type=ref,event=branch - type=ref,event=pr,value={{base_ref}} - type=ref,event=tag - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - type=sha - type=raw,event=pr,value={{base_ref}} - - name: Set up QEMU uses: docker/setup-qemu-action@v2 @@ -130,16 +91,6 @@ jobs: username: ${{ secrets.ENTANDO_RHT_DOCKER_USERNAME }} password: ${{ secrets.ENTANDO_RHT_DOCKER_PASSWORD }} - - name: Build and push eap Docker image - uses: docker/build-push-action@v4 - with: - context: ${{ env.DOCKER_IMAGE_CONTEXT }} - file: ${{ env.DOCKER_EAP_IMAGE_FILE }} - push: ${{ env.DOCKER_IMAGE_PUSH }} - tags: ${{ steps.meta-eap.outputs.tags }} - labels: ${{ steps.meta-eap.outputs.labels }} - platforms: ${{ env.DOCKER_EAP-WILDFLY_IMAGE_ARCHITECTURE }} - - name: Build tomcat Docker image for amd64 uses: docker/build-push-action@v4 with: @@ -168,14 +119,4 @@ jobs: push: ${{ env.DOCKER_IMAGE_PUSH }} tags: ${{ steps.meta-tomcat.outputs.tags }} labels: ${{ steps.meta-tomcat.outputs.labels }} - platforms: ${{ env.DOCKER_TOMCAT_IMAGE_ARCHITECTURE }} - - - name: Build and push wildfly Docker image - uses: docker/build-push-action@v4 - with: - context: ${{ env.DOCKER_IMAGE_CONTEXT }} - file: ${{ env.DOCKER_WILDFLY_IMAGE_FILE }} - push: ${{ env.DOCKER_IMAGE_PUSH }} - tags: ${{ steps.meta-wildfly.outputs.tags }} - labels: ${{ steps.meta-wildfly.outputs.labels }} - platforms: ${{ env.DOCKER_EAP-WILDFLY_IMAGE_ARCHITECTURE }} \ No newline at end of file + platforms: ${{ env.DOCKER_TOMCAT_IMAGE_ARCHITECTURE }} \ No newline at end of file diff --git a/Dockerfile.eap b/Dockerfile.eap deleted file mode 100644 index df815cde69..0000000000 --- a/Dockerfile.eap +++ /dev/null @@ -1,20 +0,0 @@ -FROM registry.hub.docker.com/entando/entando-eap73-clustered-base:7.2.0 -ARG VERSION - -### Required OpenShift Labels -LABEL name="Entando App" \ - maintainer="dev@entando.com" \ - vendor="Entando Inc." \ - version="${VERSION}" \ - release="7.3.0" \ - summary="Entando Application" \ - description="This Entando app engine application provides APIs and composition for Entando applications" - -COPY target/generated-resources/licenses /licenses -COPY target/generated-resources/licenses.xml / - -COPY webapp/target/*.war /opt/eap/standalone/deployments/ - -RUN $ENTANDO_COMMON_PATH/init-derby-from-war.sh - -RUN rm -rf /tmp/*.jpg diff --git a/Dockerfile.tomcat b/Dockerfile.tomcat index ddd8d6463a..8eb796f981 100644 --- a/Dockerfile.tomcat +++ b/Dockerfile.tomcat @@ -1,4 +1,4 @@ -FROM registry.hub.docker.com/entando/entando-tomcat-base:7.3.0 +FROM registry.hub.docker.com/entando/entando-tomcat-base:v7.3.1-ENG-5347-PR-29-BB-release-2F-7.3 ARG VERSION ### Required OpenShift Labels @@ -6,7 +6,7 @@ LABEL name="Entando App" \ maintainer="dev@entando.com" \ vendor="Entando Inc." \ version="${VERSION}" \ - release="7.3.0" \ + release="7.3.1" \ summary="Entando Application" \ description="This Entando app engine application provides APIs and composition for Entando applications" diff --git a/Dockerfile.wildfly b/Dockerfile.wildfly deleted file mode 100644 index ee27854dce..0000000000 --- a/Dockerfile.wildfly +++ /dev/null @@ -1,21 +0,0 @@ -FROM registry.hub.docker.com/entando/entando-wildfly17-base:7.2.0 - -ARG VERSION - -### Required OpenShift Labels -LABEL name="Entando App" \ - maintainer="dev@entando.com" \ - vendor="Entando Inc." \ - version="${VERSION}" \ - release="7.3.0" \ - summary="Entando Application" \ - description="This Entando app engine application provides APIs and composition for Entando applications" - -COPY target/generated-resources/licenses /licenses -COPY target/generated-resources/licenses.xml / - -COPY webapp/target/*.war /wildfly/standalone/deployments/ - -RUN $ENTANDO_COMMON_PATH/init-derby-from-war.sh - -RUN rm -rf /tmp/*.jpg diff --git a/admin-console/pom.xml b/admin-console/pom.xml index b8cb5051bf..4054735eb6 100644 --- a/admin-console/pom.xml +++ b/admin-console/pom.xml @@ -4,7 +4,7 @@ org.entando app-engine - 7.3.0 + 7.3.1 org.entando.entando entando-admin-console diff --git a/admin-console/src/main/webapp/404.jsp b/admin-console/src/main/webapp/404.jsp deleted file mode 100644 index 7788f7683e..0000000000 --- a/admin-console/src/main/webapp/404.jsp +++ /dev/null @@ -1 +0,0 @@ -<%@ page isErrorPage="true" %> diff --git a/admin-console/src/main/webapp/META-INF/MANIFEST.MF b/admin-console/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 5e9495128c..0000000000 --- a/admin-console/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/admin-console/src/main/webapp/META-INF/context.xml b/admin-console/src/main/webapp/META-INF/context.xml deleted file mode 100644 index 5bee3dc30f..0000000000 --- a/admin-console/src/main/webapp/META-INF/context.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/admin-console/src/main/webapp/error.jsp b/admin-console/src/main/webapp/error.jsp index 8bec65d6fd..dfa5b9dfd5 100644 --- a/admin-console/src/main/webapp/error.jsp +++ b/admin-console/src/main/webapp/error.jsp @@ -12,14 +12,14 @@ - Entando - Error + Error administration/bootstrap/css/bootstrap.min.css" media="screen" />
-

Entando - Error

+

Error

Go to Home

diff --git a/admin-console/src/main/webapp/index.jsp b/admin-console/src/main/webapp/index.jsp deleted file mode 100644 index 5e557b0dca..0000000000 --- a/admin-console/src/main/webapp/index.jsp +++ /dev/null @@ -1,4 +0,0 @@ -<%@ taglib uri="/aps-core" prefix="wp" %> - - - \ No newline at end of file diff --git a/admin-console/src/main/webapp/logout.jsp b/admin-console/src/main/webapp/logout.jsp index 5e4db796a1..5e557b0dca 100644 --- a/admin-console/src/main/webapp/logout.jsp +++ b/admin-console/src/main/webapp/logout.jsp @@ -1,3 +1,4 @@ <%@ taglib uri="/aps-core" prefix="wp" %> - - \ No newline at end of file + + + \ No newline at end of file diff --git a/cds-plugin/pom.xml b/cds-plugin/pom.xml index 9932ad875e..3e8185da63 100644 --- a/cds-plugin/pom.xml +++ b/cds-plugin/pom.xml @@ -5,7 +5,7 @@ org.entando app-engine - 7.3.0 + 7.3.1 entando-plugin-jpcds org.entando.entando.plugins diff --git a/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsConfiguration.java b/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsConfiguration.java index 331f74992e..e812a28b97 100644 --- a/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsConfiguration.java +++ b/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsConfiguration.java @@ -26,7 +26,7 @@ @Component @CdsActive(true) public class CdsConfiguration { - + @Value("${resourceRootURL}") private String baseURL; @Value("${resourceDiskRootFolder}") @@ -39,6 +39,10 @@ public class CdsConfiguration { private boolean enabled; @Value("${CDS_PUBLIC_URL:https://cds.entando.org}") private String cdsPublicUrl; + @Value("${CDS_PUBLIC_PATH:}") + private String cdsPublicPath; + @Value("${CDS_INTERNAL_PUBLIC_SECTION:}") + private String cdsInternalPublicSection; @Value("${CDS_PRIVATE_URL:https://cds.entando.org}") private String cdsPrivateUrl; @Value("${CDS_PATH:/api/v1}") @@ -51,6 +55,5 @@ public class CdsConfiguration { private String kcClientId; @Value("${KEYCLOAK_CLIENT_SECRET:}") private String kcClientSecret; - - + } diff --git a/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsRemoteCaller.java b/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsRemoteCaller.java index f017474496..17eba2842a 100644 --- a/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsRemoteCaller.java +++ b/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsRemoteCaller.java @@ -64,7 +64,8 @@ public class CdsRemoteCaller { private Map tenantsToken = new WeakHashMap<>(); @Autowired - public CdsRemoteCaller(@Qualifier("keycloakRestTemplate")RestTemplate restTemplate,@Qualifier("keycloakRestTemplateWithRedirect")RestTemplate restTemplateWithRedirect, + public CdsRemoteCaller(@Qualifier("keycloakRestTemplate")RestTemplate restTemplate, + @Qualifier("keycloakRestTemplateWithRedirect")RestTemplate restTemplateWithRedirect, CdsConfiguration configuration) { this.restTemplate = restTemplate; this.restTemplateWithRedirect = restTemplateWithRedirect; diff --git a/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsStorageManager.java b/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsStorageManager.java index 9cb0c81c2b..b11bea817b 100644 --- a/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsStorageManager.java +++ b/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsStorageManager.java @@ -13,6 +13,7 @@ */ package org.entando.entando.plugins.jpcds.aps.system.storage; +import com.agiletec.aps.system.SystemConstants; import com.agiletec.aps.util.ApsTenantApplicationUtils; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -23,6 +24,8 @@ import java.util.List; import java.util.Optional; import java.util.stream.Collectors; +import lombok.AccessLevel; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; @@ -41,17 +44,22 @@ import org.springframework.stereotype.Service; import org.springframework.web.client.HttpClientErrorException; import org.entando.entando.aps.system.services.storage.CdsActive; +import org.springframework.web.context.ContextLoader; +import org.springframework.web.context.WebApplicationContext; @Slf4j -@Service("StorageManager") +@Service(SystemConstants.STORAGE_MANAGER) @CdsActive(true) public class CdsStorageManager implements IStorageManager { private static final String ERROR_VALIDATING_PATH_MSG = "Error validating path"; - private final transient ITenantManager tenantManager; - private final transient CdsConfiguration configuration; - private final transient CdsRemoteCaller caller; - + + @Getter(AccessLevel.PROTECTED) + private transient ITenantManager tenantManager; + @Getter(AccessLevel.PROTECTED) + private transient CdsConfiguration configuration; + @Getter(AccessLevel.PROTECTED) + private transient CdsRemoteCaller caller; @Autowired public CdsStorageManager(CdsRemoteCaller caller, ITenantManager tenantManager, CdsConfiguration configuration) { @@ -73,21 +81,18 @@ public void saveFile(String subPath, boolean isProtectedResource, InputStream is private void create(String subPath, boolean isProtectedResource, Optional fileInputStream) { try { - Optional config = getTenantConfig(); + Optional config = this.getTenantConfig(); if(StringUtils.isBlank(subPath)){ throw new EntRuntimeException(ERROR_VALIDATING_PATH_MSG); } - - this.validateAndReturnResourcePath(config, subPath, isProtectedResource); - + this.validateAndReturnResourcePath(config, subPath, false, isProtectedResource); URI apiUrl = CdsUrlUtils.buildCdsInternalApiUrl(config, configuration, "/upload/"); - CdsCreateResponseDto response = caller.executePostCall(apiUrl, + CdsCreateResponseDto response = this.caller.executePostCall(apiUrl, subPath, isProtectedResource, fileInputStream, config, false); - if (!response.isStatusOk()) { throw new EntRuntimeException("Invalid status - Response " + response.isStatusOk()); } @@ -106,22 +111,18 @@ public void deleteDirectory(String subPath, boolean isProtectedResource) throws @Override public boolean deleteFile(String subPath, boolean isProtectedResource) { try { - Optional config = getTenantConfig(); - if(StringUtils.isBlank(subPath)){ + Optional config = this.getTenantConfig(); + if (StringUtils.isBlank(subPath)){ throw new EntRuntimeException(ERROR_VALIDATING_PATH_MSG); } - - this.validateAndReturnResourcePath(config, subPath, isProtectedResource); - + this.validateAndReturnResourcePath(config, subPath, true, isProtectedResource); URI apiUrl = EntUrlBuilder.builder() .url(CdsUrlUtils.buildCdsInternalApiUrl(config, configuration)) .path("/delete/") - .path(CdsUrlUtils.getInternalSection(isProtectedResource)) + .path(CdsUrlUtils.getSection(isProtectedResource, config, this.configuration, true)) .path(subPath) .build(); - - return caller.executeDeleteCall(apiUrl, config, false); - + return this.caller.executeDeleteCall(apiUrl, config, false); } catch (EntRuntimeException ert) { throw ert; } catch (Exception e) { @@ -134,32 +135,31 @@ public InputStream getStream(String subPath, boolean isProtectedResource) throws final String ERROR_EXTRACTING_FILE = "Error extracting file"; URI url = null; try { - Optional config = getTenantConfig(); - if(StringUtils.isBlank(subPath)){ + Optional config = this.getTenantConfig(); + if (StringUtils.isBlank(subPath)) { throw new EntRuntimeException(ERROR_VALIDATING_PATH_MSG); } - - this.validateAndReturnResourcePath(config, subPath, isProtectedResource); - + if (!this.exists(subPath, isProtectedResource)) { + throw new EntResourceNotFoundException( + String.format("File \"%s\", protected \"%s\", Not Found", subPath, isProtectedResource)); + } + this.validateAndReturnResourcePath(config, subPath, true, isProtectedResource); url = (isProtectedResource) ? CdsUrlUtils.buildCdsInternalApiUrl(config, configuration) : CdsUrlUtils.buildCdsExternalPublicResourceUrl(config, configuration); - url = EntUrlBuilder.builder() .url(url) - .path(CdsUrlUtils.getInternalSection(isProtectedResource)) + .path(CdsUrlUtils.getSection(isProtectedResource, config, this.configuration, true)) .path(subPath).build(); - Optional is = caller.getFile(url, config, isProtectedResource); - return is.orElseThrow(IOException::new); - - } catch (EntRuntimeException ert) { + return is.orElse(new ByteArrayInputStream(new byte[0])); + } catch (EntResourceNotFoundException | EntRuntimeException ert) { throw ert; } catch (HttpClientErrorException e) { if (e.getStatusCode().equals(HttpStatus.NOT_FOUND)) { log.info("File Not found - uri {}", url); return null; - } + } throw new EntResourceNotFoundException(ERROR_EXTRACTING_FILE, e); } catch (Exception e) { throw new EntResourceNotFoundException(ERROR_EXTRACTING_FILE, e); @@ -169,8 +169,8 @@ public InputStream getStream(String subPath, boolean isProtectedResource) throws @Override public String getResourceUrl(String subPath, boolean isProtectedResource) { try { - Optional config = getTenantConfig(); - return this.validateAndReturnResourcePath(config, subPath, isProtectedResource); + Optional config = this.getTenantConfig(); + return this.validateAndReturnResourcePath(config, subPath, false, isProtectedResource); } catch (Exception e) { throw new EntRuntimeException("Error extracting resource url", e); } @@ -183,9 +183,33 @@ public boolean exists(String subPath, boolean isProtectedResource) { return (null != filenames && isSubPathPresent(filenames,subPathParsed.getFileName())); } + @Override + public boolean move(String subPathSource, boolean isProtectedResourceSource, String subPathDest, boolean isProtectedResourceDest) throws EntException { + if (!this.exists(subPathSource, isProtectedResourceSource)) { + log.error(String.format( + "Source File does not exists - path '%s' protected '%s'", + subPathSource, isProtectedResourceSource)); + return false; + } + if (this.exists(subPathDest, isProtectedResourceDest)) { + log.error(String.format( + "Destination already exists - path '%s' protected '%s'", + subPathDest, isProtectedResourceDest)); + return false; + } + try { + InputStream stream = this.getStream(subPathSource, isProtectedResourceSource); + this.saveFile(subPathDest, isProtectedResourceDest, stream); + this.deleteFile(subPathSource, isProtectedResourceSource); + } catch (Exception e) { + throw new EntException("Error moving file", e); + } + return true; + } + // when frontend wants to retrieve public or protected folder contents it gets request with an empty subpath private boolean isSubPathPresent(String[] filenames, String subPath){ - if(StringUtils.isEmpty(subPath)) { + if (StringUtils.isEmpty(subPath)) { return filenames.length > 0; } else { return Arrays.asList(filenames).contains(subPath); @@ -240,15 +264,15 @@ public BasicFileAttributeView[] listFileAttributes(String subPath, boolean isPro private List listAttributes(String subPath, boolean isProtectedResource, CdsFilter filter) { Optional config = this.getTenantConfig(); - this.validateAndReturnResourcePath(config, subPath, isProtectedResource); + this.validateAndReturnResourcePath(config, subPath, true, isProtectedResource); URI apiUrl = EntUrlBuilder.builder() .url(CdsUrlUtils.buildCdsInternalApiUrl(config, configuration).toString()) .path("/list/") - .path(CdsUrlUtils.getInternalSection(isProtectedResource)) + .path(CdsUrlUtils.getSection(isProtectedResource, config, this.configuration, true)) .path(subPath) .build(); - + Optional cdsFileList = caller.getFileAttributeView(apiUrl, config); return remapAndSort(cdsFileList, filter); @@ -310,22 +334,19 @@ private Optional getTenantConfig() { } - private String validateAndReturnResourcePath(Optional config, String resourceRelativePath, boolean privateUrl) { + private String validateAndReturnResourcePath(Optional config, String resourceRelativePath, boolean privateCall, boolean privateUrl) { try { String baseUrl = EntUrlBuilder.builder() .url(CdsUrlUtils.fetchBaseUrl(config, configuration, privateUrl)) - .path(CdsUrlUtils.getInternalSection(privateUrl)) // << this is part of base url because we want check path traversal!! + .path(CdsUrlUtils.getSection(privateUrl, config, this.configuration, privateCall)) .build().toString(); - String fullPath = EntUrlBuilder.builder() .url(baseUrl) .path(resourceRelativePath) .build().toString(); - if (!StorageManagerUtil.doesPathContainsPath(baseUrl, fullPath, true)) { throw mkPathValidationErr(baseUrl, fullPath); } - return fullPath; } catch (IOException e) { throw new EntRuntimeException(ERROR_VALIDATING_PATH_MSG, e); @@ -355,4 +376,18 @@ public enum CdsFilter { DIRECTORY, ALL; } + + private void readObject(java.io.ObjectInputStream in) + throws IOException, ClassNotFoundException { + in.defaultReadObject(); + WebApplicationContext ctx = ContextLoader.getCurrentWebApplicationContext(); + if (ctx == null) { + log.warn("Null WebApplicationContext during deserialization"); + return; + } + this.tenantManager = ctx.getBean(ITenantManager.class); + this.configuration = ctx.getBean(CdsConfiguration.class); + this.caller = ctx.getBean(CdsRemoteCaller.class); + } + } diff --git a/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsUrlUtils.java b/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsUrlUtils.java index b62d3d5a47..8171c94549 100644 --- a/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsUrlUtils.java +++ b/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsUrlUtils.java @@ -29,28 +29,34 @@ public final class CdsUrlUtils { private static final String CDS_PUBLIC_URL_TENANT_PARAM = "cdsPublicUrl"; private static final String CDS_PRIVATE_URL_TENANT_PARAM = "cdsPrivateUrl"; + private static final String CDS_PUBLIC_PATH_TENANT_PARAM = "cdsPublicPath"; + private static final String CDS_INTERNAL_PUBLIC_SECTION_TENANT_PARAM = "cdsInternalPublicSection"; private static final String CDS_PATH_TENANT_PARAM = "cdsPath"; private static final String URL_SEP = "/"; - private static final String SECTION_PUBLIC = "/public"; - private static final String SECTION_PRIVATE = "/protected"; + private static final String DEFAULT_SECTION_PUBLIC = ""; + private static final String DEFAULT_SECTION_PRIVATE = "/protected"; private CdsUrlUtils(){ } - - public static String getInternalSection(boolean isProtectedResource) { - return (isProtectedResource) ? SECTION_PRIVATE : SECTION_PUBLIC; + + public static String getSection(boolean isProtectedResource, Optional config, CdsConfiguration configuration, boolean internalCall) { + if (isProtectedResource) { + return DEFAULT_SECTION_PRIVATE; + } + if (internalCall) { + return config.map(c -> c.getProperty(CDS_INTERNAL_PUBLIC_SECTION_TENANT_PARAM).orElse(DEFAULT_SECTION_PUBLIC)).orElse(configuration.getCdsInternalPublicSection()); + } else { + return config.map(c -> c.getProperty(CDS_PUBLIC_PATH_TENANT_PARAM).orElse(DEFAULT_SECTION_PUBLIC)).orElse(configuration.getCdsPublicPath()); + } } - + public static URI buildCdsExternalPublicResourceUrl(Optional config, CdsConfiguration configuration, String ... paths){ log.debug("Trying to build CDS external public url with is tenant config empty:'{}', CDS primary configuration public url:'{}' and paths:'{}'", config.isEmpty(), configuration.getCdsPublicUrl(), paths); - String publicUrl = config.flatMap(c -> c.getProperty(CDS_PUBLIC_URL_TENANT_PARAM)).orElse(configuration.getCdsPublicUrl()); - return EntUrlBuilder.builder().url(publicUrl).paths(paths).build(); - } public static URI buildCdsInternalApiUrl(Optional config, CdsConfiguration configuration, String ... paths){ @@ -59,10 +65,8 @@ public static URI buildCdsInternalApiUrl(Optional config, CdsConfi configuration.getCdsPrivateUrl(), configuration.getCdsPath(), paths); - String apiUrl = config.flatMap(c -> c.getProperty(CDS_PRIVATE_URL_TENANT_PARAM)).orElse(configuration.getCdsPrivateUrl()); String basePath = config.flatMap(c -> c.getProperty(CDS_PATH_TENANT_PARAM)).orElse(configuration.getCdsPath()); - return EntUrlBuilder.builder().url(apiUrl).path(basePath).paths(paths).build(); } diff --git a/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsConfigurationTest.java b/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsConfigurationTest.java index 13a9e0f698..5fff824a7f 100644 --- a/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsConfigurationTest.java +++ b/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsConfigurationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-Present Entando S.r.l. (http://www.entando.com) All rights reserved. + * Copyright 2024-Present Entando S.r.l. (http://www.entando.com) All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -15,7 +15,6 @@ import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Value; class CdsConfigurationTest { diff --git a/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsCreateRowResponseDtoTest.java b/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsCreateRowResponseDtoTest.java index 7cc7c9c467..f0819dc938 100644 --- a/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsCreateRowResponseDtoTest.java +++ b/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsCreateRowResponseDtoTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-Present Entando S.r.l. (http://www.entando.com) All rights reserved. + * Copyright 2024-Present Entando S.r.l. (http://www.entando.com) All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -13,7 +13,6 @@ */ package org.entando.entando.plugins.jpcds.aps.system.storage; - import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; diff --git a/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsFileAttributeViewDtoTest.java b/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsFileAttributeViewDtoTest.java index c87b6212a2..51f636938f 100644 --- a/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsFileAttributeViewDtoTest.java +++ b/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsFileAttributeViewDtoTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-Present Entando S.r.l. (http://www.entando.com) All rights reserved. + * Copyright 2024-Present Entando S.r.l. (http://www.entando.com) All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -13,13 +13,11 @@ */ package org.entando.entando.plugins.jpcds.aps.system.storage; - import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.util.HashMap; import java.util.Map; -import lombok.ToString; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsRemoteCallerTest.java b/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsRemoteCallerTest.java index 3e3d392740..28c137c812 100644 --- a/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsRemoteCallerTest.java +++ b/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsRemoteCallerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved. + * Copyright 2024-Present Entando Inc. (http://www.entando.com) All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -31,10 +31,7 @@ import org.apache.commons.io.IOUtils; import org.entando.entando.aps.system.services.tenants.TenantConfig; import org.entando.entando.ent.exception.EntRuntimeException; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; diff --git a/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsStorageManagerTest.java b/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsStorageManagerTest.java index 3e4e0a483c..355eb7cffd 100644 --- a/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsStorageManagerTest.java +++ b/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsStorageManagerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved. + * Copyright 2024-Present Entando Inc. (http://www.entando.com) All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -35,6 +35,7 @@ import org.entando.entando.aps.system.services.tenants.ITenantManager; import org.entando.entando.aps.system.services.tenants.TenantConfig; import org.entando.entando.ent.exception.EntException; +import org.entando.entando.ent.exception.EntResourceNotFoundException; import org.entando.entando.ent.exception.EntRuntimeException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -151,7 +152,6 @@ void shouldCreateFile() throws Exception { CdsCreateResponseDto ret = new CdsCreateResponseDto(); ret.setStatusOk(true); - ArgumentCaptor captor = ArgumentCaptor.forClass(URI.class); Mockito.when(cdsRemoteCaller.executePostCall(any(), eq("/sub-path-testy/myfilename"), eq(false), @@ -226,6 +226,8 @@ void shouldManageErrorWhenCallGetStream() throws Exception { final String baseUrl = "http://my-server/tenant1/cms-resources"; Map configMap = Map.of("cdsPublicUrl", baseUrl, "cdsPrivateUrl","http://cds-kube-service:8081/", + "cdsPublicPath","/custom-path", + "cdsInternalPublicSection", "/custom-path", "cdsPath","/mytenant/api/v1/"); TenantConfig tc = new TenantConfig(configMap); Mockito.when(tenantManager.getConfig("my-tenant")).thenReturn(Optional.ofNullable(tc)); @@ -236,17 +238,22 @@ void shouldManageErrorWhenCallGetStream() throws Exception { ).isInstanceOf(EntRuntimeException.class).hasMessageStartingWith("Error validating path"); String testFilePath = "/testfolder/test.txt"; - URI testFile = URI.create( baseUrl + "/public" + testFilePath); + + Assertions.assertThatThrownBy( + ()-> cdsStorageManager.getStream(testFilePath, false) + ).isInstanceOf(EntResourceNotFoundException.class).hasMessageStartingWith("File \"" + testFilePath); + + URI testFile = URI.create( baseUrl + "/custom-path" + testFilePath); + this.mockExecuteListFolder("http://cds-kube-service:8081/mytenant/api/v1/list/protected/testfolder", "test.txt"); Mockito.when(cdsRemoteCaller.getFile(eq(testFile), any(), eq(false))).thenReturn(null); Assertions.assertThatThrownBy( ()-> cdsStorageManager.getStream(testFilePath,false) - ).isInstanceOf(EntException.class).hasMessageStartingWith("Error extracting file"); - - + ).isInstanceOf(EntResourceNotFoundException.class).hasMessageStartingWith("Error extracting file"); + String testFilePathBadGateway = "/testfolder/test-badgw.txt"; - URI testFileBadGateway = URI.create( baseUrl + "/public" + testFilePathBadGateway); + URI testFileBadGateway = URI.create( baseUrl + "/custom-path" + testFilePathBadGateway); Mockito.when(cdsRemoteCaller.getFile(eq(testFileBadGateway), any(), eq(false))).thenThrow(new HttpClientErrorException(HttpStatus.BAD_GATEWAY)); @@ -255,15 +262,9 @@ void shouldManageErrorWhenCallGetStream() throws Exception { ()-> cdsStorageManager.getStream(testFilePathBadGateway,false) ).isInstanceOf(EntException.class).hasMessageStartingWith("Error extracting file"); - String testFilePathNotFound = "/testfolder/test-notfound.txt"; - URI testFileNotFound = URI.create( baseUrl + "/public" + testFilePathNotFound); - Mockito.when(cdsRemoteCaller.getFile(eq(testFileNotFound), - any(), - eq(false))).thenThrow(new HttpClientErrorException(HttpStatus.NOT_FOUND)); - - Assertions.assertThat(cdsStorageManager.getStream(testFilePathNotFound,false)).isNull(); - + Assertions.assertThatThrownBy(() -> cdsStorageManager.getStream(testFilePathNotFound,false)) + .isInstanceOf(EntResourceNotFoundException.class).hasMessageStartingWith("Error extracting file"); } @Test @@ -275,32 +276,33 @@ void shouldReturnDataWhenCallGetStream() throws Exception { "cdsPath","/mytenant/api/v1/"); TenantConfig tc = new TenantConfig(configMap); Mockito.when(tenantManager.getConfig("my-tenant")).thenReturn(Optional.ofNullable(tc)); - - Mockito.when(cdsRemoteCaller.getFile(eq(URI.create("http://my-server/tenant1/cms-resources/public/test-folder/test.txt")), + + Mockito.when(cdsRemoteCaller.getFile(eq(URI.create("http://my-server/tenant1/cms-resources/test-folder/test.txt")), any(), eq(false))).thenReturn(Optional.ofNullable(new ByteArrayInputStream("text random".getBytes(StandardCharsets.UTF_8)))); - + + this.mockExecuteListFolder("http://cds-kube-service:8081/mytenant/api/v1/list/test-folder", "test.txt"); ApsTenantApplicationUtils.setTenant("my-tenant"); InputStream is = cdsStorageManager.getStream(testFilePath,false); Assertions.assertThat(new BufferedReader(new InputStreamReader(is)) .lines().collect(Collectors.joining(""))).isEqualTo("text random"); - + Mockito.when(cdsRemoteCaller.getFile(eq(URI.create("http://cds-kube-service:8081/mytenant/api/v1/protected/test-folder/test.txt")), any(), eq(true))).thenReturn(Optional.ofNullable(new ByteArrayInputStream("text random".getBytes(StandardCharsets.UTF_8)))); - + + this.mockExecuteListFolder("http://cds-kube-service:8081/mytenant/api/v1/list/protected/test-folder", "test.txt"); is = cdsStorageManager.getStream(testFilePath,true); Assertions.assertThat(new BufferedReader(new InputStreamReader(is)) .lines().collect(Collectors.joining(""))).isEqualTo("text random"); - } @Test void shouldReturnRightUrlWhenCallGetResourceUrl() throws Exception { String testFilePath = "/test-folder/test.txt"; - Map configMap = Map.of("cdsPublicUrl","http://my-server/tenant1/cms-resources", "cdsPrivateUrl","http://cds-tenant1-kube-service:8081/", + "cdsPublicPath","/public", "cdsPath","/mytenant/api/v1/"); TenantConfig tc = new TenantConfig(configMap); Mockito.when(tenantManager.getConfig("my-tenant")).thenReturn(Optional.ofNullable(tc)); @@ -314,7 +316,6 @@ void shouldReturnRightUrlWhenCallGetResourceUrl() throws Exception { resourceUrl = cdsStorageManager.createFullPath(testFilePath,true); Assertions.assertThat(resourceUrl).isEqualTo("http://cds-tenant1-kube-service:8081/protected/test-folder/test.txt"); - } @Test @@ -359,15 +360,15 @@ void shouldWorkFineWhenCallExists() throws Exception { "http://cds-tenant1-kube-service:8081/mytenant/api/v1/list/protected/test-folder")), any())).thenReturn(Optional.ofNullable(new CdsFileAttributeViewDto[]{file})); Assertions.assertThat(cdsStorageManager.exists(testFilePath,true)).isTrue(); - } @Test void shouldWorkFineWhenCallExistsWithRootAsEmpty() throws Exception { String testFilePath = ""; - Map configMap = Map.of("cdsPublicUrl","http://my-server/tenant1/cms-resources", "cdsPrivateUrl","http://cds-tenant1-kube-service:8081/", + "cdsPublicPath","/public", + "cdsInternalPublicSection", "/public", "cdsPath","/mytenant/api/v1/"); TenantConfig tc = new TenantConfig(configMap); Mockito.when(tenantManager.getConfig("my-tenant")).thenReturn(Optional.ofNullable(tc)); @@ -467,7 +468,7 @@ void shouldReadFile() throws Exception { String testFilePath = "/testfolder/test.txt"; Map configMap = Map.of("cdsPublicUrl","http://my-server/tenant1/cms-resources", - "cdsPrivateUrl","http://cds-kube-service:8081/", + "cdsPrivateUrl","http://cdsmaster-kube-service:8081/", "cdsPath","/mytenant/api/v1/"); TenantConfig tc = new TenantConfig(configMap); Mockito.when(tenantManager.getConfig("my-tenant")).thenReturn(Optional.ofNullable(tc)); @@ -475,8 +476,9 @@ void shouldReadFile() throws Exception { Mockito.when(cdsRemoteCaller.getFile(any(), any(), eq(false))).thenReturn(Optional.ofNullable(new ByteArrayInputStream("text random".getBytes(StandardCharsets.UTF_8)))); - - + + this.mockExecuteListFolder("http://cdsmaster-kube-service:8081/mytenant/api/v1/list/testfolder", "test.txt"); + ApsTenantApplicationUtils.setTenant("my-tenant"); Assertions.assertThat(cdsStorageManager.readFile(testFilePath,false)) .isEqualTo("text random"); @@ -484,7 +486,7 @@ void shouldReadFile() throws Exception { @Test void shouldManageExceptionWhenReadFile() throws Exception { - String testFilePath = "/testfolder/test.txt"; + String testFilePath = "/testfolder/subfolder/test.txt"; Map configMap = Map.of("cdsPublicUrl","http://my-server/tenant1/cms-resources", "cdsPrivateUrl","http://cds-kube-service:8081/", @@ -501,7 +503,7 @@ void shouldManageExceptionWhenReadFile() throws Exception { try (MockedStatic ioUtils = Mockito.mockStatic(IOUtils.class)) { ioUtils.when(() -> IOUtils.toString(any(InputStream.class), eq(StandardCharsets.UTF_8))) .thenThrow(new IOException()); - + this.mockExecuteListFolder("http://cds-kube-service:8081/mytenant/api/v1/list/testfolder/subfolder", "test.txt"); Assertions.assertThatThrownBy(() -> cdsStorageManager.readFile(testFilePath, false)) .isInstanceOf(EntException.class) .hasMessageStartingWith("Error extracting text"); @@ -555,7 +557,74 @@ void shouldEditFile() throws Exception { eq(false)); } - + + @Test + void shouldMoveFile() throws Exception { + this.initTenantForMovement(); + this.setExistingFileForMovement("source_file.txt", false, "test", false); + this.setExistingFileForMovement("dest_file.txt", true, "test_dest", true); + CdsCreateResponseDto ret = new CdsCreateResponseDto(); + ret.setStatusOk(true); + Mockito.when(cdsRemoteCaller.executePostCall(any(), + eq("test_dest/dest_file.txt"), + eq(true), + any(), + any(), + eq(false))).thenReturn(ret); + boolean result = this.cdsStorageManager.move("test/source_file.txt", false, "test_dest/dest_file.txt", true); + Assertions.assertThat(result).isTrue(); + Mockito.verify(this.cdsRemoteCaller, Mockito.times(3)).getFileAttributeView(Mockito.any(URI.class), Mockito.any()); + Mockito.verify(this.cdsRemoteCaller, Mockito.times(1)).getFile(Mockito.any(URI.class), Mockito.any(), Mockito.anyBoolean()); + } + + @Test + void shouldFailMovementDueMissingSource() throws Exception { + this.initTenantForMovement(); + boolean result = this.cdsStorageManager.move("test/source_file.txt", false, "test_dest/dest_file.txt", false); + Assertions.assertThat(result).isFalse(); + Mockito.verify(this.cdsRemoteCaller, Mockito.times(1)).getFileAttributeView(Mockito.any(URI.class), Mockito.any()); + Mockito.verify(this.cdsRemoteCaller, Mockito.times(0)).getFile(Mockito.any(URI.class), Mockito.any(), Mockito.anyBoolean()); + } + + @Test + void shouldFailMovementDueExistingDestination() throws Exception { + this.initTenantForMovement(); + this.setExistingFileForMovement("source_file.txt", false, "test", false); + this.setExistingFileForMovement("dest_file.txt", true, "test_dest", false); + boolean result = this.cdsStorageManager.move("test/source_file.txt", false, "test_dest/dest_file.txt", true); + Assertions.assertThat(result).isFalse(); + Mockito.verify(this.cdsRemoteCaller, Mockito.times(2)).getFileAttributeView(Mockito.any(URI.class), Mockito.any()); + Mockito.verify(this.cdsRemoteCaller, Mockito.times(0)).getFile(Mockito.any(URI.class), Mockito.any(), Mockito.anyBoolean()); + } + + private void initTenantForMovement() { + Map configMap = Map.of("cdsPublicUrl","http://my-server/tenant1/cms-resources", + "cdsPrivateUrl","http://cds-kube-service:8081/", + "cdsPath","/mytenant/api/v1/"); + TenantConfig tc = new TenantConfig(configMap); + Mockito.when(tenantManager.getConfig("my-tenant")).thenReturn(Optional.ofNullable(tc)); + ApsTenantApplicationUtils.setTenant("my-tenant"); + } + + private void setExistingFileForMovement(String existingFileName, boolean isProtected, String path, boolean returnEmpty) { + String subPath = ((isProtected) ? "protected/" : "") + path; + if (returnEmpty) { + Mockito.when(cdsRemoteCaller.getFileAttributeView(eq(URI.create( + "http://cds-kube-service:8081/mytenant/api/v1/list/" + subPath)), + any())).thenReturn(Optional.empty()); + return; + } + CdsFileAttributeViewDto file = new CdsFileAttributeViewDto(); + file.setName(existingFileName); + file.setDirectory(false); + CdsFileAttributeViewDto dir = new CdsFileAttributeViewDto(); + dir.setName("test-folder"); + dir.setDirectory(true); + Mockito.when(cdsRemoteCaller.getFileAttributeView(eq(URI.create( + "http://cds-kube-service:8081/mytenant/api/v1/list/" + subPath)), + any())).thenReturn(Optional.ofNullable(new CdsFileAttributeViewDto[]{file, dir})); + } + //@Test void testListAttributes() throws Throwable { Map configMap = Map.of("cdsPublicUrl","http://my-server/tenant1/cms-resources", @@ -586,5 +655,12 @@ void testListAttributes() throws Throwable { } assertTrue(containsCms); } + + private void mockExecuteListFolder(String uri, String filename) { + CdsFileAttributeViewDto file = new CdsFileAttributeViewDto(); + file.setName(filename); + Mockito.when(cdsRemoteCaller.getFileAttributeView(eq(URI.create(uri)), + any())).thenReturn(Optional.ofNullable(new CdsFileAttributeViewDto[]{file})); + } } diff --git a/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsStorageSerializationIntegrationTest.java b/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsStorageSerializationIntegrationTest.java new file mode 100644 index 0000000000..3cfd6d4236 --- /dev/null +++ b/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsStorageSerializationIntegrationTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2024-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ +package org.entando.entando.plugins.jpcds.aps.system.storage; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import org.entando.entando.aps.system.services.tenants.ITenantManager; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.context.ContextLoader; +import org.springframework.web.context.WebApplicationContext; + +@ExtendWith(MockitoExtension.class) +class CdsStorageSerializationIntegrationTest { + + private CdsStorageManager cdsStorageManager; + + @BeforeEach + public void init() { + CdsRemoteCaller cdsRemoteCaller = new CdsRemoteCaller(Mockito.mock(RestTemplate.class), + Mockito.mock(RestTemplate.class), Mockito.mock(CdsConfiguration.class)); + cdsStorageManager = new CdsStorageManager(cdsRemoteCaller, Mockito.mock(ITenantManager.class), Mockito.mock(CdsConfiguration.class)); + } + + @Test + void testSerializeStorageManager() throws Exception { + Assertions.assertNotNull(this.cdsStorageManager.getCaller()); + Assertions.assertNotNull(this.cdsStorageManager.getTenantManager()); + Assertions.assertNotNull(this.cdsStorageManager.getConfiguration()); + CdsStorageManager badProcessed = testSerializeAndDeserializeNullApplicationContext(cdsStorageManager); + Assertions.assertNull(badProcessed.getCaller()); + Assertions.assertNull(badProcessed.getTenantManager()); + Assertions.assertNull(badProcessed.getConfiguration()); + + CdsStorageManager processed = testSerializeAndDeserializeMockApplicationContext(cdsStorageManager); + Assertions.assertNotNull(processed.getCaller()); + Assertions.assertNotNull(processed.getTenantManager()); + Assertions.assertNotNull(processed.getConfiguration()); + } + + private T testSerializeAndDeserializeNullApplicationContext(T object) throws Exception { + try (MockedStatic contextLoader = Mockito.mockStatic(ContextLoader.class)) { + contextLoader.when(() -> ContextLoader.getCurrentWebApplicationContext()).thenReturn(null); + return testSerializeAndDeserialize(object); + } + } + + private T testSerializeAndDeserializeMockApplicationContext(T object) throws Exception { + try (MockedStatic contextLoader = Mockito.mockStatic(ContextLoader.class)) { + WebApplicationContext ctx = Mockito.mock(WebApplicationContext.class); + contextLoader.when(() -> ContextLoader.getCurrentWebApplicationContext()).thenReturn(ctx); + Mockito.when(ctx.getBean(ITenantManager.class)).thenReturn(Mockito.mock(ITenantManager.class)); + Mockito.when(ctx.getBean(CdsConfiguration.class)).thenReturn(Mockito.mock(CdsConfiguration.class)); + Mockito.when(ctx.getBean(CdsRemoteCaller.class)).thenReturn(Mockito.mock(CdsRemoteCaller.class)); + return testSerializeAndDeserialize(object); + } + } + + private T testSerializeAndDeserialize(T object) throws Exception { + byte[] data; + try (ByteArrayOutputStream os = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(os)) { + objectOutputStream.writeObject(object); + data = os.toByteArray(); + } + try (ByteArrayInputStream is = new ByteArrayInputStream(data); ObjectInputStream objectInputStream = new ObjectInputStream(is)) { + return (T) objectInputStream.readObject(); + } + } + +} diff --git a/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsUtilsTest.java b/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsUtilsTest.java index 241852a10a..d80e050fad 100644 --- a/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsUtilsTest.java +++ b/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved. + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -13,30 +13,24 @@ */ package org.entando.entando.plugins.jpcds.aps.system.storage; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import org.entando.entando.aps.system.services.tenants.TenantConfig; import org.entando.entando.plugins.jpcds.aps.system.storage.CdsUrlUtils.EntSubPath; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) class CdsUtilsTest { - @BeforeEach - private void init() throws Exception { - } - - @AfterEach - public void afterAll() throws Exception { - } - @Test void shouldExtractPathAndFilename() throws Exception { EntSubPath subPath = CdsUrlUtils.extractPathAndFilename("/folder1/folder2/file.txt"); Assertions.assertEquals("file.txt", subPath.getFileName()); - //Assertions.assertEquals("folder1/folder2/", subPath.getPath()); Assertions.assertEquals("/folder1/folder2", subPath.getPath()); subPath = CdsUrlUtils.extractPathAndFilename("file.txt"); @@ -45,12 +39,10 @@ void shouldExtractPathAndFilename() throws Exception { subPath = CdsUrlUtils.extractPathAndFilename("/folder/"); Assertions.assertEquals("", subPath.getFileName()); - //Assertions.assertEquals("folder/", subPath.getPath()); Assertions.assertEquals("/folder", subPath.getPath()); subPath = CdsUrlUtils.extractPathAndFilename("../../folder/file.txt"); Assertions.assertEquals("file.txt", subPath.getFileName()); - //Assertions.assertEquals("../../folder/", subPath.getPath()); Assertions.assertEquals("../../folder", subPath.getPath()); subPath = CdsUrlUtils.extractPathAndFilename(""); @@ -60,18 +52,34 @@ void shouldExtractPathAndFilename() throws Exception { subPath = CdsUrlUtils.extractPathAndFilename(null); Assertions.assertEquals("", subPath.getFileName()); Assertions.assertEquals("", subPath.getPath()); - } - - + @Test void shouldWorkFineWithToString() { - EntSubPath subPath1 = new EntSubPath("","my-filename"); - EntSubPath subPath2 = new EntSubPath("","my-filename"); - Assertions.assertEquals(subPath1.toString(),subPath2.toString()); } + + @Test + void shouldExtractRigthSection() { + Assertions.assertEquals("/protected", CdsUrlUtils.getSection(true, null, null, true)); + Assertions.assertEquals("/protected", CdsUrlUtils.getSection(true, null, Mockito.mock(CdsConfiguration.class), true)); + + CdsConfiguration cdsConfiguration = new CdsConfiguration(); + cdsConfiguration.setCdsPublicPath(""); + Assertions.assertEquals("", CdsUrlUtils.getSection(false, Optional.ofNullable(null), cdsConfiguration, false)); + + cdsConfiguration.setCdsPublicPath("/public"); + Assertions.assertEquals("/public", CdsUrlUtils.getSection(false, Optional.ofNullable(null), cdsConfiguration, false)); + + Map tenantParams = new HashMap<>(); + TenantConfig tenantConfig = new TenantConfig(tenantParams); + Assertions.assertEquals("", CdsUrlUtils.getSection(false, Optional.ofNullable(tenantConfig), cdsConfiguration, true)); + + tenantParams.put("cdsPublicPath", "/customPath"); + Assertions.assertEquals("/customPath", CdsUrlUtils.getSection(false, Optional.ofNullable(tenantConfig), cdsConfiguration, false)); + Assertions.assertEquals("/protected", CdsUrlUtils.getSection(true, Optional.ofNullable(tenantConfig), cdsConfiguration, true)); + } } diff --git a/cms-plugin/README.md b/cms-plugin/README.md index b7847bb84b..efc3b4bcf2 100644 --- a/cms-plugin/README.md +++ b/cms-plugin/README.md @@ -17,19 +17,6 @@ entando-plugin-jacms CMS is a plugin that allows to registered users to manage in the Back Office dynamic contents and digital assets. -**Installation** - -In order to install the CMS plugin, you must insert the following dependency in the pom.xml file of your project: - -``` - - org.entando.entando.bundles.app-view - entando-app-view-cms-default - ${entando.version} - war - -``` - # Developing against local versions of upstream projects (e.g. admin-console, entando-engine). Full instructions on how to develop against local versions of upstream projects are available in the diff --git a/cms-plugin/pom.xml b/cms-plugin/pom.xml index 632f6bf14d..3e1c51459c 100644 --- a/cms-plugin/pom.xml +++ b/cms-plugin/pom.xml @@ -5,7 +5,7 @@ org.entando app-engine - 7.3.0 + 7.3.1 org.entando.entando.plugins entando-plugin-jacms diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/ContentManager.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/ContentManager.java index b85fd127c4..1ec8ee3738 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/ContentManager.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/ContentManager.java @@ -14,7 +14,6 @@ package com.agiletec.plugins.jacms.aps.system.services.content; import com.agiletec.aps.system.ApsSystemUtils; -import com.agiletec.aps.system.SystemConstants; import com.agiletec.aps.system.common.entity.ApsEntityManager; import com.agiletec.aps.system.common.entity.IEntityDAO; import com.agiletec.aps.system.common.entity.IEntitySearcherDAO; @@ -53,8 +52,9 @@ * Contents manager. This implements all the methods needed to create and manage * the contents. */ -public class ContentManager extends ApsEntityManager - implements IContentManager, GroupUtilizer, PageUtilizer, ContentUtilizer, ResourceUtilizer, CategoryUtilizer { +public class ContentManager extends ApsEntityManager + implements IContentManager, GroupUtilizer, PageUtilizer, + ContentUtilizer, ResourceUtilizer, CategoryUtilizer { private static final EntLogger logger = EntLogFactory.getSanitizedLogger(ContentManager.class); @@ -615,7 +615,7 @@ public List getPageUtilizers(String pageCode) throws EntException { } @Override - public List getContentUtilizers(String contentId) throws EntException { + public List getContentUtilizers(String contentId) throws EntException { try { return this.getContentDAO().getContentUtilizers(contentId); } catch (Throwable t) { @@ -633,7 +633,7 @@ public List getGroupUtilizers(String groupName) throws EntException { } @Override - public List getResourceUtilizers(String resourceId) throws EntException { + public List getResourceUtilizers(String resourceId) throws EntException { try { return this.getContentDAO().getResourceUtilizers(resourceId); } catch (Throwable t) { @@ -642,7 +642,7 @@ public List getResourceUtilizers(String resourceId) throws EntException { } @Override - public List getCategoryUtilizers(String resourceId) throws EntException { + public List getCategoryUtilizers(String resourceId) throws EntException { try { return this.getContentDAO().getCategoryUtilizers(resourceId); } catch (Throwable t) { @@ -661,7 +661,7 @@ public void reloadCategoryReferences(String categoryCode) { @SuppressWarnings("rawtypes") @Override - public List getCategoryUtilizersForReloadReferences(String categoryCode) { + public List getCategoryUtilizersForReloadReferences(String categoryCode) { List contentIdToReload = new ArrayList<>(); try { Set contents = this.getContentUpdaterService().getContentsId(categoryCode); diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/ContentUtilizer.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/ContentUtilizer.java index 79528b0801..6b6faf7810 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/ContentUtilizer.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/ContentUtilizer.java @@ -20,8 +20,9 @@ /** * Basic interface for the services whose handled elements may have references to contents. * @author E.Santoboni + * @param */ -public interface ContentUtilizer { +public interface ContentUtilizer { /** * Return the ID of the utilizer service. @@ -35,6 +36,6 @@ public interface ContentUtilizer { * @return the list of the objects which reference the content. * @throws EntException in case of error. */ - public List getContentUtilizers(String contentId) throws EntException; + public List getContentUtilizers(String contentId) throws EntException; } diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/PublicContentSearcherDAO.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/PublicContentSearcherDAO.java index 28d20951bc..2062cac3b6 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/PublicContentSearcherDAO.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/PublicContentSearcherDAO.java @@ -22,6 +22,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import org.apache.commons.lang3.ArrayUtils; import org.entando.entando.ent.util.EntLogging.EntLogFactory; import org.entando.entando.ent.util.EntLogging.EntLogger; @@ -31,6 +32,14 @@ public class PublicContentSearcherDAO extends AbstractContentSearcherDAO implements IContentSearcherDAO { private static final EntLogger _logger = EntLogFactory.getSanitizedLogger(PublicContentSearcherDAO.class); + + @Override + public int countContents(String[] categories, boolean orClauseCategoryFilter, + EntitySearchFilter[] filters, Collection userGroupCodes) { + EntitySearchFilter onLineFilter = new EntitySearchFilter<>(IContentManager.CONTENT_ONLINE_FILTER_KEY, false); + EntitySearchFilter[] updatedFilters = (null != filters) ? ArrayUtils.add(filters, onLineFilter) : new EntitySearchFilter[]{onLineFilter}; + return super.countContents(categories, orClauseCategoryFilter, updatedFilters, userGroupCodes); + } @Override public List loadContentsId(String[] categories, diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/helper/ContentAuthorizationHelper.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/helper/ContentAuthorizationHelper.java index ffc3341faf..570cec57c9 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/helper/ContentAuthorizationHelper.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/helper/ContentAuthorizationHelper.java @@ -30,7 +30,6 @@ import com.agiletec.plugins.jacms.aps.system.services.content.IContentManager; import com.agiletec.plugins.jacms.aps.system.services.content.model.Content; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.annotation.Cacheable; /** * Return informations of content authorization @@ -110,18 +109,17 @@ public boolean isAuthToEdit(UserDetails user, String contentId, boolean publicVe } @Override - @Cacheable(value = ICacheInfoManager.DEFAULT_CACHE_NAME, - key = "'" + JacmsSystemConstants.CONTENT_AUTH_INFO_CACHE_PREFIX + "'.concat(#contentId)") public PublicContentAuthorizationInfo getAuthorizationInfo(String contentId) { return this.getAuthorizationInfo(contentId, true); } @Override - @Cacheable(value = ICacheInfoManager.DEFAULT_CACHE_NAME, condition = "#cacheable", - key = "'" + JacmsSystemConstants.CONTENT_AUTH_INFO_CACHE_PREFIX + "'.concat(#contentId)") public PublicContentAuthorizationInfo getAuthorizationInfo(String contentId, boolean cacheable) { - PublicContentAuthorizationInfo authInfo = null; String cacheKey = JacmsSystemConstants.CONTENT_AUTH_INFO_CACHE_PREFIX + contentId; + PublicContentAuthorizationInfo authInfo = this.getCacheInfoManager().getFromCache(ICacheInfoManager.DEFAULT_CACHE_NAME, cacheKey, PublicContentAuthorizationInfo.class); + if (null != authInfo) { + return authInfo; + } try { Content content = this.getContentManager().loadContent(contentId, true); if (null == content) { @@ -131,7 +129,7 @@ public PublicContentAuthorizationInfo getAuthorizationInfo(String contentId, boo authInfo = new PublicContentAuthorizationInfo(content, this.getLangManager().getLangs()); if (cacheable) { String[] groups = CmsCacheWrapperManager.getContentCacheGroups(contentId); - this.getCacheInfoManager().putInGroup(ICacheInfoManager.DEFAULT_CACHE_NAME, cacheKey, groups); + this.getCacheInfoManager().putInCache(ICacheInfoManager.DEFAULT_CACHE_NAME, cacheKey, authInfo, groups); } } catch (Throwable t) { _logger.error("error in getAuthorizationInfo for content {}", contentId, t); diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/model/ContentDto.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/model/ContentDto.java index 67413eca75..7711a20c15 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/model/ContentDto.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/model/ContentDto.java @@ -294,6 +294,7 @@ private void fillAbstractResourceAttribute(AttributeInterface attribute, EntityA if (AbstractResourceAttribute.class.isAssignableFrom(attribute.getClass())) { AbstractResourceAttribute resourceAttribute = (AbstractResourceAttribute) attribute; for (Entry resourceEntry : attributeDto.getValues().entrySet()) { + @SuppressWarnings("unchecked") Map resourceMap = (Map) resourceEntry.getValue(); this.setResourceAttribute(resourceAttribute, resourceMap, resourceEntry.getKey()); } @@ -312,27 +313,27 @@ private void setResourceAttribute(AbstractResourceAttribute resourceAttribute, M resourceInterface.setId(resourceId); resourceInterface.setCorrelationCode(correlationCode); resourceAttribute.setResource(resourceInterface, langCode); + @SuppressWarnings("unchecked") Map values = (Map) resource.get("metadata"); if (values != null) { - Map metadata = values.entrySet().stream() - .collect(Collectors.toMap(Entry::getKey, e -> (String) e.getValue())); - resourceAttribute.setMetadataMap(langCode, metadata); + values.entrySet().stream() + .forEach(e -> resourceAttribute.setMetadata(e.getKey(), langCode, (String) e.getValue())); } } private Map getAdditionalLinkAttributes(final EntityAttributeDto attributeDto) { final Map linkProperties = new HashMap<>(); - final String rel = (String) ((Map) attributeDto.getValue()).get("rel"); + final String rel = (String) ((Map) attributeDto.getValue()).get(LinkAttribute.REL_ATTRIBUTE); if (rel != null) { - linkProperties.put("rel", rel); + linkProperties.put(LinkAttribute.REL_ATTRIBUTE, rel); } - final String target = (String) ((Map) attributeDto.getValue()).get("target"); + final String target = (String) ((Map) attributeDto.getValue()).get(LinkAttribute.TARGET_ATTRIBUTE); if (target != null) { - linkProperties.put("target", target); + linkProperties.put(LinkAttribute.TARGET_ATTRIBUTE, target); } - final String hreflang = (String) ((Map) attributeDto.getValue()).get("hreflang"); + final String hreflang = (String) ((Map) attributeDto.getValue()).get(LinkAttribute.HREFLANG_ATTRIBUTE); if (hreflang != null) { - linkProperties.put("hreflang", hreflang); + linkProperties.put(LinkAttribute.HREFLANG_ATTRIBUTE, hreflang); } return linkProperties; } @@ -341,7 +342,7 @@ private void fillLinkAttribute(AttributeInterface attribute, EntityAttributeDto if (LinkAttribute.class.isAssignableFrom(attribute.getClass())) { LinkAttribute linkAttribute = (LinkAttribute) attribute; String defaultLangCode = linkAttribute.getDefaultLangCode(); - if (attributeDto.getValue() != null && attributeDto.getValue() instanceof SymbolicLink) { + if (attributeDto.getValue() instanceof SymbolicLink) { linkAttribute.setSymbolicLink(defaultLangCode, (SymbolicLink) attributeDto.getValue()); } else { SymbolicLink link = new SymbolicLink(); diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/widget/UserFilterOptionBean.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/widget/UserFilterOptionBean.java index fbc772a0fb..065be8a49b 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/widget/UserFilterOptionBean.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/widget/UserFilterOptionBean.java @@ -243,7 +243,7 @@ public EntitySearchFilter getEntityFilter() throws EntException { AttributeInterface attribute = this.getAttribute(); if (attribute instanceof ITextAttribute) { String text = this.getFormFieldValues().get(this.getFormFieldNames()[0]); - filter = new EntitySearchFilter(attribute.getName(), true, text, true); + filter = new EntitySearchFilter(attribute.getName(), true, text, true); if (attribute.isMultilingual()) { filter.setLangCode(this.getCurrentLang().getCode()); } @@ -252,7 +252,7 @@ public EntitySearchFilter getEntityFilter() throws EntException { String end = this.getFormFieldValues().get(this.getFormFieldNames()[1]); Date startDate = DateConverter.parseDate(start, this.getDateFormat()); Date endDate = DateConverter.parseDate(end, this.getDateFormat()); - filter = new EntitySearchFilter(attribute.getName(), true, startDate, endDate); + filter = new EntitySearchFilter(attribute.getName(), true, startDate, endDate); } else if (attribute instanceof BooleanAttribute) { String value = this.getFormFieldValues().get(this.getFormFieldNames()[0]); String ignore = this.getFormFieldValues().get(this.getFormFieldNames()[1]); @@ -263,7 +263,7 @@ public EntitySearchFilter getEntityFilter() throws EntException { filter = new EntitySearchFilter(attribute.getName(), true); filter.setNullOption(true); } else { - filter = new EntitySearchFilter(attribute.getName(), true, value, false); + filter = new EntitySearchFilter(attribute.getName(), true, value, false); } } else if (attribute instanceof NumberAttribute) { String start = this.getFormFieldValues().get(this.getFormFieldNames()[0]); @@ -278,7 +278,7 @@ public EntitySearchFilter getEntityFilter() throws EntException { Integer endNumberInt = Integer.parseInt(end); endNumber = new BigDecimal(endNumberInt); } catch (Throwable t) {} - filter = new EntitySearchFilter(attribute.getName(), true, startNumber, endNumber); + filter = new EntitySearchFilter(attribute.getName(), true, startNumber, endNumber); } } catch (Throwable t) { _logger.error("Error extracting entity search filters", t); @@ -308,45 +308,31 @@ public SearchEngineFilter extractFilter() { if (!this.isAttributeFilter()) { if (this.getKey().equals(KEY_FULLTEXT) && !StringUtils.isEmpty(value0)) { //String[] fieldsSuffix = {"", "_option"}; - filter = new SearchEngineFilter(this.getCurrentLang().getCode(), value0, this.getOption(value1)); + filter = new SearchEngineFilter<>(this.getCurrentLang().getCode(), value0, this.getOption(value1)); } else if (this.getKey().equals(KEY_CATEGORY) && !StringUtils.isEmpty(value0)) { - filter = new SearchEngineFilter(IIndexerDAO.CONTENT_CATEGORY_FIELD_NAME, value0, SearchEngineFilter.TextSearchOption.EXACT); + filter = new SearchEngineFilter<>(IIndexerDAO.CONTENT_CATEGORY_FIELD_NAME, value0, SearchEngineFilter.TextSearchOption.EXACT); } } else { AttributeInterface attribute = this.getAttribute(); if (attribute instanceof ITextAttribute && !StringUtils.isEmpty(value0)) { - filter = new SearchEngineFilter(this.getIndexFieldName(), value0, SearchEngineFilter.TextSearchOption.EXACT); + filter = new SearchEngineFilter<>(this.getIndexFieldName(), value0, SearchEngineFilter.TextSearchOption.EXACT); //String[] fieldsSuffix = {"_textFieldName"}; } else if (attribute instanceof DateAttribute && (!StringUtils.isEmpty(value0) || !StringUtils.isEmpty(value1))) { - Date big0 = null; - try { - big0 = DateConverter.parseDate(value0, this.getDateFormat()); - } catch (Exception e) {} - Date big1 = null; - try { - big1 = DateConverter.parseDate(value1, this.getDateFormat()); - } catch (Exception e) {} + Date big0 = DateConverter.parseDate(value0, this.getDateFormat()); + Date big1 = DateConverter.parseDate(value1, this.getDateFormat()); //String[] fieldsSuffix = {"_dateStartFieldName", "_dateEndFieldName"}; - filter = new SearchEngineFilter(this.getIndexFieldName(), big0, big1); + filter = new SearchEngineFilter<>(this.getIndexFieldName(), big0, big1); } else if (attribute instanceof BooleanAttribute && (!StringUtils.isEmpty(value0) && !StringUtils.isEmpty(value1))) { - filter = new SearchEngineFilter(this.getIndexFieldName(), value0, SearchEngineFilter.TextSearchOption.EXACT); + filter = new SearchEngineFilter<>(this.getIndexFieldName(), value0, SearchEngineFilter.TextSearchOption.EXACT); //String[] fieldsSuffix = {"_booleanFieldName", "_booleanFieldName_ignore", "_booleanFieldName_control"}; } else if (attribute instanceof NumberAttribute && (!StringUtils.isEmpty(value0) || !StringUtils.isEmpty(value1))) { //String[] fieldsSuffix = {"_numberStartFieldName", "_numberEndFieldName"}; - BigDecimal big0 = null; - try { - big0 = new BigDecimal(value0); - } catch (Exception e) { - } - BigDecimal big1 = null; - try { - big1 = new BigDecimal(value1); - } catch (Exception e) { - } - filter = new SearchEngineFilter(this.getIndexFieldName(), big0, big1); + BigDecimal big0 = new BigDecimal(value0); + BigDecimal big1 = new BigDecimal(value1); + filter = new SearchEngineFilter<>(this.getIndexFieldName(), big0, big1); } } return filter; diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceManager.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceManager.java index da3d4da41f..7ccad81dc4 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceManager.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceManager.java @@ -59,8 +59,8 @@ * * @author W.Ambu - E.Santoboni */ -public class ResourceManager extends AbstractService implements IResourceManager, GroupUtilizer, CategoryUtilizer, - RefreshableBeanTenantAware { +public class ResourceManager extends AbstractService + implements IResourceManager, GroupUtilizer, CategoryUtilizer, RefreshableBeanTenantAware { private final EntLogger logger = EntLogFactory.getSanitizedLogger(getClass()); @@ -262,29 +262,27 @@ protected void generateAndSetResourceId(ResourceInterface resource, String id) t resource.setId(String.valueOf(newId)); } } - + @Override public void updateResource(ResourceDataBean bean) throws EntException { - ResourceInterface oldResource = this.loadResource(bean.getResourceId()); try { - if (null == bean.getInputStream()) { + ResourceInterface oldResource = this.loadResource(bean.getResourceId()); + ResourceInterface updatedResource = null; + if (null != bean.getInputStream()) { + updatedResource = this.createResource(bean); + oldResource.moveInstances("todelete"); + updatedResource.saveResourceInstances(bean, getIgnoreMetadataKeysForResourceType(bean.getResourceType())); + oldResource.deleteResourceInstances(); + } else { oldResource.setDescription(bean.getDescr()); oldResource.setCategories(bean.getCategories()); oldResource.setMetadata(bean.getMetadata()); oldResource.setMainGroup(bean.getMainGroup()); oldResource.setFolderPath(bean.getFolderPath()); - this.getResourceDAO().updateResource(oldResource); - this.notifyResourceChanging(oldResource, ResourceChangedEvent.UPDATE_OPERATION_CODE); - } else { - ResourceInterface updatedResource = this.createResource(bean); - updatedResource - .saveResourceInstances(bean, getIgnoreMetadataKeysForResourceType(bean.getResourceType())); - this.getResourceDAO().updateResource(updatedResource); - if (!updatedResource.getMasterFileName().equals(oldResource.getMasterFileName())) { - oldResource.deleteResourceInstances(); - } - this.notifyResourceChanging(updatedResource, ResourceChangedEvent.UPDATE_OPERATION_CODE); + updatedResource = oldResource; } + this.getResourceDAO().updateResource(updatedResource); + this.notifyResourceChanging(updatedResource, ResourceChangedEvent.UPDATE_OPERATION_CODE); } catch (Throwable t) { logger.error("Error updating resource", t); throw new EntException("Error updating resource", t); @@ -603,7 +601,7 @@ public List getGroupUtilizers(String groupName) throws EntException { } @Override - public List getCategoryUtilizers(String categoryCode) throws EntException { + public List getCategoryUtilizers(String categoryCode) throws EntException { List resourcesId = null; try { resourcesId = this.getResourceDAO().searchResourcesId(null, null, null, categoryCode, null); @@ -643,7 +641,7 @@ public void reloadCategoryReferences(String categoryCode) throws EntException { } @Override - public List getCategoryUtilizersForReloadReferences(String categoryCode) throws EntException { + public List getCategoryUtilizersForReloadReferences(String categoryCode) throws EntException { List resourcesId = null; try { resourcesId = this.getCategoryUtilizers(categoryCode); diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceUtilizer.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceUtilizer.java index 1c9d97a74d..d606c83a50 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceUtilizer.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceUtilizer.java @@ -21,8 +21,9 @@ * Interfaccia base per i servizi, i cui elementi gestiti, * possono presentare delle referenziazione con delle risorse. * @author E.Santoboni + * @param */ -public interface ResourceUtilizer { +public interface ResourceUtilizer { /** * Restituisce l'identificativo del servizio utilizzatore. @@ -37,6 +38,6 @@ public interface ResourceUtilizer { * @return La lista degli oggetti referenzianti la risorsa. * @throws EntException in caso di errore. */ - public List getResourceUtilizers(String resourceId) throws EntException; + public List getResourceUtilizers(String resourceId) throws EntException; } diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/AbstractMonoInstanceResource.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/AbstractMonoInstanceResource.java index 029bd8346d..c0b1af89be 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/AbstractMonoInstanceResource.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/AbstractMonoInstanceResource.java @@ -19,8 +19,12 @@ import org.apache.commons.lang.StringUtils; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; import org.entando.entando.ent.exception.EntResourceNotFoundException; import org.entando.entando.ent.exception.EntResourceNotFoundRuntimeException; +import org.entando.entando.ent.exception.EntRuntimeException; import org.entando.entando.ent.util.EntLogging.EntLogFactory; import org.entando.entando.ent.util.EntLogging.EntLogger; @@ -44,6 +48,12 @@ public abstract class AbstractMonoInstanceResource extends AbstractResource { public boolean isMultiInstance() { return false; } + + @Override + public List getInstanceList() { + return Optional.ofNullable(this.getInstance()) + .map(i -> new ArrayList<>(List.of(i))).orElse(new ArrayList<>()); + } @Override public InputStream getResourceStream(int size, String langCode) { @@ -60,7 +70,7 @@ public InputStream getResourceStream() { throw new EntResourceNotFoundRuntimeException(ERROR_ON_EXTRACTING_RESOURCE_STREAM, e); } catch (Throwable t) { logger.error(ERROR_ON_EXTRACTING_RESOURCE_STREAM, t); - throw new RuntimeException(ERROR_ON_EXTRACTING_RESOURCE_STREAM, t); + throw new EntRuntimeException(ERROR_ON_EXTRACTING_RESOURCE_STREAM, t); } } diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/AbstractMultiInstanceResource.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/AbstractMultiInstanceResource.java index e7571d7169..cf0151ccfc 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/AbstractMultiInstanceResource.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/AbstractMultiInstanceResource.java @@ -38,10 +38,15 @@ public AbstractMultiInstanceResource() { instances = new HashMap<>(); } + @Override + public List getInstanceList() { + return new ArrayList<>(this.getInstances().values()); + } + @Override public void deleteResourceInstances() throws EntException { try { - Collection resources = this.getInstances().values(); + Collection resources = this.getInstanceList(); for (ResourceInstance currentInstance : resources) { String fileName = currentInstance.getFileName(); String subPath = this.getDiskSubFolder() + fileName; @@ -73,7 +78,7 @@ public boolean isMultiInstance() { @Override public String getXML() { ResourceDOM resourceDom = getResourceDOM(); - List resources = new ArrayList<>(this.getInstances().values()); + List resources = this.getInstanceList(); for (ResourceInstance currentInstance : resources) { resourceDom.addInstance(currentInstance.getJDOMElement()); } diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/AbstractResource.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/AbstractResource.java index ceaf45d7e0..0cba760ac3 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/AbstractResource.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/AbstractResource.java @@ -25,6 +25,7 @@ import java.io.*; import java.util.*; +import org.apache.commons.lang3.StringUtils; public abstract class AbstractResource implements ResourceInterface, Serializable { @@ -365,9 +366,16 @@ protected ResourceDOM getNewResourceDOM() { } protected String getDiskSubFolder() { - StringBuilder diskFolder = new StringBuilder(folder); + return this.getDiskSubFolder(this.getFolderPath()); + } + + protected String getDiskSubFolder(String folderPath) { + StringBuilder diskFolder = new StringBuilder(this.folder); + if (!StringUtils.isBlank(folderPath)) { + diskFolder.append(folderPath).append(File.separator); + } if (this.isProtectedResource()) { - diskFolder.append(mainGroup).append("/"); + diskFolder.append(mainGroup).append(File.separator); } return diskFolder.toString(); } @@ -420,6 +428,12 @@ protected String getUrlPath(ResourceInstance instance) { if (!subFolder.toString().endsWith("/")) { subFolder.append("/"); } + if (!StringUtils.isBlank(this.getFolderPath())) { + subFolder.append(this.getFolderPath()); + if (!this.getFolderPath().endsWith("/")) { + subFolder.append("/"); + } + } subFolder.append(instance.getFileName()); String path = this.getStorageManager().getResourceUrl(subFolder.toString(), false); urlPath.append(path); @@ -504,6 +518,33 @@ protected boolean exists(String instanceFileName) { } } + @Override + public void moveInstances(String newFolderPath) throws EntException { + Map movements = new HashMap<>(); + try { + for (ResourceInstance resourceInstance : this.getInstanceList()) { + String internalPath = this.getDiskSubFolder() + resourceInstance.getFileName(); + String destInternalPath = this.getDiskSubFolder(newFolderPath) + resourceInstance.getFileName(); + boolean result = this.getStorageManager().move(internalPath, this.isProtectedResource(), destInternalPath, this.isProtectedResource()); + if (!result) { + throw new EntException(String.format("Error moving File '%s' to '%s', protected '%s'", internalPath, destInternalPath, this.isProtectedResource())); + } + movements.put(internalPath, destInternalPath); + } + logger.warn("Move resource instances from '{}' to '{}' has no effect on the database, protected '{}'", this.getDiskSubFolder(), newFolderPath, this.isProtectedResource()); + this.setFolderPath(newFolderPath); + } catch (Exception e) { + for (ResourceInstance resourceInstance : this.getInstanceList()) { + String internalPath = this.getDiskSubFolder() + resourceInstance.getFileName(); + String dest = movements.get(internalPath); + if (null != dest) { + this.getStorageManager().move(dest, this.isProtectedResource(), internalPath, this.isProtectedResource()); + } + } + throw new EntException("Error moving resource instances", e); + } + } + public IStorageManager getStorageManager() { return storageManager; } diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/ImageResource.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/ImageResource.java index 6e7df183ec..db308e390a 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/ImageResource.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/ImageResource.java @@ -36,6 +36,7 @@ import org.entando.entando.ent.exception.EntException; import org.entando.entando.ent.exception.EntResourceNotFoundException; import org.entando.entando.ent.exception.EntResourceNotFoundRuntimeException; +import org.entando.entando.ent.exception.EntRuntimeException; import org.entando.entando.ent.util.EntLogging.EntLogFactory; import org.entando.entando.ent.util.EntLogging.EntLogger; import org.im4java.core.ConvertCmd; @@ -81,7 +82,7 @@ public InputStream getResourceStream(int size, String langCode) { throw new EntResourceNotFoundRuntimeException(ERROR_ON_EXTRACTING_FILE, e); } catch (Throwable t) { logger.error(ERROR_ON_EXTRACTING_FILE, t); - throw new RuntimeException(ERROR_ON_EXTRACTING_FILE, t); + throw new EntRuntimeException(ERROR_ON_EXTRACTING_FILE, t); } } diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/ResourceDataBean.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/ResourceDataBean.java index 010e6d9225..23d571b89c 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/ResourceDataBean.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/ResourceDataBean.java @@ -108,7 +108,7 @@ public interface ResourceDataBean { * * @return La lista dei metadati della risorsa. */ - public Map getMetadata(); + public Map getMetadata(); public void setMetadata(Map metadata); diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/ResourceInterface.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/ResourceInterface.java index 31ff476e2c..3c4a328e96 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/ResourceInterface.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/model/ResourceInterface.java @@ -229,6 +229,8 @@ default void saveResourceInstances(ResourceDataBean bean, List ignoreMet */ void saveResourceInstances(ResourceDataBean bean, List ignoreMetadataKeys, boolean instancesAlreadySaved) throws EntException; + + public List getInstanceList(); /** * Cancella tutte le istanze associate alla risorsa. @@ -239,7 +241,8 @@ void saveResourceInstances(ResourceDataBean bean, List ignoreMetadataKey public void reloadResourceInstances() throws EntException; - //public boolean exists(String masterFormFileName) throws EntException; + public void moveInstances(String newFolderPath) throws EntException; + public ResourceInstance getDefaultInstance(); /** diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/apsadmin/resource/MultipleResourceAction.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/apsadmin/resource/MultipleResourceAction.java index d78d3cbcea..79729f90de 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/apsadmin/resource/MultipleResourceAction.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/apsadmin/resource/MultipleResourceAction.java @@ -1,5 +1,5 @@ /* -* Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved. +* Copyright 2024-Present Entando Inc. (http://www.entando.com) All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -13,10 +13,12 @@ */ package com.agiletec.plugins.jacms.apsadmin.resource; +import com.agiletec.aps.system.common.FieldSearchFilter; import com.agiletec.aps.system.common.entity.model.FieldError; import org.entando.entando.ent.exception.EntException; import com.agiletec.aps.system.services.category.Category; import com.agiletec.apsadmin.system.ApsAdminSystemConstants; +import com.agiletec.plugins.jacms.aps.system.services.resource.IResourceManager; import com.agiletec.plugins.jacms.aps.system.services.resource.model.BaseResourceDataBean; import com.agiletec.plugins.jacms.aps.system.services.resource.model.ResourceInterface; import org.apache.commons.lang.StringUtils; @@ -47,16 +49,19 @@ public class MultipleResourceAction extends ResourceAction { public void validate() { logger.debug("MultipleResourceAction validate"); savedId.clear(); + if (ApsAdminSystemConstants.EDIT == this.getStrutsAction()) { + this.fetchFileUploadFileNames(); this.fetchFileDescriptions(); addFieldErrors(validateFileDescriptions()); } else { this.fetchFileFields(); addFieldErrors(validateFileDescriptions()); addFieldErrors(validateFileUploadIDs()); - addFieldErrors(validateFileUploaNames()); + addFieldErrors(validateFileUploadNames()); addFieldErrors(validateFileUploadContentType()); } + addFieldErrors(validateCheckDuplicateFile()); } private void addFieldErrors(List fieldErrors) { @@ -74,15 +79,12 @@ private List validateFileDescriptions() { errors.add(new FieldError(FILE_DESCR_FIELD + "0", getText("error.resource.file.descrEmpty"))); return errors; } - if (fileDescriptions.isEmpty()) { errors.add(new FieldError(FILE_DESCR_FIELD + "0", getText("error.resource.file.descrEmpty"))); return errors; } - for (int i = 0; i < fileDescriptions.size(); i++) { String fileDescription = fileDescriptions.get(i); - if (StringUtils.isEmpty(fileDescription)) { errors.add(new FieldError(FILE_DESCR_FIELD + i, getText("error.resource.file.descrEmpty"))); } @@ -90,7 +92,6 @@ private List validateFileDescriptions() { errors.add(new FieldError(FILE_DESCR_FIELD + i, getText("error.resource.file.descrTooLong"))); } } - return errors; } @@ -100,43 +101,37 @@ private List validateFileUploadIDs() { errors.add(new FieldError(FILE_UPLOAD_ID_FIELD + "0", getText("error.resource.filename.uploadError"))); return errors; } - if (fileUploadIDs.isEmpty()) { errors.add(new FieldError(FILE_UPLOAD_ID_FIELD + "0", getText("error.resource.filename.uploadError"))); return errors; } - for (int i = 0; i < fileUploadIDs.size(); i++) { String fileUploadID = fileUploadIDs.get(i); - if (StringUtils.isEmpty(fileUploadID)) { errors.add(new FieldError(FILE_UPLOAD_ID_FIELD + i, getText("error.resource.filename.uploadError"))); } } - return errors; } - private List validateFileUploaNames() { + private List validateFileUploadNames() { List errors = new ArrayList<>(); if (fileUploadFileNames == null) { errors.add(new FieldError(FILE_NAME_FIELD + "0", getText("error.resource.filename.uploadError"))); return errors; } - if (fileUploadFileNames.isEmpty()) { errors.add(new FieldError(FILE_NAME_FIELD + "0", getText("error.resource.filename.uploadError"))); return errors; } - for (int i = 0; i < fileUploadFileNames.size(); i++) { String fileUploadFileName = fileUploadFileNames.get(i); - if (StringUtils.isEmpty(fileUploadFileName)) { errors.add(new FieldError(FILE_NAME_FIELD + i, getText("error.resource.filename.uploadError"))); + } else if (fileUploadFileName.length() > 100) { + errors.add(new FieldError(FILE_NAME_FIELD + i, getText("error.resource.filename.tooLong.config", List.of(fileUploadFileName, 100)))); } } - return errors; } @@ -146,20 +141,46 @@ private List validateFileUploadContentType() { errors.add(new FieldError(FILE_CONTENT_TYPE_FIELD + "0", getText("error.resource.filename.uploadError"))); return errors; } - if (fileUploadContentTypes.isEmpty()) { errors.add(new FieldError(FILE_CONTENT_TYPE_FIELD + "0", getText("error.resource.filename.uploadError"))); return errors; } - for (int i = 0; i < fileUploadContentTypes.size(); i++) { String fileUploadContentType = fileUploadContentTypes.get(i); - if (StringUtils.isEmpty(fileUploadContentType)) { errors.add(new FieldError(FILE_CONTENT_TYPE_FIELD + i, getText("error.resource.filename.uploadError"))); } } - + return errors; + } + + private List validateCheckDuplicateFile() { + List errors = new ArrayList<>(); + try { + if (StringUtils.isBlank(this.getMainGroup())) { + return errors; + } + FieldSearchFilter groupFilter = new FieldSearchFilter<>(IResourceManager.RESOURCE_MAIN_GROUP_FILTER_KEY, this.getMainGroup(), false); + for (int i = 0; i < getFileUploadFileName().size(); i++) { + String formFileName = this.getFileUploadFileName(i); + if (formFileName.isEmpty()){ + continue; + } + FieldSearchFilter fileNameFilter = new FieldSearchFilter<>(IResourceManager.RESOURCE_FILENAME_FILTER_KEY, formFileName, false); + FieldSearchFilter[] filters = new FieldSearchFilter[]{groupFilter, fileNameFilter}; + List resourcesId = this.getResourceManager().searchResourcesId(filters, List.of()); + if (resourcesId.isEmpty()) { + continue; + } + if ((this.getStrutsAction() == ApsAdminSystemConstants.ADD) || + (this.getStrutsAction() == ApsAdminSystemConstants.EDIT && !resourcesId.contains(this.getResourceId()))) { + String[] args = {formFileName}; + errors.add(new FieldError(FILE_NAME_FIELD + i, getText("error.resource.file.alreadyPresent", args))); + } + } + } catch (EntException e) { + logger.error("Error on check duplicated files", e); + } return errors; } diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/apsadmin/resource/ResourceFinderAction.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/apsadmin/resource/ResourceFinderAction.java index aabeaedebd..0a21f2c21d 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/apsadmin/resource/ResourceFinderAction.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/apsadmin/resource/ResourceFinderAction.java @@ -47,6 +47,7 @@ public class ResourceFinderAction extends AbstractResourceAction { private static final EntLogger logger = EntLogFactory.getSanitizedLogger(ResourceFinderAction.class); private String text; + private String searchedResourceId; private String fileName; private String ownerGroupName; private String categoryCode; @@ -103,23 +104,25 @@ public String getPagerId() { } protected FieldSearchFilter[] createSearchFilters() { - FieldSearchFilter typeCodeFilter; FieldSearchFilter[] filters = new FieldSearchFilter[] {}; - if (StringUtils.isNotBlank(this.getResourceTypeCode())) { - typeCodeFilter = new FieldSearchFilter(IResourceManager.RESOURCE_TYPE_FILTER_KEY, this.getResourceTypeCode(), false); - filters = new FieldSearchFilter[] {typeCodeFilter}; - } + FieldSearchFilter typeCodeFilter = new FieldSearchFilter<>(IResourceManager.RESOURCE_TYPE_FILTER_KEY, this.getResourceTypeCode(), false); + filters = ArrayUtils.add(filters, typeCodeFilter); + } + if (StringUtils.isNotBlank(this.getSearchedResourceId())) { + FieldSearchFilter idFilter = new FieldSearchFilter<>(IResourceManager.RESOURCE_ID_FILTER_KEY, this.getSearchedResourceId(), true); + filters = ArrayUtils.add(filters, idFilter); + } if (StringUtils.isNotBlank(this.getOwnerGroupName())) { - FieldSearchFilter groupFilter = new FieldSearchFilter(IResourceManager.RESOURCE_MAIN_GROUP_FILTER_KEY, this.getOwnerGroupName(), false); + FieldSearchFilter groupFilter = new FieldSearchFilter<>(IResourceManager.RESOURCE_MAIN_GROUP_FILTER_KEY, this.getOwnerGroupName(), false); filters = ArrayUtils.add(filters, groupFilter); } if (StringUtils.isNotBlank(this.getText())) { - FieldSearchFilter textFilter = new FieldSearchFilter(IResourceManager.RESOURCE_DESCR_FILTER_KEY, this.getText(), true); + FieldSearchFilter textFilter = new FieldSearchFilter<>(IResourceManager.RESOURCE_DESCR_FILTER_KEY, this.getText(), true); filters = ArrayUtils.add(filters, textFilter); } if (StringUtils.isNotBlank(this.getFileName())) { - FieldSearchFilter filenameFilter = new FieldSearchFilter(IResourceManager.RESOURCE_FILENAME_FILTER_KEY, this.getFileName(), true); + FieldSearchFilter filenameFilter = new FieldSearchFilter<>(IResourceManager.RESOURCE_FILENAME_FILTER_KEY, this.getFileName(), true); filters = ArrayUtils.add(filters, filenameFilter); } filters = ArrayUtils.add(filters, this.getOrderFilter()); @@ -209,6 +212,14 @@ public void setText(String text) { this.text = text; } + public String getSearchedResourceId() { + return searchedResourceId; + } + + public void setSearchedResourceId(String searchedResourceId) { + this.searchedResourceId = searchedResourceId; + } + public String getFileName() { return fileName; } @@ -260,6 +271,7 @@ public boolean isOpenCollapsed() { } return (this.openCollapsed || hasFilterByCat || !StringUtils.isBlank(this.getFileName()) + || !StringUtils.isBlank(this.getSearchedResourceId()) || !StringUtils.isBlank(this.getOwnerGroupName())); } diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/apsadmin/resource/package_en.properties b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/apsadmin/resource/package_en.properties index 436288cb0e..7342966b39 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/apsadmin/resource/package_en.properties +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/apsadmin/resource/package_en.properties @@ -28,6 +28,7 @@ title.referencedContents=Contents having this resource title.contentList=Content list title.resourceManagement.help=Default image file formats are jpg,jpeg,png title.resourceAttach.help=Default attachments file formats are pdf, xls, doc, ppt, txt, rtf, sxw, sxc, odt, ods, odp, tar, gz, zip, rar, flv, swf, avi, wmv, ogg, mp3, wav, ogm, mov, iso, nrg, docx, docm, xlsx, xlsm, xlsb, pptx, pptm, ppsx, ppsm, sldx, sldm. +label.resourceId=Resource Id label.categoriesTree=Categories tree label.size=Size @@ -79,17 +80,19 @@ fileName=File name error.resource.file.required=File path is required error.resource.file.void=File is void error.resource.filename.tooLong=File name ''${getFileName()}'' exceeds the length of 100 characters. +error.resource.filename.tooLong.config=File name ''{0}'' exceeds the length of {1} characters. error.resource.file.wrongFormat=File format is not allowed - Filename ''{0}'' error.resource.delete.invalid=Sorry, the resource you are trying to delete is unknown: cannot proceed. error.resource.filename.wrongCharacters=The file name contains invalid characters. Only alphanumeric characters, the dot ".", the dash "-" and the underscore "_" are allowed. error.resource.filename.blankSpace=The file name contains white spaces. Please use the "normalize" option to automagically get rid of them. error.resource.filename.uploadError=File name ''{0}'' was not uploaded. error.resource.file.tooBig=File size of ''{0}'' exceeds the maximum allowed size. +error.resource.file.alreadyPresent=File ''{0}'' is already present error.resource.file.descrEmpty=File description is empty error.resource.file.fileNameEmpty=The file name list is empty error.resource.file.fileEmpty=The file list is empty -message.resource.filename.uploaded=File name ''{0}'' was uploaded successfully. +message.resource.filename.uploaded=File name ''{0}'' was uploaded successfully. label.add-fileinput=Add Another Resource label.remove-fileinput=Remove from form error.resource.file.descrTooLong=File description is too long diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/apsadmin/resource/package_it.properties b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/apsadmin/resource/package_it.properties index 17065b7d28..19b5b7bfe5 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/apsadmin/resource/package_it.properties +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/apsadmin/resource/package_it.properties @@ -27,8 +27,10 @@ title.referencedContents=Contenuti che contengono questa risorsa title.contentList=Lista Contenuti title.resourceManagement.help=I formati delle Immagini consentiti di default sono jpg,jpeg,png title.resourceAttach.help=I formati dei file consentiti di default sono pdf, xls, doc, ppt, txt, rtf, sxw, sxc, odt, ods, odp, tar, gz, zip, rar, flv, swf, avi, wmv, ogg, mp3, wav, ogm, mov, iso, nrg, docx, docm, xlsx, xlsm, xlsb, pptx, pptm, ppsx, ppsm, sldx, sldm. +label.resourceId=Id Risorsa label.categoriesTree=Albero categorie label.size=Dimensione + help.Attach.list.title=Documenti help.Attach.list.info=Lista dei documenti del CMS help.Image.list.title=Immagini @@ -77,12 +79,14 @@ fileName=Nome file error.resource.file.required=Il File è richiesto error.resource.file.void=Il File è vuoto error.resource.filename.tooLong=Filename ''${getFileName()}'' eccede la lunghezza massima consentita di 100 caratteri. +error.resource.filename.tooLong.config=Filename ''{0}'' eccede la lunghezza massima consentita di {1} caratteri. error.resource.file.wrongFormat=Il formato del file non è tra quelli compatibili - ''{0}'' error.resource.delete.invalid=ID della risorsa da cancellare sconosciuto, impossibile proseguire error.resource.filename.wrongCharacters=Il nome file contiene caratteri non consentiti. Sono consentiti caratteri alfanumerici, il punto ".", il meno "-" e il trattino basso "_". Anche spazi " " se attivata la normalizzazione. error.resource.filename.blankSpace=Il nome file contiene degli spazi. Attiva l''opzione di normalizzazione per consentire il caricamento. error.resource.filename.uploadError=Il file ''{0}'' non \u00e8 stato caricato. error.resource.file.tooBig=La dimensione del file ''{0}'' supera il limite massimo consentito. +error.resource.file.alreadyPresent=Il file ''{0}'' è già presente error.resource.file.descrEmpty=La descrizione del file \u00e8 vuota error.resource.file.fileNameEmpty=La lista dei nomi dei file \u00e8 vuota error.resource.file.fileEmpty==La lista dei file \u00e8 vuota diff --git a/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/aps/system/services/content/ContentService.java b/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/aps/system/services/content/ContentService.java index 2eae9592f4..d204769efa 100644 --- a/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/aps/system/services/content/ContentService.java +++ b/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/aps/system/services/content/ContentService.java @@ -109,13 +109,13 @@ public class ContentService extends AbstractEntityService implements IContentService, GroupServiceUtilizer, CategoryServiceUtilizer, - PageServiceUtilizer, ContentServiceUtilizer, - ResourceServiceUtilizer, - IComponentExistsService, IComponentUsageService, + PageServiceUtilizer, ContentServiceUtilizer, + ResourceServiceUtilizer, + IComponentExistsService, IComponentUsageService, ApplicationContextAware { private final EntLogger logger = EntLogFactory.getSanitizedLogger(getClass()); - + private ICategoryManager categoryManager; private IContentManager contentManager; private IContentModelManager contentModelManager; @@ -305,10 +305,12 @@ private void convertResourceAttributeToDto(AbstractResourceAttribute contentAttr AssetDto assetDto = resourcesService.convertResourceToDto((ResourceInterface) e.getValue()); if (contentAttr.getMetadatas() != null && ImageAssetDto.class .isAssignableFrom(assetDto.getClass())) { - ((ImageAssetDto) assetDto).setMetadata(contentAttr.getMetadatas().get(e.getKey())); + Map metadataByLang = contentAttr.getMetadatas().entrySet().stream() + .filter(entry -> entry.getKey() != null && entry.getValue().get(e.getKey()) != null) + .collect(Collectors.toMap(Entry::getKey, entry -> entry.getValue().get(e.getKey()))); + ((ImageAssetDto) assetDto).setMetadata(metadataByLang); } assetDto.setName(contentAttr.getTextForLang(e.getKey())); - return new AbstractMap.SimpleEntry<>(e.getKey(), assetDto); }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); } @@ -326,7 +328,8 @@ protected void fillEntity(EntityDto request, Content entity, BindingResult bindi @Override public List getGroupUtilizer(String groupCode) { try { - List contentIds = ((GroupUtilizer) this.getContentManager()).getGroupUtilizers(groupCode); + @SuppressWarnings("unchecked") + List contentIds = ((GroupUtilizer) this.getContentManager()).getGroupUtilizers(groupCode); return this.buildDtoList(contentIds); } catch (EntException ex) { logger.error("Error loading content references for group {}", groupCode, ex); @@ -337,6 +340,7 @@ public List getGroupUtilizer(String groupCode) { @Override public List getCategoryUtilizer(String categoryCode) { try { + @SuppressWarnings("unchecked") List contentIds = ((CategoryUtilizer) this.getContentManager()).getCategoryUtilizers(categoryCode); return this.buildDtoList(contentIds); } catch (EntException ex) { @@ -348,6 +352,7 @@ public List getCategoryUtilizer(String categoryCode) { @Override public List getPageUtilizer(String pageCode) { try { + @SuppressWarnings("unchecked") List contentIds = ((PageUtilizer) this.getContentManager()).getPageUtilizers(pageCode); return this.buildDtoList(contentIds); } catch (EntException ex) { @@ -359,6 +364,7 @@ public List getPageUtilizer(String pageCode) { @Override public List getContentUtilizer(String contentId) { try { + @SuppressWarnings("unchecked") List contentIds = ((ContentUtilizer) this.getContentManager()).getContentUtilizers(contentId); return this.buildDtoList(contentIds); } catch (EntException ex) { @@ -370,6 +376,7 @@ public List getContentUtilizer(String contentId) { @Override public List getResourceUtilizer(String resourceId) { try { + @SuppressWarnings("unchecked") List contentIds = ((ResourceUtilizer) this.getContentManager()).getResourceUtilizers(resourceId); return this.buildDtoList(contentIds); } catch (EntException ex) { @@ -561,11 +568,9 @@ private PagedMetadata toPagedMetadata(RestContentListRequest request public Integer countContentsByType(String contentType) { try { EntitySearchFilter[] filters = new EntitySearchFilter[]{ - new EntitySearchFilter("typeCode", false, contentType, false) + new EntitySearchFilter<>("typeCode", false, contentType, false) }; - List userGroupCodes = Collections.singletonList("administrators"); - return getContentManager().countWorkContents(null, false, filters, userGroupCodes); } catch (Exception t) { logger.error("error in contents count by type", t); @@ -714,13 +719,13 @@ public ContentDto updateContent(ContentDto request, UserDetails user, BindingRes request.setRestriction(ContentRestriction.getRestrictionValue(request.getMainGroup())); return super.updateEntity(JacmsSystemConstants.CONTENT_MANAGER, request, bindingResult); } - + @Override public void deleteContent(String code, UserDetails user) { this.checkContentAuthorization(user, code, false, true, null); this.deleteContent(code); } - + private void deleteContent(String code) { try { Content content = this.getContentManager().loadContent(code, false); @@ -749,7 +754,7 @@ public ContentDto updateContentStatus(String code, String status, UserDetails us return updateContentStatus(code, status, user, bindingResult, false); } - private ContentDto updateContentStatus(String code, String status, + private ContentDto updateContentStatus(String code, String status, UserDetails user, BeanPropertyBindingResult bindingResult, boolean forceUnpublish) { try { this.checkContentExists(code); @@ -841,16 +846,16 @@ public PagedMetadata getContentReferences(String code, String managerName, Us logger.warn("no content found with code {}", code); throw new ResourceNotFoundException(ERRCODE_CONTENT_NOT_FOUND, ComponentUsageEntity.TYPE_CONTENT, code); } - ContentServiceUtilizer utilizer = this.getContentServiceUtilizer(managerName); + ContentServiceUtilizer utilizer = this.getContentServiceUtilizer(managerName); if (null == utilizer) { logger.warn("no references found for {}", managerName); throw new ResourceNotFoundException(ERRCODE_CONTENT_REFERENCES, "reference", managerName); } - List dtoList = utilizer.getContentUtilizer(code); - List subList = requestList.getSublist(dtoList); - SearcherDaoPaginatedResult pagedResult = new SearcherDaoPaginatedResult(dtoList.size(), subList); - PagedMetadata pagedMetadata = new PagedMetadata<>(requestList, pagedResult); - pagedMetadata.setBody((List) subList); + List dtoList = utilizer.getContentUtilizer(code); + List subList = requestList.getSublist(dtoList); + SearcherDaoPaginatedResult pagedResult = new SearcherDaoPaginatedResult<>(dtoList.size(), subList); + PagedMetadata pagedMetadata = new PagedMetadata<>(requestList, pagedResult); + pagedMetadata.setBody(subList); return pagedMetadata; } catch (EntException ex) { logger.error("Error extracting content references - content {} - manager {}", code, managerName, ex); @@ -858,7 +863,8 @@ public PagedMetadata getContentReferences(String code, String managerName, Us } } - private ContentServiceUtilizer getContentServiceUtilizer(String managerName) { + @SuppressWarnings("unchecked") + private ContentServiceUtilizer getContentServiceUtilizer(String managerName) { Map beans = this.applicationContext .getBeansOfType(ContentServiceUtilizer.class); ContentServiceUtilizer defName = beans.values().stream() @@ -885,6 +891,7 @@ public PagedMetadata getComponentUsageDetails(String compo List components = new ArrayList<>(); Map beans = this.applicationContext.getBeansOfType(ContentServiceUtilizer.class); for (var utilizer : beans.values()) { + @SuppressWarnings("unchecked") List objects = utilizer.getContentUtilizer(componentCode); List utilizerForService = objects.stream() .map(o -> o.buildUsageEntity()).collect(Collectors.toList()); diff --git a/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/aps/system/services/content/ContentTypeService.java b/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/aps/system/services/content/ContentTypeService.java index 1108a8841f..f54c0bc05d 100644 --- a/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/aps/system/services/content/ContentTypeService.java +++ b/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/aps/system/services/content/ContentTypeService.java @@ -199,6 +199,7 @@ public PagedMetadata getComponentUsageDetails(String compo .map(ContentDto::buildUsageEntity) .collect(Collectors.toList()); for (var utilizer : this.contentTypeServiceUtilizers) { + @SuppressWarnings("unchecked") List objects = utilizer.getContentTypeUtilizer(componentCode); List utilizerForService = objects.stream() .map(o -> o.buildUsageEntity()).collect(Collectors.toList()); diff --git a/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/aps/system/services/page/CmsPageServiceWrapper.java b/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/aps/system/services/page/CmsPageServiceWrapper.java index fb8f540323..9504f2b9c2 100644 --- a/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/aps/system/services/page/CmsPageServiceWrapper.java +++ b/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/aps/system/services/page/CmsPageServiceWrapper.java @@ -58,6 +58,7 @@ public String getManagerName() { @Override public List getContentUtilizer(String contentId) { try { + @SuppressWarnings("unchecked") List pages = this.getPageManagerWrapper().getContentUtilizers(contentId); return this.getDtoBuilder().convert(pages); } catch (EntException ex) { diff --git a/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/aps/system/services/resource/ResourcesService.java b/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/aps/system/services/resource/ResourcesService.java index 8c45d4db9d..577d5adf1c 100644 --- a/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/aps/system/services/resource/ResourcesService.java +++ b/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/aps/system/services/resource/ResourcesService.java @@ -51,6 +51,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -61,7 +62,6 @@ import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.beanutils.BeanComparator; import org.apache.commons.lang3.StringUtils; import org.entando.entando.aps.system.exception.ResourceNotFoundException; import org.entando.entando.aps.system.exception.RestServerError; @@ -406,9 +406,7 @@ public AssetDto editAsset(String resourceId, String correlationCode, /****** Auxiliary Methods ******/ private BaseResourceDataBean createDataBeanFromResource(ResourceInterface resourceInterface) throws EntException { - AbstractResource resource = (AbstractResource) resourceInterface; - BaseResourceDataBean resourceFile = new BaseResourceDataBean(); resourceFile.setResourceType(resource.getType()); resourceFile.setResourceId(resource.getId()); @@ -419,40 +417,32 @@ private BaseResourceDataBean createDataBeanFromResource(ResourceInterface resour resourceFile.setCategories(resource.getCategories()); resourceFile.setMainGroup(resource.getMainGroup()); resourceFile.setFileName(resource.getMasterFileName()); - - ResourceInstance instance = null; - - if (resource.isMultiInstance()) { - instance = resource.getDefaultInstance(); - } else { - instance = ((AbstractMonoInstanceResource) resource).getInstance(); - } - + ResourceInstance instance = resource.getDefaultInstance(); try { - - boolean isProtected = resource.isProtectedResource(); - String absolutePath = null; - if (isProtected) { - absolutePath = resource.getFolder() + resource.getMainGroup() + "/" + instance.getFileName(); - } else { - absolutePath = resource.getFolder() + instance.getFileName(); - } - - String filePath = resource.getStorageManager().createFullPath(absolutePath, isProtected); - + String absolutePath = this.getDiskSubFolder(resource) + File.separator + instance.getFileName(); + String filePath = resource.getStorageManager().createFullPath(absolutePath, resource.isProtectedResource()); File file = new File(filePath); Path path = file.toPath(); Long size = Files.size(path) / 1000; - resourceFile.setInputStream(new FileInputStream(file)); resourceFile.setFileSize(size.intValue()); resourceFile.setMimeType(Files.probeContentType(path)); } catch (IOException | EntException e) { throw new EntException("Error reading file input stream", e); } - return resourceFile; } + + private String getDiskSubFolder(AbstractResource resource) { + StringBuilder diskFolder = new StringBuilder(resource.getFolder()); + if (!StringUtils.isBlank(resource.getFolderPath())) { + diskFolder.append(resource.getFolderPath()).append(File.separator); + } + if (resource.isProtectedResource()) { + diskFolder.append(resource.getMainGroup()).append(File.separator); + } + return diskFolder.toString(); + } private List convertCategories(List categories) { return categories.stream().map(code -> Optional.ofNullable(categoryManager.getCategory(code)) @@ -523,8 +513,7 @@ public void validateMimeType(String resourceType, final String mimeType) { private List getImageDimensions() { Map master = imageDimensionManager.getImageDimensions(); List dimensions = new ArrayList<>(master.values()); - BeanComparator comparator = new BeanComparator("dimx"); - Collections.sort(dimensions, comparator); + Collections.sort(dimensions, Comparator.comparing(ImageResourceDimension::getDimx)); return dimensions; } @@ -532,7 +521,7 @@ private FieldSearchFilter[] createSearchFilters(ListResourceRequest requestList) List filters = new ArrayList<>(); if (requestList.getType() != null) { filters.add( - new FieldSearchFilter(IResourceManager.RESOURCE_TYPE_FILTER_KEY, + new FieldSearchFilter<>(IResourceManager.RESOURCE_TYPE_FILTER_KEY, convertResourceType(requestList.getType()), false) ); } @@ -582,7 +571,7 @@ private FieldSearchFilter[] createSearchFilters(ListResourceRequest requestList) private FieldSearchFilter[] createFolderPathSearchFilter(String folderPath) { List filters = new ArrayList<>(); if (folderPath != null) { - filters.add(new FieldSearchFilter(IResourceManager.RESOURCE_FOLDER_PATH_FILTER_KEY, folderPath, true)); + filters.add(new FieldSearchFilter<>(IResourceManager.RESOURCE_FOLDER_PATH_FILTER_KEY, folderPath, true)); } return filters.stream().toArray(FieldSearchFilter[]::new); } @@ -593,10 +582,10 @@ private FieldSearchFilter checkDateFilter(FieldSearchFilter original) { Object start = original.getStart(); Object end = original.getEnd(); if (null != value) { - dateFilter = new FieldSearchFilter(original.getKey(), this.checkDate(value, original), false); + dateFilter = new FieldSearchFilter<>(original.getKey(), this.checkDate(value, original), false); dateFilter.setValueDateDelay(original.getValueDateDelay()); } else if (null != start || null != end) { - dateFilter = new FieldSearchFilter(original.getKey(), this.checkDate(start, original), this.checkDate(end, original)); + dateFilter = new FieldSearchFilter<>(original.getKey(), this.checkDate(start, original), this.checkDate(end, original)); dateFilter.setStartDateDelay(original.getStartDateDelay()); dateFilter.setEndDateDelay(original.getEndDateDelay()); } else { diff --git a/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/aps/system/services/widgettype/validators/WidgetValidatorCmsHelper.java b/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/aps/system/services/widgettype/validators/WidgetValidatorCmsHelper.java index 80b7f71e35..e6cb0adc8e 100644 --- a/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/aps/system/services/widgettype/validators/WidgetValidatorCmsHelper.java +++ b/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/aps/system/services/widgettype/validators/WidgetValidatorCmsHelper.java @@ -119,6 +119,7 @@ public static String extractConfigParam(WidgetConfigurationRequest widget, Strin return null; } + @SuppressWarnings("unchecked") public static List extractConfig(WidgetConfigurationRequest widget, String key) { Map config = (Map) widget.getConfig(); if (null != config) { diff --git a/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/web/content/ContentController.java b/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/web/content/ContentController.java index 9749ca6528..6bc5bfd9a1 100644 --- a/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/web/content/ContentController.java +++ b/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/web/content/ContentController.java @@ -38,6 +38,7 @@ import org.entando.entando.plugins.jacms.web.content.validator.RestContentListRequest; import org.entando.entando.web.common.annotation.RestAccessControl; import org.entando.entando.web.common.exceptions.ValidationGenericException; +import org.entando.entando.web.common.exceptions.ValidationUnprocessableEntityException; import org.entando.entando.web.common.model.PagedMetadata; import org.entando.entando.web.common.model.PagedRestResponse; import org.entando.entando.web.common.model.RestResponse; @@ -202,18 +203,24 @@ public ResponseEntity>> addContent(@Valid @R } List response = bodyRequest.stream() - .map(content -> { - this.getContentValidator().validate(content, bindingResult); - if (bindingResult.hasErrors()) { - throw new ValidationGenericException(bindingResult); - } - ContentDto result = this.getContentService().addContent(content, userDetails, bindingResult); - if (bindingResult.hasErrors()) { - throw new ValidationGenericException(bindingResult); - } - return result; - }) - .collect(Collectors.toList()); + .map(content -> { + this.getContentValidator().validate(content, bindingResult); + if (bindingResult.hasErrors()) { + throw new ValidationGenericException(bindingResult); + } + + this.getContentValidator().validateTypeCode(content, bindingResult); + if (bindingResult.hasErrors()) { + throw new ValidationUnprocessableEntityException(bindingResult); + } + + ContentDto result = this.getContentService().addContent(content, userDetails, bindingResult); + if (bindingResult.hasErrors()) { + throw new ValidationGenericException(bindingResult); + } + return result; + }) + .collect(Collectors.toList()); return new ResponseEntity<>(new SimpleRestResponse<>(response), HttpStatus.OK); } diff --git a/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/web/content/validator/ContentValidator.java b/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/web/content/validator/ContentValidator.java index b589e0fa82..654b274cb7 100644 --- a/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/web/content/validator/ContentValidator.java +++ b/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/web/content/validator/ContentValidator.java @@ -16,6 +16,7 @@ import com.agiletec.aps.system.common.entity.IEntityManager; import com.agiletec.plugins.jacms.aps.system.services.content.IContentManager; import com.agiletec.plugins.jacms.aps.system.services.content.model.ContentDto; +import java.util.Objects; import org.entando.entando.aps.system.exception.RestServerError; import org.entando.entando.aps.system.services.entity.model.EntityDto; import org.entando.entando.plugins.jacms.aps.system.services.content.IContentService; @@ -36,6 +37,8 @@ public class ContentValidator extends EntityValidator { @Autowired private IContentManager contentManager; + public static final String ERRCODE_TYPE_INVALID = "5"; + public boolean existContent(String code, String status) { boolean online = (IContentService.STATUS_ONLINE.equalsIgnoreCase(status)); try { @@ -46,6 +49,14 @@ public boolean existContent(String code, String status) { } } + public void validateTypeCode(Object target, Errors errors) { + EntityDto request = (EntityDto) target; + if (Objects.isNull(this.getEntityManager().getEntityPrototype(request.getTypeCode()))) { + errors.reject(ERRCODE_TYPE_INVALID,"entity.typeCode.invalid"); + } + } + + @Override protected IEntityManager getEntityManager() { return this.contentManager; diff --git a/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/web/resource/ResourcesController.java b/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/web/resource/ResourcesController.java index 5e8b263b18..4bdc76db4c 100644 --- a/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/web/resource/ResourcesController.java +++ b/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/web/resource/ResourcesController.java @@ -241,6 +241,6 @@ public ResponseEntity>> deleteAsset( } service.deleteAsset(resourceId, correlationCode); - return ResponseEntity.ok(new SimpleRestResponse<>(new HashMap())); + return ResponseEntity.ok(new SimpleRestResponse<>(new HashMap<>())); } } diff --git a/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/web/resource/validator/ResourcesValidator.java b/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/web/resource/validator/ResourcesValidator.java index e0e0512397..a023607a09 100644 --- a/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/web/resource/validator/ResourcesValidator.java +++ b/cms-plugin/src/main/java/org/entando/entando/plugins/jacms/web/resource/validator/ResourcesValidator.java @@ -66,6 +66,7 @@ public boolean resourceExists(String resourceId, String correlationCode) throws } public void resourceReferencesValidation(String resourceId, Errors errors) throws EntException { + @SuppressWarnings("unchecked") List references = ((ResourceUtilizer) contentManager).getResourceUtilizers(resourceId); if (references != null && references.size() > 0) { references.forEach(reference->{ diff --git a/cms-plugin/src/main/webapp/WEB-INF/plugins/jacms/apsadmin/jsp/resource/attachArchive.jsp b/cms-plugin/src/main/webapp/WEB-INF/plugins/jacms/apsadmin/jsp/resource/attachArchive.jsp index d0e939635a..4af7a46e65 100644 --- a/cms-plugin/src/main/webapp/WEB-INF/plugins/jacms/apsadmin/jsp/resource/attachArchive.jsp +++ b/cms-plugin/src/main/webapp/WEB-INF/plugins/jacms/apsadmin/jsp/resource/attachArchive.jsp @@ -165,6 +165,7 @@

+ diff --git a/cms-plugin/src/main/webapp/WEB-INF/plugins/jacms/apsadmin/jsp/resource/imageArchive.jsp b/cms-plugin/src/main/webapp/WEB-INF/plugins/jacms/apsadmin/jsp/resource/imageArchive.jsp index 2538e9a41d..0a57392792 100644 --- a/cms-plugin/src/main/webapp/WEB-INF/plugins/jacms/apsadmin/jsp/resource/imageArchive.jsp +++ b/cms-plugin/src/main/webapp/WEB-INF/plugins/jacms/apsadmin/jsp/resource/imageArchive.jsp @@ -177,6 +177,7 @@

+ diff --git a/cms-plugin/src/main/webapp/WEB-INF/plugins/jacms/apsadmin/jsp/resource/inc/resource_searchForm.jsp b/cms-plugin/src/main/webapp/WEB-INF/plugins/jacms/apsadmin/jsp/resource/inc/resource_searchForm.jsp index afbd5902d0..51e38c7f25 100644 --- a/cms-plugin/src/main/webapp/WEB-INF/plugins/jacms/apsadmin/jsp/resource/inc/resource_searchForm.jsp +++ b/cms-plugin/src/main/webapp/WEB-INF/plugins/jacms/apsadmin/jsp/resource/inc/resource_searchForm.jsp @@ -53,6 +53,14 @@ + <%-- resource id --%> +

+ +
+ +
+
+ <%-- category tree --%>
diff --git a/cms-plugin/src/test/java/com/agiletec/plugins/jacms/aps/system/services/content/widget/ContentViewerHelperIntegrationTest.java b/cms-plugin/src/test/java/com/agiletec/plugins/jacms/aps/system/services/content/widget/ContentViewerHelperIntegrationTest.java index 2fd1c2a846..4a4a254894 100644 --- a/cms-plugin/src/test/java/com/agiletec/plugins/jacms/aps/system/services/content/widget/ContentViewerHelperIntegrationTest.java +++ b/cms-plugin/src/test/java/com/agiletec/plugins/jacms/aps/system/services/content/widget/ContentViewerHelperIntegrationTest.java @@ -22,10 +22,13 @@ import com.agiletec.aps.system.services.page.IPage; import com.agiletec.aps.system.services.page.IPageManager; import com.agiletec.aps.system.services.page.Widget; +import com.agiletec.aps.system.services.user.UserDetails; import com.agiletec.aps.util.ApsProperties; import com.agiletec.plugins.jacms.aps.system.JacmsSystemConstants; import com.agiletec.plugins.jacms.aps.system.services.Jdk11CompatibleDateFormatter; +import com.agiletec.plugins.jacms.aps.system.services.content.IContentManager; +import com.agiletec.plugins.jacms.aps.system.services.content.model.Content; import com.agiletec.plugins.jacms.aps.system.services.contentmodel.ContentModel; import com.agiletec.plugins.jacms.aps.system.services.contentmodel.IContentModelManager; import com.agiletec.plugins.jacms.aps.system.services.dispenser.ContentRenderizationInfo; @@ -109,6 +112,18 @@ void testGetRenderedContentWithoutCurrentFrame() throws Throwable { private void executeGetRenderedContent(boolean useExtraTitle, int frame, String contentId, String expected, boolean nullExtraParam, boolean intoWidget) throws Throwable { + this.initRequestContext(useExtraTitle, frame, contentId, intoWidget); + String renderedContent = this._helper.getRenderedContent(null, null, true, _requestContext); + assertEquals(replaceNewLine(expected.trim()), replaceNewLine(renderedContent.trim())); + if (intoWidget) { + assertEquals(nullExtraParam, null != this._requestContext.getExtraParam(SystemConstants.EXTRAPAR_EXTRA_PAGE_TITLES)); + } else { + Assertions.assertNull(this._requestContext.getExtraParam(SystemConstants.EXTRAPAR_EXTRA_PAGE_TITLES)); + } + } + + private void initRequestContext(boolean useExtraTitle, int frame, + String contentId, boolean intoWidget) throws Throwable { this._requestContext.removeExtraParam(SystemConstants.EXTRAPAR_EXTRA_PAGE_TITLES); //clean ((MockHttpServletRequest) this._requestContext.getRequest()).removeParameter(SystemConstants.K_CONTENT_ID_PARAM); //clean IPage page = this.pageManager.getOnlineRoot(); @@ -122,13 +137,6 @@ private void executeGetRenderedContent(boolean useExtraTitle, int frame, this._requestContext.removeExtraParam(SystemConstants.EXTRAPAR_CURRENT_FRAME); this._requestContext.removeExtraParam(SystemConstants.EXTRAPAR_CURRENT_WIDGET); } - String renderedContent = this._helper.getRenderedContent(null, null, true, _requestContext); - assertEquals(replaceNewLine(expected.trim()), replaceNewLine(renderedContent.trim())); - if (intoWidget) { - assertEquals(nullExtraParam, null != this._requestContext.getExtraParam(SystemConstants.EXTRAPAR_EXTRA_PAGE_TITLES)); - } else { - Assertions.assertNull(this._requestContext.getExtraParam(SystemConstants.EXTRAPAR_EXTRA_PAGE_TITLES)); - } } void testGetRenderedByModel(String contentId, String modelId, String expected) throws Throwable { @@ -212,8 +220,47 @@ private void addNewContentModel(int id, String shape, String contentTypeCode) th this._contentModelManager.addContentModel(model); } + @Test + void testGetRenderedContent() throws Throwable { + Content content = this.contentManager.loadContent("ART1", true); + content.setId(null); + String newContentId = null; + try { + RequestContext reqCtx = getRequestContext(); + setUserOnSession("admin"); + UserDetails currentUser = (UserDetails) reqCtx.getRequest().getSession().getAttribute(SystemConstants.SESSIONPARAM_CURRENT_USER); + assertEquals("admin", currentUser.getUsername()); + newContentId = this.contentManager.insertOnLineContent(content); + this.initRequestContext(false, 0, newContentId, true); + + ContentRenderizationInfo info = this._helper.getRenderizationInfo(null, null, true, reqCtx); + Assertions.assertNotNull(info); + + this.contentManager.removeOnLineContent(content); + info = this._helper.getRenderizationInfo(null, null, true, reqCtx); + Assertions.assertNull(info); + + this.contentManager.insertOnLineContent(content); + info = this._helper.getRenderizationInfo(null, null, true, reqCtx); + Assertions.assertNotNull(info); + + this.contentManager.removeOnLineContent(content); + info = this._helper.getRenderizationInfo(null, null, true, reqCtx); + Assertions.assertNull(info); + + } catch (Throwable t) { + throw t; + } finally { + if (null != newContentId) { + Content newContent = this.contentManager.loadContent(newContentId, false); + this.contentManager.removeOnLineContent(newContent); + this.contentManager.deleteContent(newContent); + } + } + } + @BeforeEach - private void init() throws Exception { + void init() throws Exception { try { this._requestContext = this.getRequestContext(); Lang lang = new Lang(); @@ -222,6 +269,7 @@ private void init() throws Exception { this._requestContext.addExtraParam(SystemConstants.EXTRAPAR_CURRENT_LANG, lang); this.configureCurrentWidget(null, null); this._helper = (IContentViewerHelper) this.getApplicationContext().getBean("jacmsContentViewerHelper"); + this.contentManager = this.getApplicationContext().getBean(IContentManager.class); } catch (Throwable t) { throw new Exception(t); } @@ -248,6 +296,7 @@ private void configureCurrentWidget(String contentId, String modelId) { private RequestContext _requestContext; private IPageManager pageManager; private IContentViewerHelper _helper; + private IContentManager contentManager; private IContentModelManager _contentModelManager = null; } diff --git a/cms-plugin/src/test/java/com/agiletec/plugins/jacms/aps/system/services/content/widget/UserFilterOptionBeanTest.java b/cms-plugin/src/test/java/com/agiletec/plugins/jacms/aps/system/services/content/widget/UserFilterOptionBeanTest.java new file mode 100644 index 0000000000..da2aecbf96 --- /dev/null +++ b/cms-plugin/src/test/java/com/agiletec/plugins/jacms/aps/system/services/content/widget/UserFilterOptionBeanTest.java @@ -0,0 +1,197 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ +package com.agiletec.plugins.jacms.aps.system.services.content.widget; + +import com.agiletec.aps.system.common.entity.model.EntitySearchFilter; +import com.agiletec.aps.system.common.entity.model.IApsEntity; +import com.agiletec.aps.system.common.entity.model.attribute.BooleanAttribute; +import com.agiletec.aps.system.common.entity.model.attribute.DateAttribute; +import com.agiletec.aps.system.common.entity.model.attribute.NumberAttribute; +import com.agiletec.aps.system.common.entity.model.attribute.TextAttribute; +import com.agiletec.aps.system.services.lang.Lang; +import com.agiletec.aps.util.DateConverter; +import com.agiletec.plugins.jacms.aps.system.services.content.widget.UserFilterOptionBean.AttributeFormFieldError; +import java.math.BigDecimal; +import java.util.Properties; +import javax.servlet.http.HttpServletRequest; +import org.entando.entando.aps.system.services.searchengine.SearchEngineFilter; +import org.entando.entando.ent.exception.EntException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class UserFilterOptionBeanTest { + + @Mock + private IApsEntity prototype; + + @Test + void shouldReturnErrorOnInvalidAttribute() { + Properties prop = new Properties(); + prop.setProperty(UserFilterOptionBean.PARAM_KEY, "textAttribute"); + prop.setProperty(UserFilterOptionBean.TYPE_ATTRIBUTE, "text"); + prop.setProperty(UserFilterOptionBean.PARAM_IS_ATTRIBUTE_FILTER, "true"); + Mockito.when(prototype.getAttribute("textAttribute")).thenReturn(null); + Assertions.assertThrows(EntException.class, () -> { + new UserFilterOptionBean(prop, prototype); + }); + } + + @Test + void shouldExtractTextFilter() throws Throwable { + Properties prop = new Properties(); + prop.setProperty(UserFilterOptionBean.PARAM_KEY, "textAttribute"); + prop.setProperty(UserFilterOptionBean.TYPE_ATTRIBUTE, "text"); + prop.setProperty(UserFilterOptionBean.PARAM_IS_ATTRIBUTE_FILTER, "true"); + TextAttribute textAttribute = Mockito.mock(TextAttribute.class); + Mockito.when(textAttribute.getType()).thenReturn("Text"); + Mockito.when(textAttribute.getName()).thenReturn("textAttribute"); + Mockito.when(prototype.getAttribute("textAttribute")).thenReturn(textAttribute); + Lang lang = new Lang(); + lang.setCode("en"); + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + // String[] fieldsSuffix = {"_textFieldName"}; + // String fieldName = paramName + fieldSuffix + frameIdSuffix; + Mockito.when(request.getParameter("textAttribute_textFieldName_frame8")).thenReturn("text"); + UserFilterOptionBean ubof = new UserFilterOptionBean(prop, prototype, 8, lang, "yyyy-MM-dd", request); + EntitySearchFilter ef = ubof.getEntityFilter(); + Assertions.assertEquals("textAttribute", ef.getKey()); + Assertions.assertEquals("text", ef.getValue()); + Assertions.assertEquals(true, ef.isAttributeFilter()); + SearchEngineFilter filter = ubof.extractFilter(); + Assertions.assertEquals("Text:textAttribute", filter.getKey()); + Assertions.assertEquals("text", filter.getValue()); + Assertions.assertEquals(SearchEngineFilter.TextSearchOption.EXACT, filter.getTextSearchOption()); + } + + @Test + void shouldExtractDateFilter() throws Throwable { + Properties prop = new Properties(); + prop.setProperty(UserFilterOptionBean.PARAM_KEY, "dateAttribute"); + prop.setProperty(UserFilterOptionBean.TYPE_ATTRIBUTE, "date"); + prop.setProperty(UserFilterOptionBean.PARAM_IS_ATTRIBUTE_FILTER, "true"); + DateAttribute dateAttribute = Mockito.mock(DateAttribute.class); + Mockito.when(dateAttribute.getType()).thenReturn("Date"); + Mockito.when(dateAttribute.getName()).thenReturn("dateAttribute"); + Mockito.when(prototype.getAttribute("dateAttribute")).thenReturn(dateAttribute); + Lang lang = new Lang(); + lang.setCode("it"); + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + // String[] fieldsSuffix = {"_dateStartFieldName", "_dateEndFieldName"}; + Mockito.when(request.getParameter("dateAttribute_dateStartFieldName_frame7")).thenReturn("2023-10-23"); + Mockito.when(request.getParameter("dateAttribute_dateEndFieldName_frame7")).thenReturn("2023-11-17"); + UserFilterOptionBean ubof = new UserFilterOptionBean(prop, prototype, 7, lang, "yyyy-MM-dd", request); + Assertions.assertNull(ubof.getFormFieldErrors()); + EntitySearchFilter ef = ubof.getEntityFilter(); + Assertions.assertEquals("dateAttribute", ef.getKey()); + Assertions.assertNull(ef.getValue()); + Assertions.assertEquals(true, ef.isAttributeFilter()); + Assertions.assertEquals(DateConverter.parseDate("2023-10-23", "yyyy-MM-dd"), ef.getStart()); + Assertions.assertEquals(DateConverter.parseDate("2023-11-17", "yyyy-MM-dd"), ef.getEnd()); + SearchEngineFilter filter = ubof.extractFilter(); + Assertions.assertEquals("Date:dateAttribute", filter.getKey()); + Assertions.assertNull(filter.getValue()); + Assertions.assertEquals(DateConverter.parseDate("2023-10-23", "yyyy-MM-dd"), filter.getStart()); + Assertions.assertEquals(DateConverter.parseDate("2023-11-17", "yyyy-MM-dd"), filter.getEnd()); + Assertions.assertNull(filter.getTextSearchOption()); + } + + @Test + void shouldReturnErrorWithWrongDateRange() throws Throwable { + Properties prop = new Properties(); + prop.setProperty(UserFilterOptionBean.PARAM_KEY, "dateAttr"); + prop.setProperty(UserFilterOptionBean.TYPE_ATTRIBUTE, "date"); + prop.setProperty(UserFilterOptionBean.PARAM_IS_ATTRIBUTE_FILTER, "true"); + DateAttribute dateAttribute = Mockito.mock(DateAttribute.class); + Mockito.when(dateAttribute.getType()).thenReturn("Date"); + Mockito.when(dateAttribute.getName()).thenReturn("dateAttr"); + Mockito.when(prototype.getAttribute("dateAttr")).thenReturn(dateAttribute); + Lang lang = new Lang(); + lang.setCode("it"); + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + // String[] fieldsSuffix = {"_dateStartFieldName", "_dateEndFieldName"}; + Mockito.when(request.getParameter("dateAttr_dateStartFieldName_frame7")).thenReturn("2023-10-23"); + Mockito.when(request.getParameter("dateAttr_dateEndFieldName_frame7")).thenReturn("2023-07-17"); + UserFilterOptionBean ubof = new UserFilterOptionBean(prop, prototype, 7, lang, "yyyy-MM-dd", request); + Assertions.assertEquals(1, ubof.getFormFieldErrors().size()); + AttributeFormFieldError error = ubof.getFormFieldErrors().get("dateAttr_dateEndFieldName_frame7"); + Assertions.assertEquals("dateAttr", error.getAttributeName()); + Assertions.assertEquals("dateAttr_dateEndFieldName_frame7", error.getFieldName()); + Assertions.assertEquals(AttributeFormFieldError.INVALID_RANGE_KEY, error.getErrorKey()); + } + + @Test + void shouldExtractNumberFilter() throws Throwable { + Properties prop = new Properties(); + prop.setProperty(UserFilterOptionBean.PARAM_KEY, "numberAttribute"); + prop.setProperty(UserFilterOptionBean.TYPE_ATTRIBUTE, "number"); + prop.setProperty(UserFilterOptionBean.PARAM_IS_ATTRIBUTE_FILTER, "true"); + NumberAttribute numberAttribute = Mockito.mock(NumberAttribute.class); + Mockito.when(numberAttribute.getType()).thenReturn("Number"); + Mockito.when(numberAttribute.getName()).thenReturn("numberAttribute"); + Mockito.when(prototype.getAttribute("numberAttribute")).thenReturn(numberAttribute); + Lang lang = new Lang(); + lang.setCode("it"); + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + // String[] fieldsSuffix = {"_numberStartFieldName", "_numberEndFieldName"}; + Mockito.when(request.getParameter("numberAttribute_numberStartFieldName_frame3")).thenReturn("19"); + Mockito.when(request.getParameter("numberAttribute_numberEndFieldName_frame3")).thenReturn("128"); + UserFilterOptionBean ubof = new UserFilterOptionBean(prop, prototype, 3, lang, "yyyy-MM-dd", request); + Assertions.assertNull(ubof.getFormFieldErrors()); + EntitySearchFilter ef = ubof.getEntityFilter(); + Assertions.assertEquals("numberAttribute", ef.getKey()); + Assertions.assertNull(ef.getValue()); + Assertions.assertEquals(true, ef.isAttributeFilter()); + Assertions.assertEquals(19, ((BigDecimal) ef.getStart()).intValue()); + Assertions.assertEquals(128, ((BigDecimal) ef.getEnd()).intValue()); + SearchEngineFilter filter = ubof.extractFilter(); + Assertions.assertEquals("Number:numberAttribute", filter.getKey()); + Assertions.assertNull(filter.getValue()); + Assertions.assertEquals(19, ((BigDecimal) filter.getStart()).intValue()); + Assertions.assertEquals(128, ((BigDecimal) filter.getEnd()).intValue()); + Assertions.assertNull(filter.getTextSearchOption()); + } + + @Test + void shouldExtractBooleanFilter() throws Throwable { + Properties prop = new Properties(); + prop.setProperty(UserFilterOptionBean.PARAM_KEY, "booleanAttribute"); + prop.setProperty(UserFilterOptionBean.TYPE_ATTRIBUTE, "boolean"); + prop.setProperty(UserFilterOptionBean.PARAM_IS_ATTRIBUTE_FILTER, "true"); + BooleanAttribute booleanAttribute = Mockito.mock(BooleanAttribute.class); + Mockito.when(booleanAttribute.getType()).thenReturn("Boolean"); + Mockito.when(booleanAttribute.getName()).thenReturn("booleanAttribute"); + Mockito.when(prototype.getAttribute("booleanAttribute")).thenReturn(booleanAttribute); + Lang lang = new Lang(); + lang.setCode("en"); + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + // String[] fieldsSuffix = {"_booleanFieldName", "_booleanFieldName_ignore", "_booleanFieldName_control"}; + Mockito.when(request.getParameter("booleanAttribute_booleanFieldName_frame9")).thenReturn("true"); + Mockito.when(request.getParameter("booleanAttribute_booleanFieldName_ignore_frame9")).thenReturn(null); + Mockito.when(request.getParameter("booleanAttribute_booleanFieldName_control_frame9")).thenReturn("true"); + UserFilterOptionBean ubof = new UserFilterOptionBean(prop, prototype, 9, lang, "yyyy-MM-dd", request); + Assertions.assertNull(ubof.getFormFieldErrors()); + EntitySearchFilter ef = ubof.getEntityFilter(); + Assertions.assertEquals("booleanAttribute", ef.getKey()); + Assertions.assertEquals("true", ef.getValue()); + Assertions.assertEquals(true, ef.isAttributeFilter()); + Assertions.assertNull(ef.getStart()); + Assertions.assertNull(ef.getEnd()); + } + +} diff --git a/cms-plugin/src/test/java/com/agiletec/plugins/jacms/aps/system/services/dispenser/TestContentDispenser.java b/cms-plugin/src/test/java/com/agiletec/plugins/jacms/aps/system/services/dispenser/TestContentDispenser.java index 300f143999..7e0c471de9 100644 --- a/cms-plugin/src/test/java/com/agiletec/plugins/jacms/aps/system/services/dispenser/TestContentDispenser.java +++ b/cms-plugin/src/test/java/com/agiletec/plugins/jacms/aps/system/services/dispenser/TestContentDispenser.java @@ -32,6 +32,9 @@ import static org.junit.jupiter.api.Assertions.assertNull; import com.agiletec.aps.system.services.user.IUserManager; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -41,6 +44,9 @@ * @author W.Ambu - E.Santoboni */ class TestContentDispenser extends BaseTestCase { + + private static final List usernamesForTest = List.of("supervisorCoach", "mainEditor", "pageManagerCoach", + "supervisorCustomers", "pageManagerCustomers", "editorCustomers", "editorCoach", "admin"); @Test void testGetRenderedContent_1() throws Throwable { @@ -115,43 +121,30 @@ void testGetRenderedContent_2() throws Throwable { @Test void testGetRenderedContent_3_1() throws Throwable { - this.executeTestGetRenderedContent_3(Boolean.FALSE, false); - this.executeTestGetRenderedContent_3(Boolean.FALSE, true); - } - - @Test - void testGetRenderedContent_3_2() throws Throwable { - this.executeTestGetRenderedContent_3(Boolean.TRUE, false); - this.executeTestGetRenderedContent_3(Boolean.TRUE, true); - } - - @Test - void testGetRenderedContent_3_3() throws Throwable { - this.executeTestGetRenderedContent_3(null, false); + for (String username : usernamesForTest) { + this.executeTestGetRenderedContent_3(Boolean.FALSE, username); + this.executeTestGetRenderedContent_3(Boolean.TRUE, username); + this.executeTestGetRenderedContent_3(null, username); + } + this.executeTestGetRenderedContent_3(Boolean.TRUE, null); } - protected void executeTestGetRenderedContent_3(Boolean cached, boolean useCurrentUser) throws Throwable { + protected void executeTestGetRenderedContent_3(Boolean cached, String username) throws Throwable { Content content = this._contentManager.loadContent("ART120", true); content.setId(null); try { + String id = this._contentManager.insertOnLineContent(content); RequestContext reqCtx = this.getRequestContext(); - this.setUserOnSession("admin"); + this.setUserOnSession(username); UserDetails currentUser = (UserDetails) reqCtx.getRequest().getSession().getAttribute(SystemConstants.SESSIONPARAM_CURRENT_USER); - assertEquals("admin", currentUser.getUsername()); - this._contentManager.insertOnLineContent(content); - String cacheKey = (useCurrentUser) ? - BaseContentDispenser.getRenderizationInfoCacheKey(content.getId(), 2, "it", reqCtx) : - BaseContentDispenser.getRenderizationInfoCacheKey(content.getId(), 2, "it", currentUser); + Optional.ofNullable(username).ifPresent(notNull -> assertEquals(username, currentUser.getUsername())); + String cacheKey = BaseContentDispenser.getRenderizationInfoCacheKey(id, 2, "it", reqCtx); assertNull(this._cacheInfoManager.getFromCache(ICacheInfoManager.DEFAULT_CACHE_NAME, cacheKey)); ContentRenderizationInfo outputInfo = null; if (null == cached) { - outputInfo = this._contentDispenser.getRenderizationInfo(content.getId(), 2, "it", reqCtx); + outputInfo = this._contentDispenser.getRenderizationInfo(id, 2, "it", reqCtx); } else { - if (useCurrentUser) { - outputInfo = this._contentDispenser.getRenderizationInfo(content.getId(), 2, "it", currentUser, cached); - } else { - outputInfo = this._contentDispenser.getRenderizationInfo(content.getId(), 2, "it", reqCtx, cached); - } + outputInfo = this._contentDispenser.getRenderizationInfo(id, 2, "it", reqCtx, cached); } assertNotNull(outputInfo); this.waitNotifyingThread(); @@ -164,6 +157,7 @@ protected void executeTestGetRenderedContent_3(Boolean cached, boolean useCurren assertNotNull(renderedInfoInCache); assertNotNull(contentAuthInfoInCache); } + content.setId(id); this._contentManager.insertOnLineContent(content); this.waitNotifyingThread(); assertNull(this._cacheInfoManager.getFromCache(ICacheInfoManager.DEFAULT_CACHE_NAME, cacheKey)); @@ -177,6 +171,38 @@ protected void executeTestGetRenderedContent_3(Boolean cached, boolean useCurren } } + @Test + void testGetRenderedContent_3_2() throws Throwable { + RequestContext reqCtx = this.getRequestContext(); + Content content = this._contentManager.loadContent("ART120", true); + content.setId(null); + try { + String id = this._contentManager.insertOnLineContent(content); + List cacheKeys = new ArrayList<>(); + for (String username : usernamesForTest) { + setUserOnSession(username); + UserDetails currentUser = (UserDetails) reqCtx.getRequest().getSession().getAttribute(SystemConstants.SESSIONPARAM_CURRENT_USER); + assertEquals(username, currentUser.getUsername()); + String cacheKey = BaseContentDispenser.getRenderizationInfoCacheKey(id, 2, "it", reqCtx); + assertNull(this._cacheInfoManager.getFromCache(ICacheInfoManager.DEFAULT_CACHE_NAME, cacheKey)); + ContentRenderizationInfo outputInfo = this._contentDispenser.getRenderizationInfo(id, 2, "it", reqCtx); + assertNotNull(outputInfo); + cacheKeys.add(cacheKey); + } + cacheKeys.stream().forEach(key -> assertNotNull(this._cacheInfoManager.getFromCache(ICacheInfoManager.DEFAULT_CACHE_NAME, key))); + content.setId(id); + this._contentManager.insertOnLineContent(content); + this.waitNotifyingThread(); + cacheKeys.stream().forEach(key -> assertNull(this._cacheInfoManager.getFromCache(ICacheInfoManager.DEFAULT_CACHE_NAME, key))); + } catch (Throwable t) { + throw t; + } finally { + if (null != content.getId()) { + this._contentManager.deleteContent(content); + } + } + } + @Test void testGetRenderedContent_4() throws Throwable { String contentId = "ART120"; diff --git a/cms-plugin/src/test/java/com/agiletec/plugins/jacms/apsadmin/resource/TestMultipleResourceAction.java b/cms-plugin/src/test/java/com/agiletec/plugins/jacms/apsadmin/resource/MultipleResourceActionIntegrationTest.java similarity index 98% rename from cms-plugin/src/test/java/com/agiletec/plugins/jacms/apsadmin/resource/TestMultipleResourceAction.java rename to cms-plugin/src/test/java/com/agiletec/plugins/jacms/apsadmin/resource/MultipleResourceActionIntegrationTest.java index 4b2e01875d..a6e9082af7 100644 --- a/cms-plugin/src/test/java/com/agiletec/plugins/jacms/apsadmin/resource/TestMultipleResourceAction.java +++ b/cms-plugin/src/test/java/com/agiletec/plugins/jacms/apsadmin/resource/MultipleResourceActionIntegrationTest.java @@ -39,7 +39,7 @@ /** * @author E.Santoboni */ -class TestMultipleResourceAction extends ApsAdminBaseTestCase { +class MultipleResourceActionIntegrationTest extends ApsAdminBaseTestCase { private IResourceManager resourceManager = null; @@ -106,6 +106,7 @@ void testSaveNewResourceMultipleResourceValidation() throws Throwable { this.addParameter("resourceTypeCode", "Image"); this.addParameter("mainGroup", "test"); this.addParameter("descr_0", "test"); + this.addParameter("fileUploadName_0", "test".repeat(100)); String result = this.executeAction(); ActionSupport action = this.getAction(); Map> actionFieldErrors = action.getFieldErrors(); diff --git a/cms-plugin/src/test/java/com/agiletec/plugins/jacms/apsadmin/resource/MultipleResourceActionTest.java b/cms-plugin/src/test/java/com/agiletec/plugins/jacms/apsadmin/resource/MultipleResourceActionTest.java new file mode 100644 index 0000000000..8f472b7f1b --- /dev/null +++ b/cms-plugin/src/test/java/com/agiletec/plugins/jacms/apsadmin/resource/MultipleResourceActionTest.java @@ -0,0 +1,156 @@ +/* +* Copyright 2024-Present Entando Inc. (http://www.entando.com) All rights reserved. +* +* This library is free software; you can redistribute it and/or modify it under +* the terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; either version 2.1 of the License, or (at your option) +* any later version. +* +* This library is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +* details. + */ +package com.agiletec.plugins.jacms.apsadmin.resource; + +import com.agiletec.aps.system.services.category.ICategoryManager; +import com.agiletec.aps.system.services.group.Group; +import com.agiletec.aps.system.services.group.IGroupManager; +import com.agiletec.apsadmin.system.ApsAdminSystemConstants; +import com.agiletec.plugins.jacms.aps.system.services.resource.IResourceManager; +import com.agiletec.plugins.jacms.apsadmin.resource.helper.IResourceActionHelper; +import java.util.List; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MultipleResourceActionTest { + + @Mock + private HttpServletRequest request; + + @Mock + private IGroupManager groupManager; + + @Mock + private IResourceManager resourceManager; + + @Mock + private ICategoryManager categoryManager; + + @Mock + private IResourceActionHelper resourceActionHelper; + + @InjectMocks + @Spy + private MultipleResourceAction action; + + @BeforeEach + void initMocks() { + MockitoAnnotations.initMocks(this); + action.setServletRequest(this.request); + action.setGroupManager(this.groupManager); + action.setResourceManager(this.resourceManager); + action.setCategoryManager(this.categoryManager); + action.setResourceActionHelper(this.resourceActionHelper); + Mockito.lenient().doReturn("text").when(action).getText(Mockito.anyString()); + } + + @Test + void validateRightForm() { + action.setMainGroup(Group.FREE_GROUP_NAME); + action.setStrutsAction(ApsAdminSystemConstants.ADD); + Map parameterMap = Map.of( + MultipleResourceAction.FILE_DESCR_FIELD + "0", new String[]{"Descrizione file"}, + MultipleResourceAction.FILE_UPLOAD_ID_FIELD + "0", new String[]{"0"}, + MultipleResourceAction.FILE_NAME_FIELD + "0", new String[]{"file.txt"}, + MultipleResourceAction.FILE_CONTENT_TYPE_FIELD + "0", new String[]{"text/plain"} + ); + Mockito.when(this.request.getParameterMap()).thenReturn(parameterMap); + action.validate(); + Assertions.assertTrue(action.getFieldErrors().isEmpty()); + } + + @Test + void validateLongDescription() { + action.setMainGroup(Group.FREE_GROUP_NAME); + action.setStrutsAction(ApsAdminSystemConstants.ADD); + Map parameterMap = Map.of( + MultipleResourceAction.FILE_DESCR_FIELD + "0", new String[]{"Descrizione file"}, + MultipleResourceAction.FILE_UPLOAD_ID_FIELD + "0", new String[]{"0"}, + MultipleResourceAction.FILE_NAME_FIELD + "0", new String[]{"file".repeat(200)+".txt"}, + MultipleResourceAction.FILE_CONTENT_TYPE_FIELD + "0", new String[]{"text/plain"} + ); + Mockito.doReturn("text").when(action).getText(Mockito.anyString(), Mockito.anyList()); + Mockito.when(this.request.getParameterMap()).thenReturn(parameterMap); + action.validate(); + Assertions.assertFalse(action.getFieldErrors().isEmpty()); + Assertions.assertEquals(1, action.getFieldErrors().get(MultipleResourceAction.FILE_NAME_FIELD + "0").size()); + } + + @Test + void validateFileAlreadyPresentOnAddExecution() throws Throwable { + action.setMainGroup(Group.FREE_GROUP_NAME); + action.setStrutsAction(ApsAdminSystemConstants.ADD); + Map parameterMap = Map.of( + MultipleResourceAction.FILE_DESCR_FIELD + "0", new String[]{"Descrizione file"}, + MultipleResourceAction.FILE_UPLOAD_ID_FIELD + "0", new String[]{"0"}, + MultipleResourceAction.FILE_NAME_FIELD + "0", new String[]{"file.txt"}, + MultipleResourceAction.FILE_CONTENT_TYPE_FIELD + "0", new String[]{"text/plain"} + ); + Mockito.when(this.resourceManager.searchResourcesId(Mockito.any(), Mockito.anyList())).thenReturn(List.of("21")); + Mockito.doReturn("text").when(action).getText(Mockito.anyString(), Mockito.any(String[].class)); + Mockito.when(this.request.getParameterMap()).thenReturn(parameterMap); + action.validate(); + Assertions.assertFalse(action.getFieldErrors().isEmpty()); + Assertions.assertEquals(1, action.getFieldErrors().get(MultipleResourceAction.FILE_NAME_FIELD + "0").size()); + } + + @Test + void validateFileAlreadyPresentOnEditExecution() throws Throwable { + action.setMainGroup(Group.FREE_GROUP_NAME); + action.setStrutsAction(ApsAdminSystemConstants.EDIT); + action.setResourceId("100"); + Map parameterMap = Map.of( + MultipleResourceAction.FILE_DESCR_FIELD + "0", new String[]{"Descrizione file"}, + MultipleResourceAction.FILE_UPLOAD_ID_FIELD + "0", new String[]{"0"}, + MultipleResourceAction.FILE_NAME_FIELD + "0", new String[]{"file.txt"}, + MultipleResourceAction.FILE_CONTENT_TYPE_FIELD + "0", new String[]{"text/plain"} + ); + Mockito.when(this.request.getParameterMap()).thenReturn(parameterMap); + Mockito.doReturn("text").when(action).getText(Mockito.anyString(), Mockito.any(String[].class)); + + Mockito.when(this.resourceManager.searchResourcesId(Mockito.any(), Mockito.anyList())).thenReturn(List.of("37")); + action.validate(); + Assertions.assertFalse(action.getFieldErrors().isEmpty()); + Assertions.assertEquals(1, action.getFieldErrors().get(MultipleResourceAction.FILE_NAME_FIELD + "0").size()); + } + + @Test + void validateFileNotPresentOnEditExecution() throws Throwable { + action.setMainGroup(Group.FREE_GROUP_NAME); + action.setStrutsAction(ApsAdminSystemConstants.EDIT); + action.setResourceId("120"); + Map parameterMap = Map.of( + MultipleResourceAction.FILE_DESCR_FIELD + "0", new String[]{"Descrizione file"}, + MultipleResourceAction.FILE_UPLOAD_ID_FIELD + "0", new String[]{"0"}, + MultipleResourceAction.FILE_NAME_FIELD + "0", new String[]{"file.txt"}, + MultipleResourceAction.FILE_CONTENT_TYPE_FIELD + "0", new String[]{"text/plain"} + ); + Mockito.when(this.request.getParameterMap()).thenReturn(parameterMap); + Mockito.when(this.resourceManager.searchResourcesId(Mockito.any(), Mockito.anyList())).thenReturn(List.of("120")); + action.validate(); + Assertions.assertTrue(action.getFieldErrors().isEmpty()); + } + +} diff --git a/cms-plugin/src/test/java/com/agiletec/plugins/jacms/apsadmin/resource/TestResourceFinderAction.java b/cms-plugin/src/test/java/com/agiletec/plugins/jacms/apsadmin/resource/TestResourceFinderAction.java index fd9281c0ab..19da2c3b96 100644 --- a/cms-plugin/src/test/java/com/agiletec/plugins/jacms/apsadmin/resource/TestResourceFinderAction.java +++ b/cms-plugin/src/test/java/com/agiletec/plugins/jacms/apsadmin/resource/TestResourceFinderAction.java @@ -17,6 +17,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.agiletec.aps.system.common.model.dao.SearcherDaoPaginatedResult; import com.agiletec.aps.system.services.category.Category; import com.agiletec.aps.system.services.group.Group; import com.agiletec.apsadmin.ApsAdminBaseTestCase; @@ -24,6 +25,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** @@ -37,6 +40,7 @@ void testViewImageResources() throws Throwable { assertEquals(Action.SUCCESS, result); ResourceFinderAction action = (ResourceFinderAction) this.getAction(); String resourceTypeCode = action.getResourceTypeCode(); + Assertions.assertFalse(action.isOpenCollapsed()); assertNotNull(resourceTypeCode); assertEquals("Image", resourceTypeCode); assertEquals(3, action.getResources().size()); @@ -86,6 +90,19 @@ private String executeShowList(String userName, String resourceTypeCode) throws return this.executeAction(); } + @Test + void testSearchResourcesById() throws Throwable { + String result = this.executeSearchResources("admin", "Image", Map.of("searchedResourceId", "2")); + assertEquals(Action.SUCCESS, result); + ResourceFinderAction action = (ResourceFinderAction) this.getAction(); + Assertions.assertFalse(action.getResources().isEmpty()); + Assertions.assertTrue(action.isOpenCollapsed()); + SearcherDaoPaginatedResult pagination = action.getPaginatedResourcesId(10); + assertEquals(2, pagination.getCount()); + assertTrue(pagination.getList().contains("82")); + assertTrue(pagination.getList().contains("22")); + } + @Test void testSearchResources_1() throws Throwable { String result = this.executeSearchResource("admin", "Attach", "WrongDescription", null, null, null); @@ -239,4 +256,12 @@ private String executeSearchResourceWithOrder(String username, String resourceTy return this.executeAction(); } + private String executeSearchResources(String username, + String resourceTypeCode, Map parameters) throws Throwable { + this.setUserOnSession(username); + this.initAction("/do/jacms/Resource", "search"); + this.addParameters(parameters); + return this.executeAction(); + } + } diff --git a/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/aps/system/services/content/ContentServiceTest.java b/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/aps/system/services/content/ContentServiceTest.java index 9021434891..732ea50a48 100644 --- a/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/aps/system/services/content/ContentServiceTest.java +++ b/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/aps/system/services/content/ContentServiceTest.java @@ -525,6 +525,23 @@ private RestContentListRequest prepareGetContentTest(UserDetails user) throws En return requestList; } + @Test + void shouldFindReferences() throws Exception { + ContentServiceUtilizer utilizer = Mockito.mock(ContentServiceUtilizer.class); + List components = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + IComponentDto dto = Mockito.mock(IComponentDto.class); + components.add(dto); + } + when(utilizer.getManagerName()).thenReturn("service"); + when(utilizer.getContentUtilizer(Mockito.anyString())).thenReturn(components); + when(this.applicationContext.getBeansOfType(ContentServiceUtilizer.class)).thenReturn(Map.of("service", utilizer)); + when(this.contentManager.loadContent("ART123", false)).thenReturn(Mockito.mock(Content.class)); + PagedMetadata result = contentService.getContentReferences("ART123", + "service", Mockito.mock(UserDetails.class), new RestListRequest()); + Assertions.assertEquals(5, result.getBody().size()); + } + @Test void shouldFindComponentDto() throws Exception { this.addMockedContent("ART123", "ART", null, null); diff --git a/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/web/content/1_POST_valid_with_some_links.json b/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/web/content/1_POST_valid_with_some_links.json index 3908d27764..cb59587872 100644 --- a/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/web/content/1_POST_valid_with_some_links.json +++ b/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/web/content/1_POST_valid_with_some_links.json @@ -16,7 +16,7 @@ "urlDest": "https://myurl.com", "rel": "rel", "target": "_blank", - "hreflang": "it" + "hrefLang": "it" }, "values": { "it": "My URL Link" diff --git a/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/web/content/1_PUT_valid_with_link.json b/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/web/content/1_PUT_valid_with_link.json index 9c30a101fe..ee00481ccc 100644 --- a/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/web/content/1_PUT_valid_with_link.json +++ b/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/web/content/1_PUT_valid_with_link.json @@ -15,7 +15,7 @@ "pageDest":"pagina_11", "target": "_blank", "rel": "alternate", - "hreflang": "it" + "hrefLang": "it" }, "values":{ "it":"pagina_11" diff --git a/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/web/content/ContentControllerIntegrationTest.java b/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/web/content/ContentControllerIntegrationTest.java index e4400fdb98..d9ea92bb54 100644 --- a/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/web/content/ContentControllerIntegrationTest.java +++ b/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/web/content/ContentControllerIntegrationTest.java @@ -59,6 +59,7 @@ import com.agiletec.plugins.jacms.aps.system.services.content.model.ContentDto; import com.agiletec.plugins.jacms.aps.system.services.content.model.SymbolicLink; import com.agiletec.plugins.jacms.aps.system.services.content.model.attribute.ImageAttribute; +import com.agiletec.plugins.jacms.aps.system.services.content.model.attribute.LinkAttribute; import com.agiletec.plugins.jacms.aps.system.services.resource.IResourceManager; import com.agiletec.plugins.jacms.aps.system.services.resource.model.ResourceInterface; import com.agiletec.plugins.jacms.aps.system.services.searchengine.ICmsSearchEngineManager; @@ -397,7 +398,6 @@ void testAddUpdateContentWithLinkAttribute() throws Exception { .andExpect(jsonPath("$.payload.size()", is(1))) .andExpect(jsonPath("$.errors.size()", is(0))) .andExpect(jsonPath("$.metaData.size()", is(0))) - .andExpect(jsonPath("$.payload[0].id", Matchers.anything())) .andExpect(jsonPath("$.payload[0].attributes[0].code", is("link1"))) .andExpect(jsonPath("$.payload[0].attributes[0].value", Matchers.isEmptyOrNullString())); @@ -412,7 +412,6 @@ void testAddUpdateContentWithLinkAttribute() throws Exception { .andExpect(jsonPath("$.payload.size()", is(1))) .andExpect(jsonPath("$.errors.size()", is(0))) .andExpect(jsonPath("$.metaData.size()", is(0))) - .andExpect(jsonPath("$.payload[0].id", Matchers.anything())) .andExpect(jsonPath("$.payload[0].attributes[0].code", is("link1"))) .andExpect(jsonPath("$.payload[0].attributes[0].value.destType", is(SymbolicLink.PAGE_TYPE))) @@ -420,8 +419,7 @@ void testAddUpdateContentWithLinkAttribute() throws Exception { .andExpect(jsonPath("$.payload[0].attributes[0].value.symbolicDestination", is("#!P;pagina_11!#"))) .andExpect(jsonPath("$.payload[0].attributes[0].value.rel", is("alternate"))) .andExpect(jsonPath("$.payload[0].attributes[0].value.target", is("_blank"))) - .andExpect(jsonPath("$.payload[0].attributes[0].value.hreflang", is("it"))) - + .andExpect(jsonPath("$.payload[0].attributes[0].value.hrefLang", is("it"))) .andExpect(jsonPath("$.payload[0].attributes[0].values.it", is("pagina_11"))); this.executeContentPut("1_PUT_invalid_with_link.json", newContentId, accessToken, status().isBadRequest()) @@ -522,7 +520,7 @@ void testAddUpdateContentWithLinksAttributeThenRemoveIt() throws Exception { .andExpect(jsonPath("$.payload[0].attributes[0].value.urlDest", is("https://myurl.com"))) .andExpect(jsonPath("$.payload[0].attributes[0].value.rel", is("rel"))) .andExpect(jsonPath("$.payload[0].attributes[0].value.target", is("_blank"))) - .andExpect(jsonPath("$.payload[0].attributes[0].value.hreflang", is("it"))) + .andExpect(jsonPath("$.payload[0].attributes[0].value.hrefLang", is("it"))) .andExpect(jsonPath("$.payload[0].attributes[1].code", is("link2"))) .andExpect(jsonPath("$.payload[0].attributes[1].value.pageDest", is("pagina_11"))) .andExpect(jsonPath("$.payload[0].attributes[2].code", is("link3"))) @@ -535,9 +533,13 @@ void testAddUpdateContentWithLinksAttributeThenRemoveIt() throws Exception { String bodyResult = result.andReturn().getResponse().getContentAsString(); newContentId = JsonPath.read(bodyResult, "$.payload[0].id"); Content newContent = this.contentManager.loadContent(newContentId, false); - Assertions.assertNotNull(newContent); - + LinkAttribute linkAttribute = (LinkAttribute) newContent.getAttribute("link1"); + Assertions.assertNotNull(linkAttribute); + Assertions.assertEquals("_blank", linkAttribute.getTarget()); + Assertions.assertEquals("rel", linkAttribute.getRel()); + Assertions.assertEquals("it", linkAttribute.getHrefLang()); + this.executeContentPut("1_PUT_valid_with_some_more_links.json", newContentId, accessToken, status().isOk()) .andExpect(jsonPath("$.payload.size()", is(1))) .andExpect(jsonPath("$.errors.size()", is(0))) @@ -1103,19 +1105,27 @@ void testAddContentWithAttachAndImageAttribute() throws Exception { String bodyResult = result.andReturn().getResponse().getContentAsString(); newContentId1 = JsonPath.read(bodyResult, "$.payload[0].id"); + newContentId2 = JsonPath.read(bodyResult, "$.payload[1].id"); + Content newContent1 = this.contentManager.loadContent(newContentId1, false); Assertions.assertNotNull(newContent1); - - newContentId2 = JsonPath.read(bodyResult, "$.payload[1].id"); + ImageAttribute imageAttribute1 = (ImageAttribute) newContent1.getAttribute("img1"); + Assertions.assertNotNull(imageAttribute1); + Assertions.assertEquals("alt img en1", imageAttribute1.getMetadataForLang(IResourceManager.ALT_METADATA_KEY, "en")); + Assertions.assertEquals("legend img en1", imageAttribute1.getMetadataForLang(IResourceManager.LEGEND_METADATA_KEY, "en")); + Assertions.assertNull(imageAttribute1.getMetadataForLang(IResourceManager.DESCRIPTION_METADATA_KEY, "it")); + Assertions.assertEquals("alt img it1", imageAttribute1.getMetadataForLang(IResourceManager.ALT_METADATA_KEY, "it")); + Assertions.assertEquals("legend img it1", imageAttribute1.getMetadataForLang(IResourceManager.LEGEND_METADATA_KEY, "it")); + Content newContent2 = this.contentManager.loadContent(newContentId2, false); Assertions.assertNotNull(newContent2); - + ImageAttribute imageAttribute2 = (ImageAttribute) newContent2.getAttribute("img1"); + Assertions.assertNotNull(imageAttribute2); + Assertions.assertEquals("legend img it2", imageAttribute2.getMetadataForLang(IResourceManager.LEGEND_METADATA_KEY, "it")); + Assertions.assertEquals("alt img it2", imageAttribute2.getMetadataForLang(IResourceManager.ALT_METADATA_KEY, "it")); + } finally { - if (null != imageResourceId) { - performDeleteResource(accessToken, "image", imageResourceId) - .andExpect(status().isOk()); - } if (null != newContentId1) { Content newContent = this.contentManager.loadContent(newContentId1, false); if (null != newContent) { @@ -1131,6 +1141,10 @@ void testAddContentWithAttachAndImageAttribute() throws Exception { if (null != this.contentManager.getEntityPrototype("IAT")) { ((IEntityTypesConfigurer) this.contentManager).removeEntityPrototype("IAT"); } + if (null != imageResourceId) { + performDeleteResource(accessToken, "image", imageResourceId) + .andExpect(status().isOk()); + } } } @@ -2649,9 +2663,31 @@ private ResultActions performDeleteResource(String accessToken, String type, Str delete(path) .header("Authorization", "Bearer " + accessToken)); } - + + @Test + void testGetAllPaginatedContents() throws Exception { + UserDetails user = new OAuth2TestUtils.UserBuilder("jack_bauer", "0x24").grantedToRoleAdmin().build(); + String accessToken = mockOAuthInterceptor(user); + this.executeTestGetAllPaginatedContents(accessToken, IContentService.STATUS_DRAFT, 25); + this.executeTestGetAllPaginatedContents(accessToken, IContentService.STATUS_ONLINE, 24); + } + + void executeTestGetAllPaginatedContents(String accessToken, String status, int expected) throws Exception { + ResultActions result = mockMvc + .perform(get("/plugins/cms/contents?page=1&pageSize=100") + .param("sort", IContentManager.CONTENT_CREATION_DATE_FILTER_KEY) + .param("direction", FieldSearchFilter.DESC_ORDER) + .param("status", status) + .header("Authorization", "Bearer " + accessToken)); + String bodyResult = result.andReturn().getResponse().getContentAsString(); + Integer payloadSize = JsonPath.read(bodyResult, "$.payload.size()"); + Assertions.assertEquals(expected, payloadSize.intValue()); + result.andExpect(status().isOk()); + result.andExpect(jsonPath("$.metaData.totalItems", is(payloadSize))); + } + @Test - void testGetContentsPaginated() throws Exception { + void testGetEvnContentsPaginated() throws Exception { UserDetails user = new OAuth2TestUtils.UserBuilder("jack_bauer", "0x24").grantedToRoleAdmin().build(); String accessToken = mockOAuthInterceptor(user); ResultActions result = mockMvc @@ -2662,9 +2698,7 @@ void testGetContentsPaginated() throws Exception { .param("filter[0].operator", "eq") .param("filter[0].value", "EVN") .header("Authorization", "Bearer " + accessToken)); - result - .andDo(resultPrint()) - .andExpect(status().isOk()) + result.andExpect(status().isOk()) .andExpect(jsonPath("$.payload.size()", is(2))) .andExpect(jsonPath("$.metaData.page", is(1))) .andExpect(jsonPath("$.metaData.pageSize", is(2))) @@ -5029,4 +5063,45 @@ accessToken, status().isOk()) : } } + @Test + void testGetImageAttributeWithMetadata() throws Exception { + String newContentId = null; + String accessToken = this.createAccessToken(); + try { + Content content = this.contentManager.loadContent("ART1", true); + content.setId(null); + ImageAttribute picture = (ImageAttribute) content.getAttribute("Foto"); + picture.setMetadata(IResourceManager.ALT_METADATA_KEY, "it", "Alt ita Value"); + picture.setMetadata(IResourceManager.LEGEND_METADATA_KEY, "it", "Legend Ita Value"); + newContentId = this.contentManager.addContent(content); + this.contentManager.insertOnLineContent(content); + Content addedContent = this.contentManager.loadContent(newContentId, true); + ImageAttribute addedPicture = (ImageAttribute) addedContent.getAttribute("Foto"); + Assertions.assertEquals("Alt ita Value", addedPicture.getResourceAltForLang("it")); + Assertions.assertEquals("Legend Ita Value", addedPicture.getResourceLegendForLang("it")); + ResultActions result = mockMvc + .perform(get("/plugins/cms/contents/{code}", newContentId) + .param("status", IContentService.STATUS_DRAFT) + .header("Authorization", "Bearer " + accessToken)); + String bodyResult = result.andReturn().getResponse().getContentAsString(); + result.andExpect(jsonPath("$.payload.size()", is(20))) + .andExpect(jsonPath("$.errors.size()", is(0))) + .andExpect(jsonPath("$.metaData.size()", is(0))) + .andExpect(jsonPath("$.payload.id", is(newContentId))); + int attributeSize = JsonPath.read(bodyResult, "$.payload.attributes.size()"); + Assertions.assertEquals(7, attributeSize); + for (int i = 0; i < attributeSize; i++) { + String attributeName = JsonPath.read(bodyResult, "$.payload.attributes[" + i + "].code"); + if (attributeName.equals("Foto")) { + result.andExpect(jsonPath("$.payload.attributes[" + i + "].values.it.metadata.legend", is("Legend Ita Value"))); + result.andExpect(jsonPath("$.payload.attributes[" + i + "].values.it.metadata.alt", is("Alt ita Value"))); + } + } + } finally { + Content onlineContent = this.contentManager.loadContent(newContentId, false); + this.contentManager.removeOnLineContent(onlineContent); + this.contentManager.deleteContent(newContentId); + } + } + } diff --git a/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/web/content/ContentControllerTest.java b/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/web/content/ContentControllerTest.java index b11ccad1c9..3ee44c4b95 100644 --- a/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/web/content/ContentControllerTest.java +++ b/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/web/content/ContentControllerTest.java @@ -13,6 +13,8 @@ */ package org.entando.entando.plugins.jacms.web.content; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -30,6 +32,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; @@ -38,6 +42,7 @@ import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.validation.BindingResult; +import org.springframework.validation.Errors; @ExtendWith(MockitoExtension.class) class ContentControllerTest extends AbstractControllerTest { @@ -93,7 +98,6 @@ void testAddContent() throws Exception { ResultActions result = this.performPostContent(mockJson, user); result.andExpect(status().isOk()); } - @Test void testUpdateContent() throws Exception { UserDetails user = this.createUser(true); @@ -110,6 +114,27 @@ void testUpdateContent() throws Exception { result.andExpect(status().isOk()); } + @Test + void shouldReturn422OnInvalidTypeCode() throws Exception { + UserDetails user = this.createUser(true); + + doAnswer(invocation -> { + Errors errors = invocation.getArgument(1); + errors.reject("5", "entity.typeCode.invalid"); + return null; + }).when(contentValidator).validateTypeCode(any(), any(Errors.class)); + + String mockJson = "[{\n" + + " \"id\": \"ART123\",\n" + + " \"typeCode\": \"XXX\",\n" + + " \"attributes\": [\n" + + " {\"code\": \"code1\", \"value\": \"value1\"},\n" + + " {\"code\": \"code2\", \"value\": \"value2\"}\n" + + " ]}]"; + ResultActions result = this.performPostContent(mockJson, user); + result.andExpect(status().isUnprocessableEntity()); + } + private ResultActions performGetContent(String code, String modelId, boolean online, String langCode, UserDetails user) throws Exception { String accessToken = mockOAuthInterceptor(user); diff --git a/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/web/resource/ResourcesControllerIntegrationTest.java b/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/web/resource/ResourcesControllerIntegrationTest.java index bfd272cb33..a31b5d20e5 100644 --- a/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/web/resource/ResourcesControllerIntegrationTest.java +++ b/cms-plugin/src/test/java/org/entando/entando/plugins/jacms/web/resource/ResourcesControllerIntegrationTest.java @@ -55,6 +55,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.agiletec.aps.system.services.category.Category; +import com.agiletec.plugins.jacms.aps.system.services.resource.IResourceManager; import org.junit.jupiter.api.Test; class ResourcesControllerIntegrationTest extends AbstractControllerIntegrationTest { @@ -71,6 +72,9 @@ class ResourcesControllerIntegrationTest extends AbstractControllerIntegrationTe @Autowired private ResourcesService resourcesService; + @Autowired + private IResourceManager resourceManager; + private static final ObjectMapper MAPPER = new ObjectMapper(); private DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH.mm.ss"); @@ -1096,7 +1100,6 @@ void testCreateFileResourceWithInvalidMimeType() throws Exception { .andExpect(jsonPath("$.errors.size()", is(1))) .andExpect(jsonPath("$.errors[0].code", is("4"))) .andExpect(jsonPath("$.errors[0].message", is("File type not allowed"))); - performGetResources(user, "file", null) .andDo(resultPrint()) .andExpect(status().isOk()); @@ -1107,10 +1110,8 @@ void testCreateCloneDeleteFileResource() throws Exception { UserDetails user = createAccessToken(); String createdId = null; String clonedId = null; - try { ResultActions result = performCreateResource(user, "file", "free", Arrays.stream(new String[]{"resCat1", "resCat2"}).collect(Collectors.toList()), "application/pdf") - .andDo(resultPrint()) .andExpect(status().isOk()) .andExpect(jsonPath("$.payload.id", Matchers.anything())) .andExpect(jsonPath("$.payload.categories.size()", is(2))) @@ -1120,11 +1121,9 @@ void testCreateCloneDeleteFileResource() throws Exception { .andExpect(jsonPath("$.payload.description", is("file_test.jpeg"))) .andExpect(jsonPath("$.payload.size", is("2 Kb"))) .andExpect(jsonPath("$.payload.path", startsWith("/Entando/resources/cms/documents/file_test"))); - createdId = JsonPath.read(result.andReturn().getResponse().getContentAsString(), "$.payload.id"); - + ResultActions result2 = performCloneResource(user, createdId) - .andDo(resultPrint()) .andExpect(status().isOk()) .andExpect(jsonPath("$.payload.id", not(createdId))) .andExpect(jsonPath("$.payload.categories.size()", is(2))) @@ -1134,9 +1133,7 @@ void testCreateCloneDeleteFileResource() throws Exception { .andExpect(jsonPath("$.payload.description", is("file_test.jpeg"))) .andExpect(jsonPath("$.payload.size", is("2 Kb"))) .andExpect(jsonPath("$.payload.path", startsWith("/Entando/resources/cms/documents/file_test"))); - clonedId = JsonPath.read(result2.andReturn().getResponse().getContentAsString(), "$.payload.id"); - } finally { if (clonedId != null) { performDeleteResource(user, "file", clonedId) @@ -1148,8 +1145,9 @@ void testCreateCloneDeleteFileResource() throws Exception { .andDo(resultPrint()) .andExpect(status().isOk()); } - performGetResources(user, "file", null) - .andDo(resultPrint()) + Assertions.assertNull(this.resourceManager.loadResource(clonedId)); + Assertions.assertNull(this.resourceManager.loadResource(createdId)); + this.performGetResources(user, "file", null) .andExpect(status().isOk()) .andExpect(jsonPath("$.payload.size()", is(2))); } @@ -1599,7 +1597,7 @@ void testCreateDeleteImageResourceWithPath() throws Exception { .andExpect(jsonPath("$.payload.folderPath", is(folderPath))) .andExpect(jsonPath("$.payload.versions.size()", is(4))) .andExpect(jsonPath("$.payload.versions[0].size", is("2 Kb"))) - .andExpect(jsonPath("$.payload.versions[0].path", startsWith("/Entando/resources/cms/images/image_test"))); + .andExpect(jsonPath("$.payload.versions[0].path", startsWith("/Entando/resources/cms/images/abc/image_test"))); createdId = JsonPath.read(result.andReturn().getResponse().getContentAsString(), "$.payload.id"); @@ -1644,7 +1642,7 @@ void testCreateEditDeleteImageResourceWithPath() throws Exception { .andExpect(jsonPath("$.payload.folderPath", is("abc"))) .andExpect(jsonPath("$.payload.versions.size()", is(4))) .andExpect(jsonPath("$.payload.versions[0].size", is("2 Kb"))) - .andExpect(jsonPath("$.payload.versions[0].path", startsWith("/Entando/resources/cms/images/image_test"))); + .andExpect(jsonPath("$.payload.versions[0].path", startsWith("/Entando/resources/cms/images/abc/image_test"))); createdId = JsonPath.read(result.andReturn().getResponse().getContentAsString(), "$.payload.id"); @@ -1661,7 +1659,7 @@ void testCreateEditDeleteImageResourceWithPath() throws Exception { .andExpect(jsonPath("$.payload.folderPath", is("abcd"))) .andExpect(jsonPath("$.payload.versions.size()", is(4))) .andExpect(jsonPath("$.payload.versions[0].size", is("2 Kb"))) - .andExpect(jsonPath("$.payload.versions[0].path", startsWith("/Entando/resources/cms/images/image_test"))); + .andExpect(jsonPath("$.payload.versions[0].path", startsWith("/Entando/resources/cms/images/abcd/image_test"))); } finally { @@ -1705,7 +1703,7 @@ void testCreateCloneDeleteImageResourceWithPath() throws Exception { .andExpect(jsonPath("$.payload.folderPath", is(folderPath))) .andExpect(jsonPath("$.payload.versions.size()", is(4))) .andExpect(jsonPath("$.payload.versions[0].size", is("2 Kb"))) - .andExpect(jsonPath("$.payload.versions[0].path", startsWith("/Entando/resources/cms/images/image_test"))); + .andExpect(jsonPath("$.payload.versions[0].path", startsWith("/Entando/resources/cms/images/folderPath123/image_test"))); createdId = JsonPath.read(result.andReturn().getResponse().getContentAsString(), "$.payload.id"); @@ -1722,7 +1720,7 @@ void testCreateCloneDeleteImageResourceWithPath() throws Exception { .andExpect(jsonPath("$.payload.folderPath", is(folderPath))) .andExpect(jsonPath("$.payload.versions.size()", is(4))) .andExpect(jsonPath("$.payload.versions[0].size", is("2 Kb"))) - .andExpect(jsonPath("$.payload.versions[0].path", startsWith("/Entando/resources/cms/images/image_test"))); + .andExpect(jsonPath("$.payload.versions[0].path", startsWith("/Entando/resources/cms/images/folderPath123/image_test"))); clonedId = JsonPath.read(result.andReturn().getResponse().getContentAsString(), "$.payload.id"); @@ -1777,7 +1775,7 @@ void testSubfoldersOnListAssetsFolderPath() throws Exception { String createdId5 = null; try { - + String type = "image"; String group = "free"; String folderPath = null; @@ -1798,9 +1796,9 @@ void testSubfoldersOnListAssetsFolderPath() throws Exception { .andExpect(jsonPath("$.payload.versions.size()", is(4))) .andExpect(jsonPath("$.payload.versions[0].size", is("2 Kb"))) .andExpect(jsonPath("$.payload.versions[0].path", startsWith("/Entando/resources/cms/images/image_test"))); - + createdId = JsonPath.read(result.andReturn().getResponse().getContentAsString(), "$.payload.id"); - + performGetResourcesFolder(user, folderPath) .andDo(resultPrint()) .andExpect(status().isOk()) @@ -1809,7 +1807,7 @@ void testSubfoldersOnListAssetsFolderPath() throws Exception { .andExpect(jsonPath("$.metaData.subfolders.size()", is(0))); folderPath = "abc"; - + result = performCreateResource(user, type, group, categories, folderPath, mimeType) .andDo(resultPrint()) .andExpect(status().isOk()) @@ -1823,7 +1821,7 @@ void testSubfoldersOnListAssetsFolderPath() throws Exception { .andExpect(jsonPath("$.payload.folderPath", is(folderPath))) .andExpect(jsonPath("$.payload.versions.size()", is(4))) .andExpect(jsonPath("$.payload.versions[0].size", is("2 Kb"))) - .andExpect(jsonPath("$.payload.versions[0].path", startsWith("/Entando/resources/cms/images/image_test"))); + .andExpect(jsonPath("$.payload.versions[0].path", startsWith("/Entando/resources/cms/images/abc/image_test"))); createdId2 = JsonPath.read(result.andReturn().getResponse().getContentAsString(), "$.payload.id"); @@ -1857,7 +1855,7 @@ void testSubfoldersOnListAssetsFolderPath() throws Exception { .andExpect(jsonPath("$.payload.description", is("file_test.jpeg"))) .andExpect(jsonPath("$.payload.owner", is("jack_bauer"))) .andExpect(jsonPath("$.payload.folderPath", is(folderPath))) - .andExpect(jsonPath("$.payload.path", startsWith("/Entando/resources/cms/documents/file_test"))); + .andExpect(jsonPath("$.payload.path", startsWith("/Entando/resources/cms/documents/abc/def/file_test"))); createdId3 = JsonPath.read(result.andReturn().getResponse().getContentAsString(), "$.payload.id"); @@ -1897,7 +1895,7 @@ void testSubfoldersOnListAssetsFolderPath() throws Exception { .andExpect(jsonPath("$.payload.description", is("file_test.jpeg"))) .andExpect(jsonPath("$.payload.owner", is("jack_bauer"))) .andExpect(jsonPath("$.payload.folderPath", is(folderPath))) - .andExpect(jsonPath("$.payload.path", startsWith("/Entando/resources/cms/documents/file_test"))); + .andExpect(jsonPath("$.payload.path", startsWith("/Entando/resources/cms/documents/abc/ghi/file_test"))); createdId4 = JsonPath.read(result.andReturn().getResponse().getContentAsString(), "$.payload.id"); @@ -1938,7 +1936,7 @@ void testSubfoldersOnListAssetsFolderPath() throws Exception { .andExpect(jsonPath("$.payload.description", is("file_test.jpeg"))) .andExpect(jsonPath("$.payload.owner", is("jack_bauer"))) .andExpect(jsonPath("$.payload.folderPath", is(folderPath))) - .andExpect(jsonPath("$.payload.path", startsWith("/Entando/resources/cms/documents/file_test"))); + .andExpect(jsonPath("$.payload.path", startsWith("/Entando/resources/cms/documents/abc/def/ghi/file_test"))); createdId5 = JsonPath.read(result.andReturn().getResponse().getContentAsString(), "$.payload.id"); @@ -1973,7 +1971,7 @@ void testSubfoldersOnListAssetsFolderPath() throws Exception { .andExpect(jsonPath("$.metaData.folderPath", is("abc/def"))) .andExpect(jsonPath("$.metaData.subfolders.size()", is(1))) .andExpect(jsonPath("$.metaData.subfolders[0]", is("abc/def/ghi"))); - + } finally { if (createdId5 != null) { diff --git a/contentscheduler-plugin/pom.xml b/contentscheduler-plugin/pom.xml index 647c48cae2..ea81e3d2cb 100644 --- a/contentscheduler-plugin/pom.xml +++ b/contentscheduler-plugin/pom.xml @@ -4,7 +4,7 @@ org.entando app-engine - 7.3.0 + 7.3.1 entando-plugin-jpcontentscheduler org.entando.entando.plugins diff --git a/engine/pom.xml b/engine/pom.xml index eb7b506457..cf0ec487bb 100644 --- a/engine/pom.xml +++ b/engine/pom.xml @@ -4,7 +4,7 @@ org.entando app-engine - 7.3.0 + 7.3.1 org.entando.entando entando-engine @@ -500,6 +500,10 @@ org.junit.jupiter junit-jupiter-api + + uk.org.webcompere + system-stubs-jupiter + org.mockito mockito-core diff --git a/engine/src/main/java/com/agiletec/aps/system/SystemConstants.java b/engine/src/main/java/com/agiletec/aps/system/SystemConstants.java index 9f02747047..5d070030ba 100644 --- a/engine/src/main/java/com/agiletec/aps/system/SystemConstants.java +++ b/engine/src/main/java/com/agiletec/aps/system/SystemConstants.java @@ -13,12 +13,9 @@ */ package com.agiletec.aps.system; -import com.agiletec.aps.system.services.page.IPageManager; -import com.agiletec.aps.system.services.user.IUserManager; import java.util.Arrays; import java.util.Collections; import java.util.List; -import org.entando.entando.aps.system.services.guifragment.IGuiFragmentManager; /** * Interfaccia con le principali costanti di sistema. @@ -299,11 +296,6 @@ private SystemConstants(){} public static final String USER_PROFILE_ATTRIBUTE_DISABLING_CODE_ON_EDIT = "userprofile:onEdit"; - /** - * The name of the role for attribute attribute that contains the profile picture file name - */ - public static final String USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE = "userprofile:profilepicture"; - public static final String ENTANDO_THREAD_NAME_PREFIX = "EntandoThread_"; public static final String API_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; diff --git a/engine/src/main/java/com/agiletec/aps/system/services/category/CategoryUtilizer.java b/engine/src/main/java/com/agiletec/aps/system/services/category/CategoryUtilizer.java index 77bb00ec17..318f1edc19 100644 --- a/engine/src/main/java/com/agiletec/aps/system/services/category/CategoryUtilizer.java +++ b/engine/src/main/java/com/agiletec/aps/system/services/category/CategoryUtilizer.java @@ -23,7 +23,7 @@ * * @author E.Santoboni */ -public interface CategoryUtilizer { +public interface CategoryUtilizer { /** * Restituisce l'identificativo del servizio utilizzatore. @@ -41,7 +41,7 @@ public interface CategoryUtilizer { * dal codice specificato. * @throws EntException In caso di errore. */ - public List getCategoryUtilizers(String categoryCode) throws EntException; + public List getCategoryUtilizers(String categoryCode) throws EntException; /** * Reload the category references. @@ -64,6 +64,6 @@ public interface CategoryUtilizer { * @return * @throws EntException In case of error */ - public List getCategoryUtilizersForReloadReferences(String categoryCode) throws EntException; + public List getCategoryUtilizersForReloadReferences(String categoryCode) throws EntException; } diff --git a/engine/src/main/java/com/agiletec/aps/system/services/lang/LangManager.java b/engine/src/main/java/com/agiletec/aps/system/services/lang/LangManager.java index c166b84a7d..74cac9b3a6 100644 --- a/engine/src/main/java/com/agiletec/aps/system/services/lang/LangManager.java +++ b/engine/src/main/java/com/agiletec/aps/system/services/lang/LangManager.java @@ -57,7 +57,8 @@ public void init() throws Exception { @Override public void initTenantAware() throws Exception { String xmlConfig = this.getConfigManager().getConfigItem(SystemConstants.CONFIG_ITEM_LANGS); - this.getCacheWrapper().initCache(xmlConfig); + List assignableLanguages = getAssignableLangs(); + this.getCacheWrapper().initCache(xmlConfig, assignableLanguages); } /** diff --git a/engine/src/main/java/com/agiletec/aps/system/services/lang/cache/ILangManagerCacheWrapper.java b/engine/src/main/java/com/agiletec/aps/system/services/lang/cache/ILangManagerCacheWrapper.java index 3f3ae93471..8766e297ae 100644 --- a/engine/src/main/java/com/agiletec/aps/system/services/lang/cache/ILangManagerCacheWrapper.java +++ b/engine/src/main/java/com/agiletec/aps/system/services/lang/cache/ILangManagerCacheWrapper.java @@ -13,9 +13,9 @@ */ package com.agiletec.aps.system.services.lang.cache; -import org.entando.entando.ent.exception.EntException; import com.agiletec.aps.system.services.lang.Lang; import java.util.List; +import org.entando.entando.ent.exception.EntException; /** * @author E.Santoboni @@ -28,7 +28,7 @@ public interface ILangManagerCacheWrapper { public static final String LANG_CODES_CACHE_NAME = "LangManager_codes"; public static final String LANG_DEFAULT_CACHE_NAME = "LangManager_default"; - public void initCache(String xmlConfig) throws EntException; + void initCache(String xmlConfig, List assignableLanguages) throws EntException; public List getLangs(); diff --git a/engine/src/main/java/com/agiletec/aps/system/services/lang/cache/LangManagerCacheWrapper.java b/engine/src/main/java/com/agiletec/aps/system/services/lang/cache/LangManagerCacheWrapper.java index 4e6eefb3b8..b151e69357 100644 --- a/engine/src/main/java/com/agiletec/aps/system/services/lang/cache/LangManagerCacheWrapper.java +++ b/engine/src/main/java/com/agiletec/aps/system/services/lang/cache/LangManagerCacheWrapper.java @@ -16,6 +16,8 @@ import java.util.ArrayList; import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; import org.entando.entando.ent.util.EntLogging.EntLogger; import org.entando.entando.ent.util.EntLogging.EntLogFactory; import org.springframework.cache.Cache; @@ -35,13 +37,17 @@ public class LangManagerCacheWrapper extends AbstractGenericCacheWrapper i private static final EntLogger logger = EntLogFactory.getSanitizedLogger(LangManagerCacheWrapper.class); @Override - public void initCache(String xmlConfig) throws EntException { + public void initCache(String xmlConfig, List assignableLanguages) throws EntException { try { Cache cache = this.getCache(); LangDOM langDom = new LangDOM(xmlConfig); Map langMap = new HashMap<>(); List systemLangs = langDom.getLangs(); + // Builds a map of assignable languages in order to speed up the search by language code + Map assignableLangMap = computeAssignableLangMap(assignableLanguages); for (Lang lang : systemLangs) { + // replace custom description with the official one from ISO 639-1 + replaceDescription(assignableLangMap, lang); if (lang.isDefault()) { cache.put(LANG_DEFAULT_CACHE_NAME, lang); } @@ -112,4 +118,14 @@ protected String getCacheName() { return LANG_MANAGER_CACHE_NAME; } + private static Map computeAssignableLangMap(List assignableLanguages) { + return assignableLanguages.stream() + .collect(Collectors.toMap(Lang::getCode, Function.identity())); + } + + private static void replaceDescription(Map assignableLangMap, Lang lang) { + if (assignableLangMap.containsKey(lang.getCode())) { + lang.setDescr(assignableLangMap.get(lang.getCode()).getDescr()); + } + } } diff --git a/engine/src/main/java/org/entando/entando/aps/system/init/DatabaseManager.java b/engine/src/main/java/org/entando/entando/aps/system/init/DatabaseManager.java index ed6e1af35e..af843252d8 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/init/DatabaseManager.java +++ b/engine/src/main/java/org/entando/entando/aps/system/init/DatabaseManager.java @@ -70,6 +70,7 @@ import org.entando.entando.ent.util.EntLogging.EntLogFactory; import org.entando.entando.ent.util.EntLogging.EntLogger; import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.web.context.ServletContextAware; @@ -96,6 +97,9 @@ public class DatabaseManager extends AbstractInitializerManager private int lockFallbackMinutes; private ServletContext servletContext; + + @Autowired + private ITenantManager tenantManager; public void init() { logger.debug("DatabaseManager ready"); @@ -568,7 +572,7 @@ public boolean dropAndRestoreBackup(String subFolderName) throws EntException { return false; } //TODO future improvement - execute 'lifeline' backup - this.getDatabaseRestorer().dropAndRestoreBackup(subFolderName); + this.getDatabaseRestorer().dropAndRestoreBackup(subFolderName, this.tenantManager); ApsWebApplicationUtils.executeSystemRefresh(this.getServletContext()); return true; } catch (Throwable t) { @@ -586,7 +590,7 @@ private boolean restoreBackup(String subFolderName) throws EntException { logger.error("backup not available - subfolder '{}'", subFolderName); return false; } - this.getDatabaseRestorer().restoreBackup(subFolderName); + this.getDatabaseRestorer().restoreBackup(subFolderName, this.tenantManager); return true; } catch (Throwable t) { logger.error("Error while restoring local backup", t); @@ -594,10 +598,16 @@ private boolean restoreBackup(String subFolderName) throws EntException { } } - private String[] extractBeanNames(Class beanClass) { + private String[] extractBeanNames(Class beanClass) { ListableBeanFactory factory = (ListableBeanFactory) this.getBeanFactory(); return factory.getBeanNamesForType(beanClass); } + + + + + + @Override public InputStream getTableDump(String tableName, String dataSourceName, String subFolderName) throws EntException { diff --git a/engine/src/main/java/org/entando/entando/aps/system/init/DatabaseRestorer.java b/engine/src/main/java/org/entando/entando/aps/system/init/DatabaseRestorer.java index 5d127bb04e..94963851dd 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/init/DatabaseRestorer.java +++ b/engine/src/main/java/org/entando/entando/aps/system/init/DatabaseRestorer.java @@ -13,6 +13,7 @@ */ package org.entando.entando.aps.system.init; +import com.agiletec.aps.util.ApsTenantApplicationUtils; import org.entando.entando.ent.exception.EntException; import com.agiletec.aps.util.FileTextReader; @@ -26,6 +27,7 @@ import org.entando.entando.aps.system.init.model.Component; import org.entando.entando.aps.system.init.util.QueryExtractor; import org.entando.entando.aps.system.init.util.TableDataUtils; +import org.entando.entando.aps.system.services.tenants.ITenantManager; import org.entando.entando.ent.util.EntLogging.EntLogger; import org.entando.entando.ent.util.EntLogging.EntLogFactory; @@ -50,22 +52,24 @@ protected void initOracleSchema(DataSource dataSource) throws Throwable { } } - protected void dropAndRestoreBackup(String backupSubFolder) throws EntException { + protected void dropAndRestoreBackup(String backupSubFolder, + ITenantManager tenantManager) throws EntException { try { List components = this.getComponents(); int size = components.size(); for (int i = 0; i < components.size(); i++) { Component componentConfiguration = components.get(size - i - 1); - this.dropTables(componentConfiguration.getTableNames()); + this.dropTables(componentConfiguration.getTableNames(), tenantManager); } - this.restoreBackup(backupSubFolder); + this.restoreBackup(backupSubFolder, tenantManager); } catch (Throwable t) { _logger.error("Error while restoring backup: {}", backupSubFolder, t); throw new EntException("Error while restoring backup", t); } } - private void dropTables(Map> tableMapping) throws EntException { + private void dropTables(Map> tableMapping, + ITenantManager tenantManager) throws EntException { if (null == tableMapping) { return; } @@ -77,7 +81,7 @@ private void dropTables(Map> tableMapping) throws EntExcept if (null == tableNames || tableNames.isEmpty()) { continue; } - DataSource dataSource = (DataSource) this.getBeanFactory().getBean(dataSourceName); + DataSource dataSource = this.fetchDataSource(dataSourceName, tenantManager); int size = tableNames.size(); for (int j = 0; j < tableNames.size(); j++) { String tableName = tableNames.get(size - j - 1); @@ -91,49 +95,54 @@ private void dropTables(Map> tableMapping) throws EntExcept } } - protected void restoreBackup(String backupSubFolder) throws EntException { + protected void restoreBackup(String backupSubFolder, + ITenantManager tenantManager) throws EntException { try { List components = this.getComponents(); for (int i = 0; i < components.size(); i++) { Component componentConfiguration = components.get(i); - this.restoreLocalDump(componentConfiguration.getTableNames(), backupSubFolder); + this.restoreLocalDump(componentConfiguration.getTableNames(), backupSubFolder, tenantManager); } } catch (Throwable t) { _logger.error("Error while restoring local backup", t); throw new EntException("Error while restoring local backup", t); } } - - private void restoreLocalDump(Map> tableMapping, String backupSubFolder) throws EntException { + + private void restoreLocalDump(Map> tableMapping, + String backupSubFolder, ITenantManager tenantManager) throws EntException { if (null == tableMapping) { return; } try { - StringBuilder folder = new StringBuilder(this.getLocalBackupsFolder()) - .append(backupSubFolder).append(File.separator); + String folder = this.getLocalBackupsFolder() + backupSubFolder + File.separator; String[] dataSourceNames = this.extractBeanNames(DataSource.class); - for (int i = 0; i < dataSourceNames.length; i++) { - String dataSourceName = dataSourceNames[i]; - List tableNames = tableMapping.get(dataSourceName); - if (null == tableNames || tableNames.isEmpty()) { - continue; - } - DataSource dataSource = (DataSource) this.getBeanFactory().getBean(dataSourceName); - this.initOracleSchema(dataSource); - for (int j = 0; j < tableNames.size(); j++) { - String tableName = tableNames.get(j); - String fileName = folder.toString() + dataSourceName + File.separator + tableName + ".sql"; - InputStream is = this.getStorageManager().getStream(fileName, true); - if (null != is) { - this.restoreTableData(is, dataSource); - } - } - } + for (String dataSourceName : dataSourceNames) { + List tableNames = tableMapping.get(dataSourceName); + if (null == tableNames || tableNames.isEmpty()) { + continue; + } + DataSource dataSource = this.fetchDataSource(dataSourceName, tenantManager); + this.initOracleSchema(dataSource); + for (int j = 0; j < tableNames.size(); j++) { + String tableName = tableNames.get(j); + String fileName = folder + dataSourceName + File.separator + tableName + ".sql"; + InputStream is = this.getStorageManager().getStream(fileName, true); + if (null != is) { + this.restoreTableData(is, dataSource); + } + } + } } catch (Throwable t) { _logger.error("Error while restoring local dump", t); throw new RuntimeException("Error while restoring local dump", t); } } + + private DataSource fetchDataSource(String dataSourceName, ITenantManager tenantManager) { + return ApsTenantApplicationUtils.getTenant().map(tenantManager::getDatasource) + .orElse((DataSource) this.getBeanFactory().getBean(dataSourceName)); + } private void restoreTableData(InputStream is, DataSource dataSource) { try { diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/cache/CacheInfoManager.java b/engine/src/main/java/org/entando/entando/aps/system/services/cache/CacheInfoManager.java index 4d61f984ef..a58a1e9a6c 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/cache/CacheInfoManager.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/cache/CacheInfoManager.java @@ -137,70 +137,58 @@ public void putInCache(String targetCache, String key, Object obj) { Cache cache = this.getCache(targetCache); cache.put(key, obj); } + @Override public void putInCache(String targetCache, String key, Object obj, String[] groups) { - Cache cache = this.getCache(targetCache); - cache.put(key, obj); - this.accessOnGroupMapping(targetCache, 1, groups, key); + this.putInCache(targetCache, key, obj); + this.accessOnGroupMapping(targetCache, true, groups, key); } @Override public void putInGroup(String targetCache, String key, String[] groups) { - this.accessOnGroupMapping(targetCache, 1, groups, key); + this.accessOnGroupMapping(targetCache, true, groups, key); } @Override public void flushGroup(String targetCache, String group) { String[] groups = {group}; - this.accessOnGroupMapping(targetCache, -1, groups, null); + this.accessOnGroupMapping(targetCache, false, groups, null); } - - protected synchronized void accessOnGroupMapping(String targetCache, int operationId, String[] groups, String key) { + + protected synchronized void accessOnGroupMapping(String targetCache, boolean addInGroup, String[] groups, String key) { Cache cache = this.getCache(CACHE_INFO_MANAGER_CACHE_NAME); - Map> objectsByGroup = this.get(cache, GROUP_CACHE_NAME_PREFIX + targetCache, Map.class); - if (objectsByGroup != null) { - objectsByGroup = new HashMap<>(objectsByGroup); + if (groups == null) { + return; } - boolean updateMapInCache = false; - if (operationId > 0) { - //add - if (null == objectsByGroup) { - objectsByGroup = new HashMap<>(); - } - for (String group : groups) { - List objectKeys = objectsByGroup.get(group); - if (null == objectKeys) { - objectKeys = new ArrayList<>(); - objectsByGroup.put(group, objectKeys); + for (String group : groups) { + String groupKey = GROUP_CACHE_NAME_PREFIX + targetCache + "__GR__" + group; + List keysByGroup = this.get(cache, groupKey, List.class); + if (addInGroup) { + if (null == keysByGroup) { + keysByGroup = new ArrayList<>(); } - if (!objectKeys.contains(key)) { - objectKeys.add(key); - updateMapInCache = true; + boolean updateListInCache = false; + if (!keysByGroup.contains(key)) { + keysByGroup.add(key); + updateListInCache = true; } - } - } else { - //remove - if (null == objectsByGroup) { - return; - } - for (String group : groups) { - List objectKeys = objectsByGroup.get(group); - if (null != objectKeys) { - for (String extractedKey : objectKeys) { - this.flushEntry(targetCache, extractedKey); - } - objectsByGroup.remove(group); - updateMapInCache = true; + if (updateListInCache) { + cache.put(groupKey, keysByGroup); + } + } else { + if (null == keysByGroup) { + continue; + } + for (String extractedKey : keysByGroup) { + this.flushEntry(targetCache, extractedKey); } + cache.evict(groupKey); } } - if (updateMapInCache) { - cache.put(GROUP_CACHE_NAME_PREFIX + targetCache, objectsByGroup); - } } - + protected Collection getCaches() { - Collection caches = new ArrayList(); + Collection caches = new ArrayList<>(); Iterator iter = this.getSpringCacheManager().getCacheNames().iterator(); while (iter.hasNext()) { String cacheName = iter.next(); diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/database/DatabaseService.java b/engine/src/main/java/org/entando/entando/aps/system/services/database/DatabaseService.java index 57c797c24a..3c52cce826 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/database/DatabaseService.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/database/DatabaseService.java @@ -38,6 +38,11 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.List; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import org.entando.entando.web.common.exceptions.ValidationGenericException; +import org.springframework.validation.BindException; /** * @author E.Santoboni @@ -45,9 +50,15 @@ public class DatabaseService implements IDatabaseService { private final EntLogger logger = EntLogFactory.getSanitizedLogger(this.getClass()); + + private static final String REPORT_CODE_FIELD_NAME = "reportCode"; + @Getter(AccessLevel.PROTECTED)@Setter private IDatabaseManager databaseManager; + @Getter(AccessLevel.PROTECTED)@Setter private IComponentManager componentManager; + @Getter(AccessLevel.PROTECTED)@Setter + private boolean restoreEnabled; @Override public int getStatus() { @@ -82,7 +93,7 @@ public DumpReportDto getDumpReportDto(String reportCode) { DataSourceDumpReport report = this.getDatabaseManager().getBackupReport(reportCode); if (null == report) { logger.warn("no dump found with code {}", reportCode); - throw new ResourceNotFoundException(DatabaseValidator.ERRCODE_NO_DUMP_FOUND, "reportCode", reportCode); + throw new ResourceNotFoundException(DatabaseValidator.ERRCODE_NO_DUMP_FOUND, REPORT_CODE_FIELD_NAME, reportCode); } dtos = new DumpReportDto(report, this.getComponentManager()); } catch (ResourceNotFoundException r) { @@ -121,17 +132,22 @@ public void startDatabaseBackup() { throw new RestServerError("error starting backup", t); } } - + @Override public void startDatabaseRestore(String reportCode) { try { + if (!this.isRestoreEnabled()) { + BindException bindException = new BindException(this, REPORT_CODE_FIELD_NAME); + bindException.reject(DatabaseValidator.ERRCODE_RESTORE_NO_ACTIVE, new String[]{}, "database.restore.disabled"); + throw new ValidationGenericException(bindException); + } DataSourceDumpReport report = this.getDatabaseManager().getBackupReport(reportCode); if (null == report) { logger.warn("no dump found with code {}", reportCode); - throw new ResourceNotFoundException(DatabaseValidator.ERRCODE_NO_DUMP_FOUND, "reportCode", reportCode); + throw new ResourceNotFoundException(DatabaseValidator.ERRCODE_NO_DUMP_FOUND, REPORT_CODE_FIELD_NAME, reportCode); } this.getDatabaseManager().dropAndRestoreBackup(reportCode); - } catch (ResourceNotFoundException r) { + } catch (ValidationGenericException | ResourceNotFoundException r) { throw r; } catch (Throwable t) { logger.error("error starting restore", t); @@ -173,20 +189,4 @@ public byte[] getTableDump(String reportCode, String dataSource, String tableNam return bytes; } - public IDatabaseManager getDatabaseManager() { - return databaseManager; - } - - public void setDatabaseManager(IDatabaseManager databaseManager) { - this.databaseManager = databaseManager; - } - - public IComponentManager getComponentManager() { - return componentManager; - } - - public void setComponentManager(IComponentManager componentManager) { - this.componentManager = componentManager; - } - } diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/entity/model/EntityAttributeDto.java b/engine/src/main/java/org/entando/entando/aps/system/services/entity/model/EntityAttributeDto.java index 4290bd2777..95c8ba2c0c 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/entity/model/EntityAttributeDto.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/entity/model/EntityAttributeDto.java @@ -17,19 +17,17 @@ import com.agiletec.aps.system.common.entity.model.attribute.*; import com.agiletec.aps.util.DateConverter; import com.fasterxml.jackson.annotation.JsonProperty; -import org.entando.entando.ent.util.EntLogging.EntLogFactory; -import org.entando.entando.ent.util.EntLogging.EntLogger; import org.springframework.validation.BindingResult; import java.math.BigDecimal; import java.util.*; import java.util.stream.Collectors; +import org.entando.entando.aps.system.common.entity.model.attribute.EnumeratorMapAttribute; /** * @author E.Santoboni */ public class EntityAttributeDto { - private final EntLogger logger = EntLogFactory.getSanitizedLogger(getClass()); private String code; @@ -57,6 +55,9 @@ public EntityAttributeDto(AttributeInterface src) { if (src.isSimple()) { if ((value instanceof String) || (value instanceof Number)) { this.setValue(value.toString()); + if (EnumeratorMapAttribute.class.isAssignableFrom(src.getClass())) { + this.setValues(Map.of("mapKey", value, "mapValue", ((EnumeratorMapAttribute) src).getMapValue())); + } } else if (value instanceof Boolean) { this.setValue(value); } else if (value instanceof Date) { diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/page/PageService.java b/engine/src/main/java/org/entando/entando/aps/system/services/page/PageService.java index 29afe8e446..7d02dd0bf1 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/page/PageService.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/page/PageService.java @@ -368,11 +368,13 @@ public PageDto updatePageStatus(String pageCode, String status) { try { IPage newPage = null; if (status.equals(STATUS_ONLINE)) { - IPage publicParent = this.getPageManager().getOnlinePage(currentPage.getParentCode()); - if (null == publicParent) { - bindingResult.reject(PageValidator.ERRCODE_PAGE_WITH_NO_PUBLIC_PARENT, - new String[]{pageCode, currentPage.getParentCode()}, "page.status.parent.unpublished"); - throw new ValidationGenericException(bindingResult); + if (!currentPage.isRoot()) { + IPage publicParent = this.getPageManager().getOnlinePage(currentPage.getParentCode()); + if (null == publicParent) { + bindingResult.reject(PageValidator.ERRCODE_PAGE_WITH_NO_PUBLIC_PARENT, + new String[]{pageCode, currentPage.getParentCode()}, "page.status.parent.unpublished"); + throw new ValidationGenericException(bindingResult); + } } this.getPageManager().setPageOnline(pageCode); newPage = this.getPageManager().getOnlinePage(pageCode); diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/page/serializer/WidgetConfigPropertiesSerializer.java b/engine/src/main/java/org/entando/entando/aps/system/services/page/serializer/WidgetConfigPropertiesSerializer.java index f7bd3c88dc..ca093bf2f4 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/page/serializer/WidgetConfigPropertiesSerializer.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/page/serializer/WidgetConfigPropertiesSerializer.java @@ -13,6 +13,7 @@ import java.util.Map.Entry; import java.util.Optional; import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.entando.entando.ent.util.EntLogging.EntLogger; import org.entando.entando.ent.util.EntLogging.EntLogFactory; @@ -136,36 +137,30 @@ private List> extractProperties(String regex, String value) List split = Arrays.stream(Optional.ofNullable(value) .orElse("").split(regex)) .collect(Collectors.toList()); - List> properties = new ArrayList<>(); for (String strProperty : split) { Map property = extractProperty(strProperty); - if (!property.entrySet().isEmpty()) { properties.add(property); } } - return properties; } private Map extractProperty(String value) { Map property = new HashMap<>(); - - for (String f : value.trim().split(";|,")) { - if (f == null || f.trim().isEmpty()) { + for (String f : value.trim().split("[;,]")) { + if (StringUtils.isBlank(f)) { continue; } - - String[] keyValue = f.split("=|:"); + String[] keyValue = f.trim().split("[=:]", 2); if (keyValue.length != 2) { logger.warn("Invalid filter format: {}", f); - continue; + } else { + property.put(keyValue[0].trim(), keyValue[1].trim()); } - - property.put(keyValue[0].trim(), keyValue[1].trim()); } - return property; } + } diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/storage/IStorageManager.java b/engine/src/main/java/org/entando/entando/aps/system/services/storage/IStorageManager.java index 9319131660..1d560cfed3 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/storage/IStorageManager.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/storage/IStorageManager.java @@ -18,7 +18,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.Serializable; -import java.util.function.BiFunction; /** * @author E.Santoboni @@ -41,6 +40,9 @@ public interface IStorageManager extends Serializable { public boolean exists(String subPath, boolean isProtectedResource) throws EntException; + public boolean move(String subPathSource, boolean isProtectedResourceSource, + String subPathDest, boolean isProtectedResourceDest) throws EntException; + public BasicFileAttributeView getAttributes(String subPath, boolean isProtectedResource) throws EntException; public String[] list(String subPath, boolean isProtectedResource) throws EntException; diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/storage/LocalStorageManager.java b/engine/src/main/java/org/entando/entando/aps/system/services/storage/LocalStorageManager.java index c64b407368..7f1f2b9153 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/storage/LocalStorageManager.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/storage/LocalStorageManager.java @@ -17,8 +17,6 @@ import org.apache.commons.lang3.CharEncoding; import org.entando.entando.ent.exception.EntException; import org.entando.entando.ent.exception.EntRuntimeException; -import org.entando.entando.ent.util.EntLogging.EntLogFactory; -import org.entando.entando.ent.util.EntLogging.EntLogger; import java.io.*; import java.util.Arrays; @@ -52,7 +50,7 @@ public class LocalStorageManager implements IStorageManager, InitializingBean { private String protectedBaseURL; private String allowedEditExtensions; - + @Override public void afterPropertiesSet() throws Exception { logger.info("** Enabled Local Storage Manager **"); } @@ -207,6 +205,29 @@ protected File getFile(String subPath, boolean isProtectedResource) { return new File(fullPath); } + @Override + public boolean move(String subPathSource, boolean isProtectedResourceSource, + String subPathDest, boolean isProtectedResourceDest) throws EntException { + File file = this.getFile(subPathSource, isProtectedResourceSource); + if (!file.exists()) { + logger.error("Source File does not exists - path '{}' protected '{}'", + subPathSource, isProtectedResourceSource); + return false; + } + String fullDestPath = this.createFullPath(subPathDest, isProtectedResourceDest); + File fileDest = new File(fullDestPath); + if (fileDest.exists()) { + logger.error("Destination already exists - path '{}' protected '{}'", + subPathDest, isProtectedResourceDest); + return false; + } + File dirDest = fileDest.getParentFile(); + if (!dirDest.exists()) { + dirDest.mkdirs(); + } + return file.renameTo(fileDest); + } + @Override public String getResourceUrl(String subPath, boolean isProtectedResource) { subPath = (null == subPath) ? "" : subPath; diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/IUserPreferencesDAO.java b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/IUserPreferencesDAO.java index ac96cca259..2fb201cefd 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/IUserPreferencesDAO.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/IUserPreferencesDAO.java @@ -54,4 +54,5 @@ public interface IUserPreferencesDAO { * @throws EntException the ent exception */ void deleteUserPreferences(String username) throws EntException; + } diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/IUserPreferencesManager.java b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/IUserPreferencesManager.java index 8ad6fd4bca..24ed516b2b 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/IUserPreferencesManager.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/IUserPreferencesManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved. + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -54,4 +54,18 @@ public interface IUserPreferencesManager { * @throws EntException the ent exception */ void deleteUserPreferences(String username) throws EntException; -} \ No newline at end of file + + boolean isUserGravatarEnabled(String username) throws EntException; + + void updateUserGravatarPreference(String username, boolean enabled) throws EntException; + + public default UserPreferences createDefaultUserPreferences(String username) { + UserPreferences userPreferences = new UserPreferences(); + userPreferences.setUsername(username); + userPreferences.setWizard(true); + userPreferences.setTranslationWarning(true); + userPreferences.setLoadOnPageSelect(true); + return userPreferences; + } + +} diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferences.java b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferences.java index a17819e890..b4cdacbab4 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferences.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferences.java @@ -21,7 +21,7 @@ @XmlRootElement(name = "userPreferences") @XmlType(propOrder = {"username", "wizard", "loadOnPageSelect", "translationWarning", "defaultPageOwnerGroup", "defaultPageJoinGroups", "defaultContentOwnerGroup", "defaultContentJoinGroups", "defaultWidgetOwnerGroup", - "defaultWidgetJoinGroups", "disableContentMenu"}) + "defaultWidgetJoinGroups", "disableContentMenu", "gravatar"}) public class UserPreferences implements Serializable { private String username; @@ -35,6 +35,7 @@ public class UserPreferences implements Serializable { private String defaultWidgetOwnerGroup; private String defaultWidgetJoinGroups; private boolean disableContentMenu; + private boolean gravatar; @XmlElement(name = "username", required = true) public String getUsername() { @@ -135,6 +136,15 @@ public void setDisableContentMenu(boolean disableContentMenu) { this.disableContentMenu = disableContentMenu; } + @XmlElement(name = "gravatar") + public boolean isGravatar() { + return gravatar; + } + + public void setGravatar(boolean gravatar) { + this.gravatar = gravatar; + } + @Override public String toString() { return "UserPreferences{" + @@ -148,7 +158,8 @@ public String toString() { ", defaultContentJoinGroups='" + defaultContentJoinGroups + '\'' + ", defaultWidgetOwnerGroup='" + defaultWidgetOwnerGroup + '\'' + ", defaultWidgetJoinGroups='" + defaultWidgetJoinGroups + '\'' + - ", disableContentMenu='" + disableContentMenu + + ", disableContentMenu='" + disableContentMenu + '\'' + + ", gravatar='" + gravatar + '}'; } } diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesDAO.java b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesDAO.java index dbab2edaab..a340dc67d8 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesDAO.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesDAO.java @@ -29,19 +29,19 @@ public class UserPreferencesDAO extends AbstractDAO implements IUserPreferencesD private static final String LOAD_USER_PREFERENCES = "SELECT wizard, loadonpageselect, translationwarning, defaultpageownergroup, defaultpagejoingroups, " + "defaultcontentownergroup, defaultcontentjoingroups, defaultwidgetownergroup, " - + "defaultwidgetjoingroups, disableContentMenu FROM userpreferences WHERE username = ? "; + + "defaultwidgetjoingroups, disableContentMenu, gravatar FROM userpreferences WHERE username = ? "; private static final String ADD_USER_PREFERENCES = "INSERT INTO userpreferences (username, wizard, loadonpageselect, translationwarning, " + "defaultpageownergroup, defaultpagejoingroups, defaultcontentownergroup, " - + "defaultcontentjoingroups, defaultwidgetownergroup, defaultwidgetjoingroups, disableContentMenu) " - + "VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? )"; + + "defaultcontentjoingroups, defaultwidgetownergroup, defaultwidgetjoingroups, disableContentMenu, gravatar) " + + "VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? )"; private static final String UPDATE_USER_PREFERENCES = "UPDATE userpreferences SET wizard = ? , loadonpageselect = ? , translationwarning = ? , " + "defaultpageownergroup = ? , defaultpagejoingroups = ? , defaultcontentownergroup = ? , " + "defaultcontentjoingroups = ? , defaultwidgetownergroup = ?, defaultwidgetjoingroups = ? , " - + "disableContentMenu = ? WHERE username = ? "; + + "disableContentMenu = ? , gravatar = ? WHERE username = ? "; private static final String DELETE_USER_PREFERENCES = "DELETE FROM userpreferences WHERE username = ? "; @@ -70,6 +70,7 @@ public UserPreferences loadUserPreferences(String username) throws EntException response.setDefaultWidgetOwnerGroup(res.getString(8)); response.setDefaultWidgetJoinGroups(res.getString(9)); response.setDisableContentMenu(1 == res.getInt(10)); + response.setGravatar(1 == res.getInt(11)); } } catch (SQLException e) { _logger.error("Error loading user preferences for user {}", username, e); @@ -99,6 +100,7 @@ public void addUserPreferences(UserPreferences userPreferences) throws EntExcept stat.setString(9, userPreferences.getDefaultWidgetOwnerGroup()); stat.setString(10, userPreferences.getDefaultWidgetJoinGroups()); stat.setInt(11, userPreferences.getDisableContentMenu() ? 1 : 0); + stat.setInt(12, userPreferences.isGravatar() ? 1 : 0); stat.executeUpdate(); conn.commit(); } catch (SQLException e) { @@ -128,7 +130,8 @@ public void updateUserPreferences(UserPreferences userPreferences) throws EntExc stat.setString(8, userPreferences.getDefaultWidgetOwnerGroup()); stat.setString(9, userPreferences.getDefaultWidgetJoinGroups()); stat.setInt(10, userPreferences.getDisableContentMenu() ? 1 : 0); - stat.setString(11, userPreferences.getUsername()); + stat.setInt(11, userPreferences.isGravatar() ? 1 : 0); + stat.setString(12, userPreferences.getUsername()); stat.executeUpdate(); conn.commit(); } catch (SQLException e) { @@ -158,4 +161,5 @@ public void deleteUserPreferences(String username) throws EntException { closeDaoResources(null, stat, conn); } } + } diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesManager.java b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesManager.java index 6a932060f6..9a695d48eb 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesManager.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved. + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -13,8 +13,13 @@ */ package org.entando.entando.aps.system.services.userpreferences; +import java.util.Optional; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import org.entando.entando.ent.exception.EntException; +@Slf4j +@Setter public class UserPreferencesManager implements IUserPreferencesManager { private UserPreferencesDAO userPreferencesDAO; @@ -35,11 +40,26 @@ public void updateUserPreferences(UserPreferences userPreferences) throws EntExc } @Override - public void deleteUserPreferences(String username) throws EntException { - userPreferencesDAO.deleteUserPreferences(username); + public boolean isUserGravatarEnabled(String username) throws EntException { + return Optional.ofNullable(this.getUserPreferences(username)).map(p -> p.isGravatar()).orElse(Boolean.FALSE); } - public void setUserPreferencesDAO(UserPreferencesDAO userPreferencesDAO) { - this.userPreferencesDAO = userPreferencesDAO; + @Override + public void updateUserGravatarPreference(String username, boolean enabled) throws EntException { + UserPreferences userPreferences = this.getUserPreferences(username); + if (null != userPreferences) { + userPreferences.setGravatar(enabled); + this.updateUserPreferences(userPreferences); + } else { + userPreferences = this.createDefaultUserPreferences(username); + userPreferences.setGravatar(enabled); + this.addUserPreferences(userPreferences); + } + } + + @Override + public void deleteUserPreferences(String username) throws EntException { + userPreferencesDAO.deleteUserPreferences(username); } + } diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesService.java b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesService.java index d43ca0ea65..0d407fa1ee 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesService.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userpreferences/UserPreferencesService.java @@ -95,6 +95,9 @@ public UserPreferencesDto updateUserPreferences(String username, UserPreferences if (request.getDisableContentMenu() != null) { userPreferences.setDisableContentMenu(request.getDisableContentMenu()); } + if (request.getGravatar() != null) { + userPreferences.setGravatar(request.getGravatar()); + } userPreferencesManager.updateUserPreferences(userPreferences); return new UserPreferencesDto(userPreferencesManager.getUserPreferences(username)); } else { @@ -109,12 +112,8 @@ public UserPreferencesDto updateUserPreferences(String username, UserPreferences private void createNewDefaultUserPreferences(String username) { try { - UserPreferences userPreferences = new UserPreferences(); - userPreferences.setUsername(username); - userPreferences.setWizard(true); - userPreferences.setTranslationWarning(true); - userPreferences.setLoadOnPageSelect(true); - userPreferencesManager.addUserPreferences(userPreferences); + UserPreferences userPreferences = this.userPreferencesManager.createDefaultUserPreferences(username); + this.userPreferencesManager.addUserPreferences(userPreferences); } catch (EntException e) { logger.error("Error in creating new default userPreferences for {}", username, e); throw new RestServerError("Error creating new default userPreferences", e); diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/AvatarService.java b/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/AvatarService.java new file mode 100644 index 0000000000..c2c373fa98 --- /dev/null +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/AvatarService.java @@ -0,0 +1,159 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ +package org.entando.entando.aps.system.services.userprofile; + +import com.agiletec.aps.system.services.user.UserDetails; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.entando.entando.aps.system.exception.ResourceNotFoundException; +import org.entando.entando.aps.system.exception.RestServerError; +import org.entando.entando.aps.system.services.storage.IFileBrowserService; +import org.entando.entando.aps.system.services.storage.model.BasicFileAttributeViewDto; +import org.entando.entando.aps.system.services.userpreferences.IUserPreferencesManager; +import org.entando.entando.aps.system.services.userprofile.model.AvatarDto; +import org.entando.entando.ent.exception.EntException; +import org.entando.entando.web.entity.validator.EntityValidator; +import org.entando.entando.web.filebrowser.model.FileBrowserFileRequest; +import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; +import org.springframework.validation.BindingResult; + +@Slf4j +@RequiredArgsConstructor +public class AvatarService implements IAvatarService { + + private final IFileBrowserService fileBrowserService; + private final IUserPreferencesManager userPreferencesManager; + + // CONSTANTS + private static final String DEFAULT_AVATAR_PATH = "static/profile"; + + @Override + public AvatarDto getAvatarData(UserDetails userDetails) { + try { + boolean isGravatarEnabled = this.userPreferencesManager.isUserGravatarEnabled(userDetails.getUsername()); + if (isGravatarEnabled) { + return AvatarDto.builder().gravatar(true).build(); + } + String fileName = this.getAvatarFilenameByUsername(userDetails.getUsername()); + if (StringUtils.isEmpty(fileName)) { + throw new ResourceNotFoundException(EntityValidator.ERRCODE_ENTITY_DOES_NOT_EXIST, "image", + userDetails.getUsername()); + } + boolean protectedFolder = false; + String currentPath = Paths.get(DEFAULT_AVATAR_PATH, fileName).toString(); + // get file from volume or else throw exception + byte[] base64 = fileBrowserService.getFileStream(currentPath, protectedFolder); + // return an informative object + return AvatarDto.builder() + .filename(fileName) + .currentPath(currentPath) + .protectedFolder(protectedFolder) + .prevPath(DEFAULT_AVATAR_PATH) + .base64(base64) + .build(); + } catch (ResourceNotFoundException e) { + throw e; + } catch (Exception e) { + log.error("Error extracting avatar", e); + throw new RestServerError("Error extracting avatar", e); + } + } + + @Override + public String updateAvatar(ProfileAvatarRequest request, UserDetails userDetails, BindingResult bindingResult) { + try { + String username = userDetails.getUsername(); + // remove previous image if present + deletePrevUserAvatarFromFileSystemIfPresent(username); + this.userPreferencesManager.updateUserGravatarPreference(userDetails.getUsername(), request.isUseGravatar()); + if (request.isUseGravatar()) { + return null; + } + FileBrowserFileRequest fileBrowserFileRequest = addProfileImageToFileSystem(request, userDetails, bindingResult); + return fileBrowserFileRequest.getFilename(); + } catch (Exception e) { + log.error("Error updating avatar", e); + throw new RestServerError("Error updating avatar", e); + } + } + + @Override + public void deleteAvatar(UserDetails userDetails, BindingResult bindingResult) { + try { + String username = userDetails.getUsername(); + this.deletePrevUserAvatarFromFileSystemIfPresent(username); + this.userPreferencesManager.updateUserGravatarPreference(userDetails.getUsername(), false); + } catch (Exception e) { + log.error("Error deleting avatar", e); + throw new RestServerError("Error deleting avatar", e); + } + } + + @Override + public void deleteAvatar(String username) throws EntException { + this.deletePrevUserAvatarFromFileSystemIfPresent(username); + } + + //------------------------ Utility methods ------------------------------------// + + private FileBrowserFileRequest addProfileImageToFileSystem( + ProfileAvatarRequest request, UserDetails userDetails, BindingResult bindingResult) { + // prepare a FileBrowserFileRequest to use the api already available in the system + FileBrowserFileRequest fileBrowserFileRequest = convertToFileBrowserFileRequest(request, userDetails); + // add the file to the volume + fileBrowserService.addFile(fileBrowserFileRequest, bindingResult); + return fileBrowserFileRequest; + } + + private void deletePrevUserAvatarFromFileSystemIfPresent(String username) throws EntException { + String filename = this.getAvatarFilenameByUsername(username); + if (null == filename) { + return; + } + String profilePicturePath = Paths.get(DEFAULT_AVATAR_PATH, filename).toString(); + fileBrowserService.deleteFile(profilePicturePath, false); + } + + private String getAvatarFilenameByUsername(String username) throws EntException { + if (!fileBrowserService.exists(DEFAULT_AVATAR_PATH)) { + return null; + } + List fileAttributes = fileBrowserService.browseFolder(DEFAULT_AVATAR_PATH, Boolean.FALSE); + Optional fileAvatar = fileAttributes.stream().filter(bfa -> !bfa.getDirectory()) + .filter(bfa -> { + int lastDotIndex = bfa.getName().lastIndexOf('.'); + String name = (lastDotIndex > 0) ? bfa.getName().substring(0, lastDotIndex) : bfa.getName(); + return name.equalsIgnoreCase(username); + }).findAny().map(bfa -> bfa.getName()); + return fileAvatar.orElse(null); + } + + private static FileBrowserFileRequest convertToFileBrowserFileRequest(ProfileAvatarRequest request, + UserDetails userDetails) { + FileBrowserFileRequest fileBrowserFileRequest = new FileBrowserFileRequest(); + String imageExtension = FilenameUtils.getExtension(request.getFilename()); + String filename = String.format("%s.%s", userDetails.getUsername(), imageExtension); + fileBrowserFileRequest.setFilename(filename); + fileBrowserFileRequest.setPath(Paths.get(DEFAULT_AVATAR_PATH, filename).toString()); + fileBrowserFileRequest.setProtectedFolder(false); + fileBrowserFileRequest.setBase64(request.getBase64()); + return fileBrowserFileRequest; + } + +} diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/IAvatarService.java b/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/IAvatarService.java new file mode 100644 index 0000000000..f9467b8fc5 --- /dev/null +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/IAvatarService.java @@ -0,0 +1,20 @@ +package org.entando.entando.aps.system.services.userprofile; + +import com.agiletec.aps.system.services.user.UserDetails; +import org.entando.entando.aps.system.services.userprofile.model.AvatarDto; +import org.entando.entando.ent.exception.EntException; +import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; +import org.springframework.validation.BindingResult; + +public interface IAvatarService { + + AvatarDto getAvatarData(UserDetails userDetails); + + String updateAvatar(ProfileAvatarRequest request, UserDetails userDetails, + BindingResult bindingResult); + + void deleteAvatar(UserDetails userDetails, BindingResult bindingResult); + + void deleteAvatar(String username) throws EntException; + +} diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/UserProfileManagerAspect.java b/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/UserManagementAspect.java similarity index 75% rename from engine/src/main/java/org/entando/entando/aps/system/services/userprofile/UserProfileManagerAspect.java rename to engine/src/main/java/org/entando/entando/aps/system/services/userprofile/UserManagementAspect.java index 038c261e52..e9e24a83a0 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/UserProfileManagerAspect.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/UserManagementAspect.java @@ -20,6 +20,9 @@ import org.entando.entando.ent.util.EntLogging.EntLogFactory; import com.agiletec.aps.system.services.user.AbstractUser; import com.agiletec.aps.system.services.user.UserDetails; +import org.entando.entando.aps.system.services.userpreferences.IUserPreferencesManager; +import org.entando.entando.ent.exception.EntException; +import org.springframework.beans.factory.annotation.Autowired; /** * Implementation of ProfileManager Aspect. This class join a user with his @@ -28,11 +31,21 @@ * @author E.Santoboni */ @Aspect -public class UserProfileManagerAspect { +public class UserManagementAspect { - private static final EntLogger logger = EntLogFactory.getSanitizedLogger(UserProfileManagerAspect.class); + private static final EntLogger logger = EntLogFactory.getSanitizedLogger(UserManagementAspect.class); - private IUserProfileManager userProfileManager; + private final IUserProfileManager userProfileManager; + private final IAvatarService avatarService; + private final IUserPreferencesManager userPreferencesManager; + + @Autowired + public UserManagementAspect(IUserProfileManager userProfileManager, + IAvatarService avatarService, IUserPreferencesManager userPreferencesManager) { + this.userProfileManager = userProfileManager; + this.userPreferencesManager = userPreferencesManager; + this.avatarService = avatarService; + } @AfterReturning(pointcut = "execution(* com.agiletec.aps.system.services.user.IUserManager.getUser(..))", returning = "user") public void injectProfile(Object user) { @@ -80,7 +93,7 @@ public void updateProfile(Object user) { } @AfterReturning(pointcut = "execution(* com.agiletec.aps.system.services.user.IUserManager.removeUser(..)) && args(key)") - public void deleteProfile(Object key) { + public void deleteUserData(Object key) { String username = null; if (key instanceof String) { username = key.toString(); @@ -91,8 +104,18 @@ public void deleteProfile(Object key) { if (username != null) { try { this.getUserProfileManager().deleteProfile(username); - } catch (Throwable t) { - logger.error("Error deleting profile. user: {}", username, t); + } catch (EntException t) { + logger.error("Error deleting user profile. user: {}", username, t); + } + try { + this.avatarService.deleteAvatar(username); + } catch (EntException t) { + logger.error("Error deleting user avatar. user: {}", username, t); + } + try { + this.userPreferencesManager.deleteUserPreferences(username); + } catch (EntException t) { + logger.error("Error deleting user preverences. user: {}", username, t); } } } @@ -100,9 +123,5 @@ public void deleteProfile(Object key) { protected IUserProfileManager getUserProfileManager() { return userProfileManager; } - - public void setUserProfileManager(IUserProfileManager userProfileManager) { - this.userProfileManager = userProfileManager; - } } diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/attributeRoles.xml b/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/attributeRoles.xml index b0e9ee6029..a91d81b10a 100644 --- a/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/attributeRoles.xml +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/attributeRoles.xml @@ -23,10 +23,4 @@ Monotext,Email TEXT - - userprofile:profilepicture - The Attribute containing the profile picture file name - Monotext - TEXT - diff --git a/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/model/AvatarDto.java b/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/model/AvatarDto.java new file mode 100644 index 0000000000..148c60e131 --- /dev/null +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/model/AvatarDto.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ +package org.entando.entando.aps.system.services.userprofile.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AvatarDto { + + boolean protectedFolder; + String currentPath; + String filename; + byte[] base64; + String prevPath; + boolean gravatar; + +} diff --git a/engine/src/main/java/org/entando/entando/ent/exception/EntResourceNotFoundException.java b/engine/src/main/java/org/entando/entando/ent/exception/EntResourceNotFoundException.java index bf7d081e5c..da5e26891c 100644 --- a/engine/src/main/java/org/entando/entando/ent/exception/EntResourceNotFoundException.java +++ b/engine/src/main/java/org/entando/entando/ent/exception/EntResourceNotFoundException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved. + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -14,11 +14,16 @@ package org.entando.entando.ent.exception; /** - * Generic Entando Runtime Exception + * Generic Resource Not Found Exception */ public class EntResourceNotFoundException extends EntException { + public EntResourceNotFoundException(String message) { + super(message); + } + public EntResourceNotFoundException(String message, Throwable cause) { super(message, cause); } + } diff --git a/engine/src/main/java/org/entando/entando/web/common/interceptor/ActivityStreamInterceptor.java b/engine/src/main/java/org/entando/entando/web/common/interceptor/ActivityStreamInterceptor.java index 199ea79e87..1a1c96c17f 100644 --- a/engine/src/main/java/org/entando/entando/web/common/interceptor/ActivityStreamInterceptor.java +++ b/engine/src/main/java/org/entando/entando/web/common/interceptor/ActivityStreamInterceptor.java @@ -148,7 +148,7 @@ private String addParameters(String init, Map params) { } private String getCurrentUsername(HttpServletRequest request) { - UserDetails user = (UserDetails) request.getSession().getAttribute("user"); + UserDetails user = (UserDetails) request.getAttribute("user"); if (null != user) { return user.getUsername(); } diff --git a/engine/src/main/java/org/entando/entando/web/database/validator/DatabaseValidator.java b/engine/src/main/java/org/entando/entando/web/database/validator/DatabaseValidator.java index 3ae63ba37b..204a14ebca 100644 --- a/engine/src/main/java/org/entando/entando/web/database/validator/DatabaseValidator.java +++ b/engine/src/main/java/org/entando/entando/web/database/validator/DatabaseValidator.java @@ -30,6 +30,7 @@ public class DatabaseValidator extends AbstractPaginationValidator { public static final String ERRCODE_NO_DUMP_FOUND = "1"; public static final String ERRCODE_NO_TABLE_DUMP_FOUND = "1"; + public static final String ERRCODE_RESTORE_NO_ACTIVE = "2"; @Override public boolean supports(Class type) { diff --git a/engine/src/main/java/org/entando/entando/web/page/PageController.java b/engine/src/main/java/org/entando/entando/web/page/PageController.java index e34c71e78c..fd7f2c4d3e 100644 --- a/engine/src/main/java/org/entando/entando/web/page/PageController.java +++ b/engine/src/main/java/org/entando/entando/web/page/PageController.java @@ -347,13 +347,7 @@ public ResponseEntity> deletePage( if (bindingResult.hasErrors()) { throw new ValidationGenericException(bindingResult); } - //business validations - getPageValidator().validateOnlinePage(pageCode, bindingResult); - if (bindingResult.hasErrors()) { - throw new ValidationGenericException(bindingResult); - } - //business validations - getPageValidator().validateChildren(pageCode, bindingResult); + this.getPageValidator().validateDeletePageRequest(pageCode, bindingResult); if (bindingResult.hasErrors()) { throw new ValidationGenericException(bindingResult); } diff --git a/engine/src/main/java/org/entando/entando/web/page/validator/PageValidator.java b/engine/src/main/java/org/entando/entando/web/page/validator/PageValidator.java index 7a36a6274f..1f3d0d8cb3 100644 --- a/engine/src/main/java/org/entando/entando/web/page/validator/PageValidator.java +++ b/engine/src/main/java/org/entando/entando/web/page/validator/PageValidator.java @@ -48,6 +48,7 @@ public class PageValidator extends AbstractPaginationValidator { public static final String ERRCODE_PAGE_HAS_CHILDREN = "2"; public static final String ERRCODE_GROUP_MISMATCH = "2"; public static final String ERRCODE_INVALID_PARENT = "3"; + public static final String ERRCODE_PAGE_ROOT = "4"; public static final String ERRCODE_STATUS_PAGE_MISMATCH = "6"; public static final String ERRCODE_CHANGE_POSITION_INVALID_REQUEST = "7"; public static final String ERRCODE_REFERENCED_ONLINE_PAGE = "2"; @@ -132,6 +133,15 @@ public void validateChangePositionRequest(String pageCode, PagePositionRequest p errors.reject(ERRCODE_CHANGE_POSITION_INVALID_REQUEST, new String[]{pageCode}, "page.move.position.invalid"); } } + + public void validateDeletePageRequest(String pageCode, Errors errors) { + this.validateOnlinePage(pageCode, errors); + this.validateChildren(pageCode, errors); + IPage page = this.getDraftPage(pageCode); + if (null != page && page.isRoot()) { + errors.reject(ERRCODE_PAGE_ROOT, new String[]{pageCode}, "page.delete.root"); + } + } public void validateGroups(String pageCode, PagePositionRequest pageRequest, Errors errors) { IPage parent = getPage(pageRequest.getParentCode()); diff --git a/engine/src/main/java/org/entando/entando/web/userpreferences/model/UserPreferencesDto.java b/engine/src/main/java/org/entando/entando/web/userpreferences/model/UserPreferencesDto.java index 62108c488a..566af5833b 100644 --- a/engine/src/main/java/org/entando/entando/web/userpreferences/model/UserPreferencesDto.java +++ b/engine/src/main/java/org/entando/entando/web/userpreferences/model/UserPreferencesDto.java @@ -35,6 +35,7 @@ public class UserPreferencesDto { private String defaultWidgetOwnerGroup; private List defaultWidgetJoinGroups; private Boolean disableContentMenu; + private Boolean gravatar; public UserPreferencesDto(UserPreferences userPreferences) { wizard = userPreferences.isWizard(); diff --git a/engine/src/main/java/org/entando/entando/web/userpreferences/model/UserPreferencesRequest.java b/engine/src/main/java/org/entando/entando/web/userpreferences/model/UserPreferencesRequest.java index dfb80d5be8..8e07bc59ba 100644 --- a/engine/src/main/java/org/entando/entando/web/userpreferences/model/UserPreferencesRequest.java +++ b/engine/src/main/java/org/entando/entando/web/userpreferences/model/UserPreferencesRequest.java @@ -30,6 +30,7 @@ public class UserPreferencesRequest { private String defaultWidgetOwnerGroup; private List defaultWidgetJoinGroups; private Boolean disableContentMenu; + private Boolean gravatar; @Override public String toString() { @@ -42,8 +43,9 @@ public String toString() { ", defaultContentOwnerGroup='" + defaultContentOwnerGroup + '\'' + ", defaultContentJoinGroups=" + defaultContentJoinGroups + ", defaultWidgetOwnerGroup='" + defaultWidgetOwnerGroup + '\'' + - ", defaultWidgetJoinGroups=" + defaultWidgetJoinGroups + - ", disableContentMenu=" + disableContentMenu + + ", defaultWidgetJoinGroups=" + defaultWidgetJoinGroups + '\'' + + ", disableContentMenu=" + disableContentMenu + '\'' + + ", gravatar=" + gravatar + '}'; } } diff --git a/engine/src/main/java/org/entando/entando/web/userprofile/ProfileController.java b/engine/src/main/java/org/entando/entando/web/userprofile/ProfileController.java index 7a4e624e45..f44aaedf05 100644 --- a/engine/src/main/java/org/entando/entando/web/userprofile/ProfileController.java +++ b/engine/src/main/java/org/entando/entando/web/userprofile/ProfileController.java @@ -16,69 +16,74 @@ import com.agiletec.aps.system.services.role.Permission; import com.agiletec.aps.system.services.user.IUserManager; import com.agiletec.aps.system.services.user.UserDetails; -import com.fasterxml.jackson.core.JsonProcessingException; +import java.util.HashMap; +import java.util.Map; +import javax.validation.Valid; +import lombok.RequiredArgsConstructor; import org.entando.entando.aps.system.exception.ResourceNotFoundException; import org.entando.entando.aps.system.exception.RestServerError; import org.entando.entando.aps.system.services.entity.model.EntityDto; +import org.entando.entando.aps.system.services.userprofile.IAvatarService; import org.entando.entando.aps.system.services.userprofile.IUserProfileManager; import org.entando.entando.aps.system.services.userprofile.IUserProfileService; +import org.entando.entando.aps.system.services.userprofile.model.AvatarDto; import org.entando.entando.aps.system.services.userprofile.model.IUserProfile; import org.entando.entando.ent.exception.EntException; import org.entando.entando.ent.util.EntLogging.EntLogFactory; import org.entando.entando.ent.util.EntLogging.EntLogger; import org.entando.entando.web.common.annotation.RestAccessControl; import org.entando.entando.web.common.exceptions.ValidationGenericException; +import org.entando.entando.web.common.model.RestResponse; import org.entando.entando.web.common.model.SimpleRestResponse; import org.entando.entando.web.entity.validator.EntityValidator; +import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; +import org.entando.entando.web.userprofile.validator.ProfileAvatarValidator; import org.entando.entando.web.userprofile.validator.ProfileValidator; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.*; - -import javax.validation.Valid; +import org.springframework.validation.MapBindingResult; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestAttribute; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; /** * @author E.Santoboni */ @RestController +@RequiredArgsConstructor public class ProfileController { private final EntLogger logger = EntLogFactory.getSanitizedLogger(this.getClass()); - @Autowired - private IUserProfileService userProfileService; + private final IUserProfileService userProfileService; - @Autowired - private ProfileValidator profileValidator; + private final ProfileValidator profileValidator; - @Autowired - private IUserManager userManager; + private final ProfileAvatarValidator profileAvatarValidator; - @Autowired - private IUserProfileManager userProfileManager; + private final IUserManager userManager; - protected IUserProfileService getUserProfileService() { - return userProfileService; - } + private final IUserProfileManager userProfileManager; - public void setUserProfileService(IUserProfileService userProfileService) { - this.userProfileService = userProfileService; - } + private final IAvatarService avatarService; - public ProfileValidator getProfileValidator() { - return profileValidator; - } + public static final String PROTECTED_FOLDER = "protectedFolder"; - public void setProfileValidator(ProfileValidator profileValidator) { - this.profileValidator = profileValidator; - } + public static final String PREV_PATH = "prevPath"; @RestAccessControl(permission = {Permission.MANAGE_USER_PROFILES, Permission.MANAGE_USERS}) @RequestMapping(value = "/userProfiles/{username}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity> getUserProfile(@PathVariable String username) throws JsonProcessingException { + public ResponseEntity> getUserProfile(@PathVariable String username) { logger.debug("Requested profile -> {}", username); final EntityDto dto = getUserProfileEntityDto(username); logger.debug("Main Response -> {}", dto); @@ -95,7 +100,7 @@ public ResponseEntity> getMyUserProfile(@RequestAt private EntityDto getUserProfileEntityDto(final String username) { EntityDto dto; - if (!this.getProfileValidator().existProfile(username)) { + if (!profileValidator.existProfile(username)) { if (userExists(username)) { // if the user exists but the profile doesn't, creates an empty profile IUserProfile userProfile = createNewEmptyUserProfile(username); @@ -104,7 +109,7 @@ private EntityDto getUserProfileEntityDto(final String username) { throw new ResourceNotFoundException(EntityValidator.ERRCODE_ENTITY_DOES_NOT_EXIST, "Profile", username); } } else { - dto = this.getUserProfileService().getUserProfile(username); + dto = userProfileService.getUserProfile(username); } return dto; } @@ -131,16 +136,17 @@ private IUserProfile createNewEmptyUserProfile(String username) { @RestAccessControl(permission = Permission.MANAGE_USER_PROFILES) @RequestMapping(value = "/userProfiles", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity> addUserProfile(@Valid @RequestBody EntityDto bodyRequest, BindingResult bindingResult) { + public ResponseEntity> addUserProfile(@Valid @RequestBody EntityDto bodyRequest, + BindingResult bindingResult) { logger.debug("Add new user profile -> {}", bodyRequest); if (bindingResult.hasErrors()) { throw new ValidationGenericException(bindingResult); } - this.getProfileValidator().validate(bodyRequest, bindingResult); + profileValidator.validate(bodyRequest, bindingResult); if (bindingResult.hasErrors()) { throw new ValidationGenericException(bindingResult); } - EntityDto response = this.getUserProfileService().addUserProfile(bodyRequest, bindingResult); + EntityDto response = userProfileService.addUserProfile(bodyRequest, bindingResult); if (bindingResult.hasErrors()) { throw new ValidationGenericException(bindingResult); } @@ -155,8 +161,8 @@ public ResponseEntity> updateUserProfile(@PathVari if (bindingResult.hasErrors()) { throw new ValidationGenericException(bindingResult); } - this.getProfileValidator().validateBodyName(username, bodyRequest, bindingResult); - EntityDto response = this.getUserProfileService().updateUserProfile(bodyRequest, bindingResult); + profileValidator.validateBodyName(username, bodyRequest, bindingResult); + EntityDto response = userProfileService.updateUserProfile(bodyRequest, bindingResult); if (bindingResult.hasErrors()) { throw new ValidationGenericException(bindingResult); } @@ -166,17 +172,61 @@ public ResponseEntity> updateUserProfile(@PathVari @PutMapping(value = "/myUserProfile", produces = MediaType.APPLICATION_JSON_VALUE) @RestAccessControl(permission = Permission.ENTER_BACKEND) public ResponseEntity> updateMyUserProfile(@RequestAttribute("user") UserDetails user, - @Valid @RequestBody EntityDto bodyRequest, BindingResult bindingResult) { + /*@Valid*/ @RequestBody EntityDto bodyRequest, BindingResult bindingResult) { logger.debug("Update profile for the logged user {} -> {}", user.getUsername(), bodyRequest); - this.getProfileValidator().validateBodyName(user.getUsername(), bodyRequest, bindingResult); + profileValidator.validateBodyName(user.getUsername(), bodyRequest, bindingResult); + if (bindingResult.hasErrors()) { + throw new ValidationGenericException(bindingResult); + } + EntityDto response = userProfileService.updateUserProfile(bodyRequest, bindingResult); if (bindingResult.hasErrors()) { throw new ValidationGenericException(bindingResult); } - EntityDto response = this.getUserProfileService().updateUserProfile(bodyRequest, bindingResult); + return new ResponseEntity<>(new SimpleRestResponse<>(response), HttpStatus.OK); + } + + @GetMapping(path = "/userProfiles/avatar", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity, Map>> getAvatar( + @RequestAttribute("user") UserDetails userDetails) { + // request user profile picture + AvatarDto avatarData = avatarService.getAvatarData(userDetails); + // fill output + Map result = new HashMap<>(); + result.put(PROTECTED_FOLDER, avatarData.isProtectedFolder()); + result.put("isDirectory", false); + result.put("path", avatarData.getCurrentPath()); + result.put("filename", avatarData.getFilename()); + result.put("useGravatar", avatarData.isGravatar()); + result.put("base64", avatarData.getBase64()); + Map metadata = new HashMap<>(); + metadata.put(PREV_PATH, avatarData.getPrevPath()); + return new ResponseEntity<>(new RestResponse<>(result, metadata), HttpStatus.OK); + } + + @PostMapping(path = "/userProfiles/avatar", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity>> addAvatar( + @Validated @RequestBody ProfileAvatarRequest request, + @RequestAttribute("user") UserDetails user, + BindingResult bindingResult) { + // validate input dto to check for consistency of input + profileAvatarValidator.validate(request, user, bindingResult); if (bindingResult.hasErrors()) { throw new ValidationGenericException(bindingResult); } + String pictureFileName = avatarService.updateAvatar(request, user, bindingResult); + if (bindingResult.hasErrors()) { + throw new ValidationGenericException(bindingResult); + } + Map response = null != pictureFileName ? Map.of("filename", pictureFileName) : Map.of("useGravatar", true); return new ResponseEntity<>(new SimpleRestResponse<>(response), HttpStatus.OK); } + @DeleteMapping(path = "/userProfiles/avatar") + public ResponseEntity>> deleteAvatar(@RequestAttribute("user") UserDetails user) { + avatarService.deleteAvatar(user, new MapBindingResult(new HashMap<>(), "user")); + Map payload = new HashMap<>(); + payload.put("username", user.getUsername()); + return new ResponseEntity<>(new SimpleRestResponse<>(payload), HttpStatus.OK); + } + } diff --git a/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarRequest.java b/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarRequest.java new file mode 100644 index 0000000000..6487c041ea --- /dev/null +++ b/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarRequest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2018-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ +package org.entando.entando.web.userprofile.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class ProfileAvatarRequest { + + private String filename; + private byte[] base64; + private boolean useGravatar; + +} diff --git a/engine/src/main/java/org/entando/entando/web/userprofile/validator/ProfileAvatarValidator.java b/engine/src/main/java/org/entando/entando/web/userprofile/validator/ProfileAvatarValidator.java new file mode 100644 index 0000000000..4164ca1f37 --- /dev/null +++ b/engine/src/main/java/org/entando/entando/web/userprofile/validator/ProfileAvatarValidator.java @@ -0,0 +1,94 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ +package org.entando.entando.web.userprofile.validator; + +import com.agiletec.aps.system.SystemConstants; +import com.agiletec.aps.system.services.user.UserDetails; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Optional; +import javax.imageio.ImageIO; +import lombok.AllArgsConstructor; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.entando.entando.aps.system.services.userprofile.IUserProfileManager; +import org.entando.entando.ent.exception.EntException; +import org.entando.entando.ent.exception.EntRuntimeException; +import org.entando.entando.web.common.RestErrorCodes; +import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; + +@Component +@AllArgsConstructor +public class ProfileAvatarValidator implements Validator { + + public static final String ERRCODE_INVALID_FILE_NAME = "1"; + public static final String ERRCODE_INVALID_FILE_TYPE = "2"; + public static final String ERRCODE_MISSING_EMAIL_ATTRIBUTE = "3"; + + private IUserProfileManager userProfileManager; + + @Override + public boolean supports(@NonNull Class paramClass) { + return (ProfileAvatarRequest.class.equals(paramClass)); + } + + @Override + public void validate(@NonNull Object target, @NonNull Errors errors) { + ProfileAvatarRequest request = (ProfileAvatarRequest) target; + String filename = request.getFilename(); + if (StringUtils.isBlank(filename)) { + errors.rejectValue("filename", RestErrorCodes.NOT_BLANK, new String[]{}, + "avatar.filename.notBlank"); + } else if (StringUtils.isEmpty(FilenameUtils.getExtension(filename))) { + errors.rejectValue("filename", ERRCODE_INVALID_FILE_NAME, new String[]{filename}, + "fileBrowser.filename.invalidFilename"); + return; + } + byte[] base64 = request.getBase64(); + if (null == base64) { + errors.rejectValue("base64", RestErrorCodes.NOT_EMPTY, new String[]{}, + "fileBrowser.base64.notBlank"); + } else { + try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(request.getBase64())) { + if (ImageIO.read(byteArrayInputStream) == null) { + errors.rejectValue("base64", ERRCODE_INVALID_FILE_TYPE, "fileBrowser.file.invalidType"); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } + + public void validate(@NonNull Object target, UserDetails user, @NonNull Errors errors) { + ProfileAvatarRequest request = (ProfileAvatarRequest) target; + if (!request.isUseGravatar()) { + this.validate(target, errors); + return; + } + try { + Optional.ofNullable(this.userProfileManager.getProfile(user.getUsername())) + .map(up -> up.getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_MAIL)).ifPresentOrElse(up -> { + }, () -> errors.rejectValue("useGravatar", ERRCODE_MISSING_EMAIL_ATTRIBUTE, new String[]{}, + "avatar.emailAttribute.missing")); + } catch (EntException e) { + throw new EntRuntimeException("Error validating user avatar", e); + } + } + +} diff --git a/engine/src/main/resources/liquibase/changeSetPort.xml b/engine/src/main/resources/liquibase/changeSetPort.xml index 95140223cb..88cf7b8418 100644 --- a/engine/src/main/resources/liquibase/changeSetPort.xml +++ b/engine/src/main/resources/liquibase/changeSetPort.xml @@ -26,6 +26,7 @@ - - - \ No newline at end of file + + + + diff --git a/engine/src/main/resources/liquibase/port/20240112000000_user_preference_avatar.xml b/engine/src/main/resources/liquibase/port/20240112000000_user_preference_avatar.xml new file mode 100644 index 0000000000..58d41a0804 --- /dev/null +++ b/engine/src/main/resources/liquibase/port/20240112000000_user_preference_avatar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/engine/src/main/resources/liquibase/port/clob/production/sysconfig_3.xml b/engine/src/main/resources/liquibase/port/clob/production/sysconfig_3.xml index 8786c502a8..9e1d5a2425 100644 --- a/engine/src/main/resources/liquibase/port/clob/production/sysconfig_3.xml +++ b/engine/src/main/resources/liquibase/port/clob/production/sysconfig_3.xml @@ -1,28 +1,23 @@ - - - - - true - - - userprofile:fullname - - - - - true - - - userprofile:email - - - + + + + + true + - userprofile:profilepicture + userprofile:fullname - - + + + true + + + userprofile:email + + + + \ No newline at end of file diff --git a/engine/src/main/resources/liquibase/port/clob/test/sysconfig_3.xml b/engine/src/main/resources/liquibase/port/clob/test/sysconfig_3.xml index a0c63d7a78..574c0f381a 100644 --- a/engine/src/main/resources/liquibase/port/clob/test/sysconfig_3.xml +++ b/engine/src/main/resources/liquibase/port/clob/test/sysconfig_3.xml @@ -1,171 +1,194 @@ - - - - - true - - - userprofile:fullname - - - - - true - - - - userprofile:email - - - - - - - - - - - - - true - - - userprofile:firstname - - - - - true - - - userprofile:surname - - - - - true - - - userprofile:email - - - + + + + + true + - userprofile:profilepicture + userprofile:fullname - - - - - - true - - - true - - - true - - - - - 25/11/2026 - - - - true + + + true + + + + userprofile:email + + + + + + + + + + + + + true + + + userprofile:firstname + + + + + true + + + userprofile:surname + + + + + true + + + userprofile:email + + + + + + + + + true + + + + + true + + + + + true + + + + + + 25/11/2026 + + + + + true + - - - true + + + + true + - - - true - - - true - - - true - - - true - - - - true - 15 - 30 - - - - - true - - - - true - 50 - 300 - - - - true - - jacms:title - - - - - true - 15 - 30 - - - - - true - - - true - - - true - - - - - - - 10/10/2030 - - - - - - - - - - #entity.getAttribute('Number').value)]]> - - - - - - - - - - - - - true - - - - + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + 15 + 30 + + + + + + true + + + + + true + 50 + 300 + + + + + true + + + jacms:title + + + + + true + 15 + 30 + + + + + + true + + + + + true + + + + + true + + + + + + + + 10/10/2030 + + + + + + + + + + #entity.getAttribute('Number').value)]]> + + + + + + + + + + + + + true + + + + \ No newline at end of file diff --git a/engine/src/main/resources/rest/messages.properties b/engine/src/main/resources/rest/messages.properties index 7eda70d3d0..4955556dca 100644 --- a/engine/src/main/resources/rest/messages.properties +++ b/engine/src/main/resources/rest/messages.properties @@ -74,6 +74,7 @@ page.update.group.invalid=The page group ''{0}'' of existing page does not match #page.update.patch.invalid.code=The provided JSON patch can not contain operations on the code property page.movement.parent.invalid.1=The page ''{0}'' can not be a parent of itself page.movement.parent.invalid.2=The page ''{0}'' can not be the parent of ''{1}'' because he is one of his child +page.delete.root=Root page can not be deleted page.delete.online=Online pages can not be deleted page.delete.children=Pages with children pages can not be deleted page.move.position.invalid=Invalid Request for page position change @@ -282,6 +283,7 @@ userSettings.MaxMonthsSinceLastAccess.invalid=''lastAccessPasswordExpirationMont #Database database.dump.table.notFound=No table dump found for backup ''{0}'', database ''{1}'', table ''{2}'' +database.restore.disabled=Db Restore function disabled #Content plugins.jacms.content.model.invalidLangCode=Invalid content template language code @@ -346,6 +348,7 @@ fileBrowser.filename.body.mismatch=The filename specified ''{0}'' does not match fileBrowser.filename.invalidFilename=The filename ''{0}'' is invalid fileBrowser.file.exists=The file ''{0}'' already exists - protected folder ''{1}'' fileBrowser.directory.exists=The directory ''{0}'' already exists - protected folder ''{1}'' +fileBrowser.file.invalidType=The file type is invalid #activityStream activityStreamCommentRequest.recordId.required=The recordId is required @@ -357,6 +360,7 @@ entity.id.mismatch=The entity id specified ''{0}'' does not match with the one entity.notExists=The entity ''{0}'' does not exist entity.id.notBlank=Code is required entity.typeCode.notBlank=TypeCode is required +entity.typeCode.invalid=TypeCode is invalid #API Consumer api.consumer.grantType.invalid=''{0}'' is not a valid grant type @@ -391,3 +395,7 @@ error.smtpServerConfig.sender.invalid=Invalid sender code # Component usage components.usage.field.missing=Requested occurrence for position ''{0}'' - Missing field ''{1}'' components.usage.type.invalid=Requested occurrence for position ''{0}'' - Invalid field ''{1}'' + +# Avatar +avatar.filename.notBlank=''fileName'' is required +avatar.emailAttribute.missing=Missing email attribute in current user diff --git a/engine/src/main/resources/spring/aps/managers/userprofileManagers.xml b/engine/src/main/resources/spring/aps/managers/userprofileManagers.xml index e8af70afca..14283afae3 100644 --- a/engine/src/main/resources/spring/aps/managers/userprofileManagers.xml +++ b/engine/src/main/resources/spring/aps/managers/userprofileManagers.xml @@ -38,9 +38,7 @@ - - - + diff --git a/engine/src/main/resources/spring/aps/servicesConfig.xml b/engine/src/main/resources/spring/aps/servicesConfig.xml index 65c9905104..4a0fe88a80 100644 --- a/engine/src/main/resources/spring/aps/servicesConfig.xml +++ b/engine/src/main/resources/spring/aps/servicesConfig.xml @@ -117,6 +117,8 @@ + + @@ -141,6 +143,7 @@ + ${db.restore.enabled} diff --git a/engine/src/test/java/com/agiletec/aps/BaseTestCase.java b/engine/src/test/java/com/agiletec/aps/BaseTestCase.java index 704d79c593..f57ca26815 100644 --- a/engine/src/test/java/com/agiletec/aps/BaseTestCase.java +++ b/engine/src/test/java/com/agiletec/aps/BaseTestCase.java @@ -146,7 +146,7 @@ protected static UserDetails getUser(String username, String password) throws Ex SystemConstants.AUTHENTICATION_PROVIDER_MANAGER); IUserManager userManager = (IUserManager) getService(SystemConstants.USER_MANAGER); UserDetails user = null; - if (username.equals(SystemConstants.GUEST_USER_NAME)) { + if (SystemConstants.GUEST_USER_NAME.equals(username)) { user = userManager.getGuestUser(); } else { user = provider.getUser(username, password); @@ -166,9 +166,13 @@ public static UserDetails getUser(String username) throws Exception { } public static void setUserOnSession(String username) throws Exception { - UserDetails currentUser = getUser(username); HttpSession session = request.getSession(); - session.setAttribute(SystemConstants.SESSIONPARAM_CURRENT_USER, currentUser); + UserDetails currentUser = getUser(username); + if (null != currentUser) { + session.setAttribute(SystemConstants.SESSIONPARAM_CURRENT_USER, currentUser); + } else { + session.removeAttribute(SystemConstants.SESSIONPARAM_CURRENT_USER); + } } public static RequestContext getRequestContext() { diff --git a/engine/src/test/java/com/agiletec/aps/system/services/lang/LangManagerIntegrationTest.java b/engine/src/test/java/com/agiletec/aps/system/services/lang/LangManagerIntegrationTest.java index 1dfa805e89..126ff1acf3 100644 --- a/engine/src/test/java/com/agiletec/aps/system/services/lang/LangManagerIntegrationTest.java +++ b/engine/src/test/java/com/agiletec/aps/system/services/lang/LangManagerIntegrationTest.java @@ -45,7 +45,7 @@ void testGetDefaultLang() throws EntException { String langCode = lang.getCode(); String langDescr = lang.getDescr(); assertEquals("it", langCode); - assertEquals("Italiano", langDescr); + assertEquals("Italian", langDescr); } @Test @@ -55,7 +55,7 @@ void testGetLangs() throws EntException { for (Lang lang : langs) { String code = lang.getCode(); if (code.equals("it")) { - assertEquals("Italiano", lang.getDescr()); + assertEquals("Italian", lang.getDescr()); } else if (code.equals("en")) { assertEquals("English", lang.getDescr()); } else { diff --git a/engine/src/test/java/com/agiletec/aps/system/services/lang/LangManagerTest.java b/engine/src/test/java/com/agiletec/aps/system/services/lang/LangManagerTest.java index 3829d4d91e..2c3be88ac0 100644 --- a/engine/src/test/java/com/agiletec/aps/system/services/lang/LangManagerTest.java +++ b/engine/src/test/java/com/agiletec/aps/system/services/lang/LangManagerTest.java @@ -20,13 +20,12 @@ import java.util.List; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; import org.mockito.junit.jupiter.MockitoExtension; /** @@ -83,7 +82,7 @@ public void addLang() throws Throwable { } @Test - public void updateLang() throws Throwable { + void updateLang() throws Throwable { Lang requiredLang = new Lang(); requiredLang.setCode("de"); requiredLang.setCode("German"); @@ -96,7 +95,7 @@ public void updateLang() throws Throwable { } @Test - public void updateLangNullLang() throws Throwable { + void updateLangNullLang() throws Throwable { Mockito.when(cacheWrapper.getLang("et")).thenReturn(null); this.langManager.updateLang("et", "Estonian lang"); Mockito.verify(cacheWrapper, Mockito.times(0)).updateLang(Mockito.any(Lang.class)); @@ -105,7 +104,7 @@ public void updateLangNullLang() throws Throwable { } @Test - public void removeLang() throws Throwable { + void removeLang() throws Throwable { Lang requiredLang = new Lang(); requiredLang.setCode("de"); requiredLang.setCode("German"); @@ -117,7 +116,7 @@ public void removeLang() throws Throwable { } @Test - public void removeLangNullLang() throws Throwable { + void removeLangNullLang() throws Throwable { Mockito.when(cacheWrapper.getLang("et")).thenReturn(null); this.langManager.removeLang("et"); Mockito.verify(cacheWrapper, Mockito.times(0)).removeLang(Mockito.any(Lang.class)); @@ -125,4 +124,10 @@ public void removeLangNullLang() throws Throwable { Mockito.verify(notifyManager, Mockito.times(0)).publishEvent(Mockito.any(LangsChangedEvent.class)); } + @Test + void initTenantAwareShouldLoadAssignableLanguages() throws Exception { + LangManager spy = Mockito.spy(langManager); + spy.initTenantAware(); + Mockito.verify(spy, Mockito.times(1)).getAssignableLangs(); + } } diff --git a/engine/src/test/java/com/agiletec/aps/system/services/lang/cache/LangManagerCacheWrapperTest.java b/engine/src/test/java/com/agiletec/aps/system/services/lang/cache/LangManagerCacheWrapperTest.java new file mode 100644 index 0000000000..0a53826a0f --- /dev/null +++ b/engine/src/test/java/com/agiletec/aps/system/services/lang/cache/LangManagerCacheWrapperTest.java @@ -0,0 +1,49 @@ +package com.agiletec.aps.system.services.lang.cache; + +import java.util.List; +import org.entando.entando.ent.exception.EntException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; + +class LangManagerCacheWrapperTest { + @Test + void shouldInitCacheRunWithoutException() throws EntException { + LangManagerCacheWrapper langManagerCacheWrapper = new LangManagerCacheWrapper(); + LangManagerCacheWrapper spy = Mockito.spy(langManagerCacheWrapper); + + String xmlConfig = "\n" + + "\n" + + "\t\n" + + "\t\tit\n" + + "\t\tItaliano\n" + + "\t\n" + + "\t\n" + + "\t\ten\n" + + "\t\tEnglish\n" + + "\t\ttrue\n" + + "\t\n" + + ""; + + CacheManager cacheManager = Mockito.mock(CacheManager.class); + spy.setSpringCacheManager(cacheManager); + Mockito.when(cacheManager.getCache(ArgumentMatchers.anyString())).thenReturn(Mockito.mock(Cache.class)); + + Assertions.assertDoesNotThrow(() -> spy.initCache(xmlConfig, List.of())); + } + + @Test + void shouldInitCacheThrowEntExceptionThrowsEntException() { + LangManagerCacheWrapper langManagerCacheWrapper = new LangManagerCacheWrapper(); + LangManagerCacheWrapper spy = Mockito.spy(langManagerCacheWrapper); + + CacheManager cacheManager = Mockito.mock(CacheManager.class); + spy.setSpringCacheManager(cacheManager); + Mockito.when(cacheManager.getCache(ArgumentMatchers.anyString())).thenReturn(Mockito.mock(Cache.class)); + + Assertions.assertThrows(EntException.class, () -> spy.initCache("", List.of())); + } +} diff --git a/engine/src/test/java/org/entando/entando/aps/system/init/DatabaseManagerTest.java b/engine/src/test/java/org/entando/entando/aps/system/init/DatabaseManagerTest.java index a53467a616..85f25de7b5 100644 --- a/engine/src/test/java/org/entando/entando/aps/system/init/DatabaseManagerTest.java +++ b/engine/src/test/java/org/entando/entando/aps/system/init/DatabaseManagerTest.java @@ -154,7 +154,7 @@ void testInstallDatabaseRestoreLastDump() throws Exception { dbFactory.when(DatabaseFactory::getInstance).thenReturn(Mockito.mock(DatabaseFactory.class)); databaseManager.installDatabase(null, DatabaseMigrationStrategy.AUTO, Optional.empty()); - Mockito.verify(databaseRestorer).restoreBackup("/path/to/dump"); + Mockito.verify(databaseRestorer).restoreBackup("/path/to/dump", this.tenantManager); } } diff --git a/engine/src/test/java/org/entando/entando/aps/system/services/cache/CacheInfoManagerTest.java b/engine/src/test/java/org/entando/entando/aps/system/services/cache/CacheInfoManagerTest.java index 4d235d6b2c..e0a6fb0370 100644 --- a/engine/src/test/java/org/entando/entando/aps/system/services/cache/CacheInfoManagerTest.java +++ b/engine/src/test/java/org/entando/entando/aps/system/services/cache/CacheInfoManagerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-Present Entando S.r.l. (http://www.entando.com) All rights reserved. + * Copyright 2018-Present Entando Inc. (http://www.entando.com) All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -32,8 +32,8 @@ */ @ExtendWith(MockitoExtension.class) class CacheInfoManagerTest { - - @Mock + + @Mock private CacheManager cacheManager; @Mock @@ -41,158 +41,159 @@ class CacheInfoManagerTest { @Mock private Cache.ValueWrapper valueWrapperForExpirationTime; - - @Mock - private Cache.ValueWrapper valueWrapperForGroups; - + @Mock private ProceedingJoinPoint proceedingJoinPoint; - @InjectMocks + @InjectMocks private CacheInfoManager cacheInfoManager; - - @BeforeEach - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - Map map = new HashMap<>(); - Mockito.lenient().when(valueWrapperForExpirationTime.get()).thenReturn(map); - Mockito.lenient().when(cache.get(Mockito.startsWith(ICacheInfoManager.EXPIRATIONS_CACHE_NAME_PREFIX))).thenReturn(valueWrapperForExpirationTime); - Map> groupsMap = new HashMap<>(); - List list_a = Arrays.asList("key_a1", "key_a2", "key_a3"); - List list_b = Arrays.asList("key_b1", "key_b2", "key_b3", "key_b4"); - groupsMap.put("group_1", new ArrayList<>(list_a)); - groupsMap.put("group_2", new ArrayList<>(list_b)); - Mockito.lenient().when(valueWrapperForGroups.get()).thenReturn(groupsMap); - Mockito.lenient().when(cache.get(Mockito.startsWith(ICacheInfoManager.GROUP_CACHE_NAME_PREFIX))).thenReturn(valueWrapperForGroups); - Mockito.lenient().when(cacheManager.getCache(Mockito.anyString())).thenReturn(this.cache); - } - + + @BeforeEach + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + Map map = new HashMap<>(); + Mockito.lenient().when(valueWrapperForExpirationTime.get()).thenReturn(map); + Mockito.lenient().when(cache.get(Mockito.startsWith(ICacheInfoManager.EXPIRATIONS_CACHE_NAME_PREFIX))).thenReturn(valueWrapperForExpirationTime); + Mockito.lenient().when(cacheManager.getCache(Mockito.anyString())).thenReturn(this.cache); + } + @Test void setExpirationTimeInMinutes() { - String targetCache = "targetCacheName1"; - String cacheKey = "testkey1"; - cacheInfoManager.putInCache(targetCache, cacheKey, "Some value"); + String targetCache = "targetCacheName1"; + String cacheKey = "testkey1"; + cacheInfoManager.putInCache(targetCache, cacheKey, "Some value"); cacheInfoManager.setExpirationTime(targetCache, cacheKey, 1); - boolean expired = cacheInfoManager.isExpired(targetCache, cacheKey); - Assertions.assertFalse(expired); + boolean expired = cacheInfoManager.isExpired(targetCache, cacheKey); + Assertions.assertFalse(expired); } - + @Test void setExpirationTimeInSeconds() throws Throwable { - String targetCache = "targetCacheName2"; - String cacheKey = "testkey2"; - cacheInfoManager.putInCache(targetCache, cacheKey, "Some other value"); + String targetCache = "targetCacheName2"; + String cacheKey = "testkey2"; + cacheInfoManager.putInCache(targetCache, cacheKey, "Some other value"); cacheInfoManager.setExpirationTime(targetCache, cacheKey, 1L); - boolean expired = cacheInfoManager.isExpired(targetCache, cacheKey); - Assertions.assertFalse(expired); - synchronized (this) { - this.wait(2000); - } - boolean expired2 = cacheInfoManager.isExpired(targetCache, cacheKey); - Assertions.assertTrue(expired2); + boolean expired = cacheInfoManager.isExpired(targetCache, cacheKey); + Assertions.assertFalse(expired); + synchronized (this) { + this.wait(2000); + } + boolean expired2 = cacheInfoManager.isExpired(targetCache, cacheKey); + Assertions.assertTrue(expired2); } - + @Test void updateFromPageChanged() { - PageChangedEvent event = new PageChangedEvent(); - Page page = new Page(); - page.setCode("code"); - event.setPage(page); + PageChangedEvent event = new PageChangedEvent(); + Page page = new Page(); + page.setCode("code"); + event.setPage(page); cacheInfoManager.updateFromPageChanged(event); - Mockito.verify(cache, Mockito.times(1)).get(Mockito.anyString()); - Mockito.verify(cache, Mockito.times(0)).put(Mockito.anyString(), Mockito.any(Map.class)); - Object requiredMap = cacheInfoManager.getFromCache(ICacheInfoManager.CACHE_INFO_MANAGER_CACHE_NAME, - ICacheInfoManager.GROUP_CACHE_NAME_PREFIX + ICacheInfoManager.CACHE_INFO_MANAGER_CACHE_NAME); - Assertions.assertTrue(requiredMap instanceof Map); - Assertions.assertNotNull(requiredMap); - Assertions.assertEquals(2, ((Map) requiredMap).size()); + Mockito.verify(cache, Mockito.times(1)).get(Mockito.anyString()); + Mockito.verify(cache, Mockito.times(0)).put(Mockito.anyString(), Mockito.any()); } - - @Test + + @Test void destroy() { - cacheInfoManager.destroy(); - Mockito.verify(cacheManager, Mockito.times(0)).getCacheNames(); - Mockito.verify(cacheManager, Mockito.times(4)).getCache(Mockito.anyString()); - Mockito.verify(cache, Mockito.times(2)).clear(); - } - - @Test + cacheInfoManager.destroy(); + Mockito.verify(cacheManager, Mockito.times(0)).getCacheNames(); + Mockito.verify(cacheManager, Mockito.times(4)).getCache(Mockito.anyString()); + Mockito.verify(cache, Mockito.times(2)).clear(); + } + + @Test void flushAll() { - cacheInfoManager.flushAll(); - Mockito.verify(cacheManager, Mockito.times(1)).getCacheNames(); - Mockito.verify(cacheManager, Mockito.times(0)).getCache(Mockito.anyString()); - Mockito.verify(cache, Mockito.times(0)).clear(); - } - - @Test + cacheInfoManager.flushAll(); + Mockito.verify(cacheManager, Mockito.times(1)).getCacheNames(); + Mockito.verify(cacheManager, Mockito.times(0)).getCache(Mockito.anyString()); + Mockito.verify(cache, Mockito.times(0)).clear(); + } + + @Test void flushAllWithCaches() { - List cacheNames = new ArrayList<>(); - cacheNames.add("cache1"); - cacheNames.add("cache2"); - Mockito.when(cacheManager.getCacheNames()).thenReturn(cacheNames); - cacheInfoManager.flushAll(); - Mockito.verify(cacheManager, Mockito.times(1)).getCacheNames(); - Mockito.verify(cacheManager, Mockito.times(4)).getCache(Mockito.anyString()); - Mockito.verify(cache, Mockito.times(2)).clear(); - } - - @Test + List cacheNames = new ArrayList<>(); + cacheNames.add("cache1"); + cacheNames.add("cache2"); + Mockito.when(cacheManager.getCacheNames()).thenReturn(cacheNames); + cacheInfoManager.flushAll(); + Mockito.verify(cacheManager, Mockito.times(1)).getCacheNames(); + Mockito.verify(cacheManager, Mockito.times(4)).getCache(Mockito.anyString()); + Mockito.verify(cache, Mockito.times(2)).clear(); + } + + @Test void flushEntry() { - String targetCache = "targetCacheName3"; - String cacheKey = "testkey3"; - cacheInfoManager.flushEntry(targetCache, cacheKey); - Mockito.verify(cacheManager, Mockito.times(1)).getCache(Mockito.eq(targetCache)); - Mockito.verify(cache, Mockito.times(1)).evict(Mockito.eq(cacheKey)); - } - - @Test + String targetCache = "targetCacheName3"; + String cacheKey = "testkey3"; + cacheInfoManager.flushEntry(targetCache, cacheKey); + Mockito.verify(cacheManager, Mockito.times(1)).getCache(Mockito.eq(targetCache)); + Mockito.verify(cache, Mockito.times(1)).evict(Mockito.eq(cacheKey)); + } + + @Test void putInCache() { - String targetCache = "targetCacheName3"; - String cacheKey = "testkey3"; - cacheInfoManager.putInCache(targetCache, cacheKey, "Some value"); - Mockito.verify(cacheManager, Mockito.times(1)).getCache(targetCache); - Mockito.verify(cache, Mockito.times(1)).put(cacheKey, "Some value"); - } - - @Test + String targetCache = "targetCacheName3"; + String cacheKey = "testkey3"; + cacheInfoManager.putInCache(targetCache, cacheKey, "Some value"); + Mockito.verify(cacheManager, Mockito.times(1)).getCache(targetCache); + Mockito.verify(cache, Mockito.times(1)).put(cacheKey, "Some value"); + } + + @Test void putInCacheWithGroups() { - String targetCache = "targetCacheName3"; - String cacheKey = "testkey3"; - String[] groups = new String[]{"group_1", "group_2"}; - cacheInfoManager.putInCache(targetCache, cacheKey, "Some value", groups); - Mockito.verify(cacheManager, Mockito.times(1)).getCache(targetCache); - Mockito.verify(cache, Mockito.times(1)).put(cacheKey, "Some value"); - Mockito.verify(cacheManager, Mockito.times(1)).getCache(ICacheInfoManager.CACHE_INFO_MANAGER_CACHE_NAME); - } - - @Test + String targetCache = "targetCacheName3"; + this.initGroups(targetCache); + String cacheKey = "testkey3"; + String[] groups = new String[]{"group_1", "group_2"}; + cacheInfoManager.putInCache(targetCache, cacheKey, "Some value", groups); + Mockito.verify(cacheManager, Mockito.times(1)).getCache(targetCache); + Mockito.verify(cache, Mockito.times(1)).put(cacheKey, "Some value"); + Mockito.verify(cacheManager, Mockito.times(1)).getCache(ICacheInfoManager.CACHE_INFO_MANAGER_CACHE_NAME); + } + + @Test void putInGroup() { - String targetCache = "targetCacheName4"; - String cacheKey = "testkey4"; - String[] groups = new String[]{"group_1", "group_2"}; - cacheInfoManager.putInGroup(targetCache, cacheKey, groups); - Mockito.verify(cacheManager, Mockito.times(0)).getCache(targetCache); - Mockito.verify(cache, Mockito.times(0)).put(Mockito.eq(cacheKey), Mockito.anyString()); - Mockito.verify(cacheManager, Mockito.times(1)).getCache(ICacheInfoManager.CACHE_INFO_MANAGER_CACHE_NAME); - } - - @Test + String targetCache = "targetCacheName4"; + this.initGroups(targetCache); + String cacheKey = "testkey4"; + String[] groups = new String[]{"group_1", "group_2"}; + cacheInfoManager.putInGroup(targetCache, cacheKey, groups); + Mockito.verify(cacheManager, Mockito.times(0)).getCache(targetCache); + Mockito.verify(cache, Mockito.times(0)).put(Mockito.eq(cacheKey), Mockito.anyString()); + Mockito.verify(cacheManager, Mockito.times(1)).getCache(ICacheInfoManager.CACHE_INFO_MANAGER_CACHE_NAME); + } + + @Test void flushGroup_1() { - this.flushGroup("group_1", 3); - } - - @Test + this.flushGroup("group_1", 3); + } + + @Test void flushGroup_2() { - this.flushGroup("group_2", 4); - } - + this.flushGroup("group_2", 4); + } + private void flushGroup(String groupName, int expectedEvict) { - String targetCache = "targetCacheName5"; - cacheInfoManager.flushGroup(targetCache, groupName); - Mockito.verify(cacheManager, Mockito.times(1)).getCache(ICacheInfoManager.CACHE_INFO_MANAGER_CACHE_NAME); - Mockito.verify(cacheManager, Mockito.times(expectedEvict)).getCache(targetCache); - Mockito.verify(cache, Mockito.times(expectedEvict)).evict(Mockito.any(Object.class)); - Mockito.verify(cache, Mockito.times(1)).put(Mockito.startsWith(ICacheInfoManager.GROUP_CACHE_NAME_PREFIX), Mockito.any(Object.class)); - } - + String targetCache = "targetCacheName5"; + this.initGroups(targetCache); + cacheInfoManager.flushGroup(targetCache, groupName); + Mockito.verify(cacheManager, Mockito.times(1)).getCache(ICacheInfoManager.CACHE_INFO_MANAGER_CACHE_NAME); + Mockito.verify(cacheManager, Mockito.times(expectedEvict)).getCache(targetCache); + Mockito.verify(cache, Mockito.times(expectedEvict + 1)).evict(Mockito.any(Object.class)); + Mockito.verify(cache, Mockito.times(0)).put(Mockito.any(), Mockito.any(Object.class)); + } + + private void initGroups(String targetCache) { + List list_a = new ArrayList<>(List.of("key_a1", "key_a2", "key_a3")); + List list_b = new ArrayList<>(List.of("key_b1", "key_b2", "key_b3", "key_b4")); + String key1 = "CacheInfoManager_groups_" + targetCache + "__GR__group_1"; + String key2 = "CacheInfoManager_groups_" + targetCache + "__GR__group_2"; + Cache.ValueWrapper valueWrapperForGroups1 = Mockito.mock(Cache.ValueWrapper.class); + Cache.ValueWrapper valueWrapperForGroups2 = Mockito.mock(Cache.ValueWrapper.class); + Mockito.lenient().when(cache.get(key1)).thenReturn(valueWrapperForGroups1); + Mockito.lenient().when(cache.get(key2)).thenReturn(valueWrapperForGroups2); + Mockito.lenient().when(valueWrapperForGroups1.get()).thenReturn(list_a); + Mockito.lenient().when(valueWrapperForGroups2.get()).thenReturn(list_b); + } + } diff --git a/engine/src/test/java/org/entando/entando/aps/system/services/page/PageServiceTest.java b/engine/src/test/java/org/entando/entando/aps/system/services/page/PageServiceTest.java index 2ba250a109..4573a9bd2f 100644 --- a/engine/src/test/java/org/entando/entando/aps/system/services/page/PageServiceTest.java +++ b/engine/src/test/java/org/entando/entando/aps/system/services/page/PageServiceTest.java @@ -195,10 +195,6 @@ public void shouldReturnCompatiblePages() { }; // Test truth tables generation - String[] parentOwnerGroup = new String[]{ // Parent OWNER GROUPS - FREE_GROUP_NAME, ADMIN_GROUP_NAME, A_GROUP_THAT_IS_PRESENT, "GROUP2" - }; - List>> map = new ArrayList<>(); List pSubCase1 = Arrays.asList(FREE_GROUP_NAME); List pSubCase2 = Arrays.asList(ADMIN_GROUP_NAME); @@ -254,37 +250,41 @@ public void shouldReturnCompatiblePages() { * PAGE USAGE DETAILS *********************************************************************************/ - + @Test + void shouldPublishPageRoot() throws Exception { + Page requestedPage = Mockito.mock(Page.class); + when(requestedPage.isRoot()).thenReturn(true); + when(pageManager.getDraftPage("page_code")).thenReturn(requestedPage); + Page onlinePage = Mockito.mock(Page.class); + when(pageManager.getOnlinePage("page_code")).thenReturn(onlinePage); + Mockito.when(dtoBuilder.convert(onlinePage)).thenReturn(Mockito.mock(PageDto.class)); + PageDto dto = this.pageService.updatePageStatus("page_code", IPageService.STATUS_ONLINE); + Assertions.assertNotNull(dto); + verify(pageManager, times(1)).setPageOnline("page_code"); + } + @Test void getPageUsageForNonExistingCodeShouldReturnZero() { - int componentUsage = pageService.getComponentUsage("non_existing"); assertEquals(0, componentUsage); } @Test void getPageUsageDetailsWithPublishedPageShouldAddItself() { - PageDto pageDto = PageMockHelper.mockPageDto(); - this.testSinglePageUsageDetails(pageDto); } @Test void getPageUsageDetailsWithPaginationAndWithPublishedPageShouldAddItself() { - PageDto pageDto = PageMockHelper.mockPageDto(); - this.testPagedPageUsageDetails(pageDto); } - @Test void getPageUsageDetailsWithInvalidCodeShouldThrowResourceNotFoundException() { - PageDto pageDto = PageMockHelper.mockPageDto(); mockForSinglePage(PageMockHelper.mockTestPage(PageMockHelper.PAGE_CODE), pageDto, PageMockHelper.UTILIZERS); - Arrays.stream(new String[]{"not existing", null, ""}) .forEach(code -> { try { @@ -296,37 +296,26 @@ void getPageUsageDetailsWithInvalidCodeShouldThrowResourceNotFoundException() { }); } - @Test void getPageUsageDetailsWithDraftPageShouldNOTAddItself() { - PageDto pageDto = PageMockHelper.mockPageDto(); pageDto.setStatus(IPageService.STATUS_DRAFT); - this.testSinglePageUsageDetails(pageDto); } - @Test void getPageUsageDetailsWithPaginationAndWithDraftPageShouldNOTAddItself() { - PageDto pageDto = PageMockHelper.mockPageDto(); pageDto.setStatus(IPageService.STATUS_DRAFT); - this.testPagedPageUsageDetails(pageDto); } - @Test void getPageUsageDetailsWithNoChildrenShouldReturnItself() { - PageDto pageDto = PageMockHelper.mockPageDto(); pageDto.setChildren(new ArrayList<>()); - mockForSinglePage(PageMockHelper.mockTestPage(PageMockHelper.PAGE_CODE), pageDto, new String[0]); - PagedMetadata pageUsageDetails = pageService.getComponentUsageDetails(PageMockHelper.PAGE_CODE, new PageSearchRequest(PageMockHelper.PAGE_CODE)); - PageAssertionHelper.assertUsageDetails(pageUsageDetails, new String[0], 0, 1, pageDto.getStatus()); } @@ -344,13 +333,9 @@ void shouldDeleteComponent() throws EntException { * @throws Exception */ private void testSinglePageUsageDetails(PageDto pageDto) { - Page page = PageMockHelper.mockTestPage(PageMockHelper.PAGE_CODE); - mockForSinglePage(page, pageDto, PageMockHelper.UTILIZERS); - PagedMetadata pageUsageDetails = pageService.getComponentUsageDetails(PageMockHelper.PAGE_CODE, new PageSearchRequest(PageMockHelper.PAGE_CODE)); - PageAssertionHelper.assertUsageDetails(pageUsageDetails, pageDto.getStatus()); } @@ -402,7 +387,6 @@ private void testPagedPageUsageDetails(PageDto pageDto) { * init mock for a single paged request */ private void mockForSinglePage(Page page, PageDto pageDto, String[] utilizers) { - mockPagedMetadata(page, pageDto, utilizers, 1, 1, 100, utilizers.length + (pageDto.getStatus().equals(IPageService.STATUS_ONLINE) ? 1 : 0)); } @@ -411,7 +395,6 @@ private void mockForSinglePage(Page page, PageDto pageDto, String[] utilizers) { * init mock for a multipaged request */ private void mockPagedMetadata(Page page, PageDto pageDto, String[] utilizers, int currPage, int lastPage, int pageSize, int totalSize) { - try { pageDto.getChildren().stream().forEach(childCode -> { IPage mockChild = Mockito.mock(IPage.class); diff --git a/engine/src/test/java/org/entando/entando/aps/system/services/storage/LocalStorageManagerIntegrationTest.java b/engine/src/test/java/org/entando/entando/aps/system/services/storage/LocalStorageManagerIntegrationTest.java index 5c469c9780..7261585176 100644 --- a/engine/src/test/java/org/entando/entando/aps/system/services/storage/LocalStorageManagerIntegrationTest.java +++ b/engine/src/test/java/org/entando/entando/aps/system/services/storage/LocalStorageManagerIntegrationTest.java @@ -182,7 +182,65 @@ void testSaveEditDeleteFile() throws Throwable { localStorageManager.deleteFile("non-existent", false) ); } - + + @Test + void testSuccessfullAddMoveFile() throws Throwable { + String testFileSourcePath = "testfolder_move/testMove.txt"; + String testFileDestPath = "testfolder_move/subfolder/testMoved.txt"; + InputStream streamDest = null; + assertFalse(this.localStorageManager.exists(testFileSourcePath, false)); + try { + String content = "Content of new text file to move"; + localStorageManager.saveFile(testFileSourcePath, false, new ByteArrayInputStream(content.getBytes())); + assertTrue(this.localStorageManager.exists(testFileSourcePath, false)); + boolean result = this.localStorageManager.move(testFileSourcePath, false, testFileDestPath, false); + assertTrue(result); + assertFalse(this.localStorageManager.exists(testFileSourcePath, false)); + streamDest = localStorageManager.getStream(testFileDestPath, false); + assertNotNull(streamDest); + String extractedString = IOUtils.toString(streamDest, "UTF-8"); + assertEquals(content, extractedString); + } catch (Throwable t) { + throw t; + } finally { + if (null != streamDest) { + streamDest.close(); + } + localStorageManager.deleteDirectory("testfolder_move/", false); + } + assertFalse(this.localStorageManager.exists(testFileSourcePath, false)); + assertFalse(this.localStorageManager.exists(testFileDestPath, false)); + } + + @Test + void testShowldGetErrorMovingFile() throws Throwable { + String testFileSourcePath = "test_move_wrong/testToMove.txt"; + String testFileDestPath = "test_move_wrong/testMoved.txt"; + assertFalse(this.localStorageManager.exists(testFileSourcePath, false)); + try { + String content = "Content of new text file to move"; + localStorageManager.saveFile(testFileDestPath, false, new ByteArrayInputStream(content.getBytes())); // create dest file + + boolean result = this.localStorageManager.move(testFileSourcePath, false, testFileDestPath, false); + assertFalse(result); // source file does not exists + assertFalse(this.localStorageManager.exists(testFileSourcePath, false)); + + localStorageManager.saveFile(testFileSourcePath, false, new ByteArrayInputStream(content.getBytes())); // create source file + result = this.localStorageManager.move(testFileSourcePath, false, testFileDestPath, false); + assertFalse(result); // dest file already exists + + localStorageManager.deleteFile(testFileDestPath, false); + result = this.localStorageManager.move(testFileSourcePath, false, testFileDestPath, false); + assertTrue(result); + } catch (Throwable t) { + throw t; + } finally { + localStorageManager.deleteDirectory("test_move_wrong/", false); + } + assertFalse(this.localStorageManager.exists(testFileSourcePath, false)); + assertFalse(this.localStorageManager.exists(testFileDestPath, false)); + } + @Test void testCreateDeleteFile_ShouldBlockPathTraversals() throws Throwable { String testFilePath = "../../testfolder/test.txt"; diff --git a/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/AvatarServiceTest.java b/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/AvatarServiceTest.java new file mode 100644 index 0000000000..7fff096693 --- /dev/null +++ b/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/AvatarServiceTest.java @@ -0,0 +1,150 @@ +/* + * Copyright 2024-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ +package org.entando.entando.aps.system.services.userprofile; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.agiletec.aps.system.services.user.UserDetails; +import java.util.List; +import org.entando.entando.aps.system.exception.ResourceNotFoundException; +import org.entando.entando.aps.system.exception.RestServerError; +import org.entando.entando.aps.system.services.storage.IFileBrowserService; +import org.entando.entando.aps.system.services.storage.model.BasicFileAttributeViewDto; +import org.entando.entando.aps.system.services.userpreferences.IUserPreferencesManager; +import org.entando.entando.aps.system.services.userprofile.model.AvatarDto; +import org.entando.entando.ent.exception.EntException; +import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.validation.BindingResult; + +@ExtendWith(MockitoExtension.class) +class AvatarServiceTest { + + @Mock + private IFileBrowserService fileBrowserService; + @Mock + private IUserPreferencesManager userPreferencesManager; + + IAvatarService avatarService; + + @BeforeEach + void init() { + avatarService = new AvatarService(fileBrowserService, userPreferencesManager); + } + + @Test + void shouldGetAvatarDataReturnAvatarInfo() throws EntException { + UserDetails user = Mockito.mock(UserDetails.class); + when(user.getUsername()).thenReturn("username_test"); + when(userPreferencesManager.isUserGravatarEnabled(user.getUsername())).thenReturn(false); + BasicFileAttributeViewDto dto = this.createMockFileAttributeDto("username_test", "png"); + when(fileBrowserService.exists("static/profile")).thenReturn(true); + when(fileBrowserService.browseFolder("static/profile", false)).thenReturn(List.of(dto)); + when(fileBrowserService.getFileStream(any(), any())).thenReturn(new byte[0]); + AvatarDto avatarData = avatarService.getAvatarData(user); + assertEquals("username_test.png", avatarData.getFilename()); + assertEquals("static/profile/username_test.png", avatarData.getCurrentPath()); + Assertions.assertFalse(avatarData.isGravatar()); + } + + @Test + void shouldGetAvatarDataReturnAvatarInfoWithGravatarEnabled() throws EntException { + UserDetails user = Mockito.mock(UserDetails.class); + when(user.getUsername()).thenReturn("username_test"); + when(userPreferencesManager.isUserGravatarEnabled(user.getUsername())).thenReturn(true); + AvatarDto avatarData = avatarService.getAvatarData(user); + verify(fileBrowserService, Mockito.times(0)).browseFolder(any(), any()); + verify(fileBrowserService, Mockito.times(0)).getFileStream(any(), any()); + Assertions.assertNull(avatarData.getFilename()); + Assertions.assertNull(avatarData.getCurrentPath()); + Assertions.assertTrue(avatarData.isGravatar()); + } + + @Test + void shouldGetAvatarDataThrowResourceNotFoundExceptionIfNoImageIsPresent() throws EntException { + assertThrows(ResourceNotFoundException.class, () -> avatarService.getAvatarData(mock(UserDetails.class))); + } + + @Test + void shouldUpdateAvatarAddProfilePictureFromTheRequest() throws EntException { + BasicFileAttributeViewDto dtoDirectory = new BasicFileAttributeViewDto(); + dtoDirectory.setDirectory(true); + dtoDirectory.setName("folder"); + BasicFileAttributeViewDto dto = this.createMockFileAttributeDto("test_username", "jpg"); + when(fileBrowserService.exists("static/profile")).thenReturn(true); + when(fileBrowserService.browseFolder(Mockito.anyString(), Mockito.eq(false))).thenReturn(List.of(dtoDirectory, dto)); + UserDetails userDetails = mock(UserDetails.class); + when(userDetails.getUsername()).thenReturn("test_username"); + avatarService.updateAvatar(mock(ProfileAvatarRequest.class), userDetails, mock(BindingResult.class)); + verify(userPreferencesManager, Mockito.times(1)).updateUserGravatarPreference("test_username", false); + verify(fileBrowserService, Mockito.times(1)).deleteFile(any(), any()); + verify(fileBrowserService, Mockito.times(1)).addFile(any(), any()); + } + + @Test + void shouldUpdateAvatarWithGravatar() throws EntException { + ProfileAvatarRequest request = new ProfileAvatarRequest(null, null, true); + UserDetails userDetails = mock(UserDetails.class); + when(userDetails.getUsername()).thenReturn("test_username_gravatar"); + when(fileBrowserService.exists("static/profile")).thenReturn(false); + avatarService.updateAvatar(request, userDetails, mock(BindingResult.class)); + verify(userPreferencesManager, Mockito.times(1)).updateUserGravatarPreference("test_username_gravatar", true); + verify(fileBrowserService, Mockito.times(0)).deleteFile(any(), any()); + verify(fileBrowserService, Mockito.times(0)).addFile(any(), any()); + } + + @Test + void shouldDeleteAvatar() throws EntException { + UserDetails userDetails = mock(UserDetails.class); + when(userDetails.getUsername()).thenReturn("user1"); + BasicFileAttributeViewDto dto = this.createMockFileAttributeDto("user1", "png"); + when(fileBrowserService.exists("static/profile")).thenReturn(true); + when(fileBrowserService.browseFolder(Mockito.anyString(), Mockito.eq(false))).thenReturn(List.of(dto)); + avatarService.deleteAvatar(userDetails, mock(BindingResult.class)); + verify(fileBrowserService, Mockito.times(1)).deleteFile(any(), any()); + verify(userPreferencesManager, Mockito.times(1)).updateUserGravatarPreference("user1", false); + } + + @Test + void shouldDeleteAvatarThrowException() throws EntException { + // set user details to return desired username + UserDetails userDetails = mock(UserDetails.class); + when(userDetails.getUsername()).thenReturn("user1"); + BasicFileAttributeViewDto dto = this.createMockFileAttributeDto("user1", "jpg"); + when(fileBrowserService.exists("static/profile")).thenReturn(true); + when(fileBrowserService.browseFolder(Mockito.anyString(), Mockito.eq(false))).thenReturn(List.of(dto)); + Mockito.doThrow(RuntimeException.class).when(fileBrowserService).deleteFile(Mockito.any(), Mockito.eq(false)); + assertThrows(RestServerError.class, + () -> avatarService.deleteAvatar(userDetails, mock(BindingResult.class))); + } + + private BasicFileAttributeViewDto createMockFileAttributeDto(String username, String fileExtention) { + BasicFileAttributeViewDto dto = new BasicFileAttributeViewDto(); + dto.setDirectory(Boolean.FALSE); + dto.setName(username + "." + fileExtention); + return dto; + } + +} diff --git a/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/UserManagementAspectTest.java b/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/UserManagementAspectTest.java new file mode 100644 index 0000000000..0d89c209b9 --- /dev/null +++ b/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/UserManagementAspectTest.java @@ -0,0 +1,247 @@ +/* + * Copyright 2018-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ +package org.entando.entando.aps.system.services.userprofile; + +import static org.mockito.Mockito.when; + +import com.agiletec.aps.system.SystemConstants; +import com.agiletec.aps.system.common.entity.model.attribute.MonoTextAttribute; +import com.agiletec.aps.system.common.entity.parse.attribute.MonoTextAttributeHandler; +import com.agiletec.aps.system.services.user.User; +import org.entando.entando.aps.system.services.userpreferences.IUserPreferencesManager; +import org.entando.entando.aps.system.services.userprofile.model.IUserProfile; +import org.entando.entando.aps.system.services.userprofile.model.UserProfile; +import org.entando.entando.ent.exception.EntException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class UserManagementAspectTest { + + @InjectMocks + private UserManagementAspect userManagementAspect; + + @Mock + private UserProfileManager userProfileManager; + @Mock + private IAvatarService avatarService; + @Mock + private IUserPreferencesManager userPreferencesManager; + + @Test + void testInjectProfile() throws EntException { + IUserProfile returned = this.createFakeProfile("test", SystemConstants.DEFAULT_PROFILE_TYPE_CODE); + when(userProfileManager.getProfile(Mockito.anyString())).thenReturn(returned); + + User user = new User(); + user.setUsername("test"); + Assertions.assertNull(user.getProfile()); + + userManagementAspect.injectProfile(user); + Mockito.verify(userProfileManager, Mockito.times(1)).getProfile("test"); + IUserProfile profile = (IUserProfile) user.getProfile(); + Assertions.assertNotNull(profile); + Assertions.assertEquals("test", profile.getUsername()); + } + + @Test + void testInjectProfileWithError() throws EntException { + when(userProfileManager.getProfile(Mockito.anyString())).thenThrow(EntException.class); + + User user = new User(); + user.setUsername("test"); + Assertions.assertNull(user.getProfile()); + + userManagementAspect.injectProfile(user); + Mockito.verify(userProfileManager, Mockito.times(1)).getProfile("test"); + Assertions.assertNull(user.getProfile()); + } + + @Test + void testInjectProfileWithNullUser() throws EntException { + userManagementAspect.injectProfile(null); + Mockito.verifyNoInteractions(userProfileManager); + } + + @Test + void testMissingInjectProfile() throws EntException { + IUserProfile profile = this.createFakeProfile("test", SystemConstants.DEFAULT_PROFILE_TYPE_CODE); + User user = new User(); + user.setUsername("test"); + user.setProfile(profile); + + userManagementAspect.injectProfile(user); + Mockito.verify(userProfileManager, Mockito.times(0)).getProfile("test"); + Assertions.assertNotNull(user.getProfile()); + Assertions.assertSame(profile, user.getProfile()); + } + + @Test + void testAddProfile() throws EntException { + IUserProfile profile = this.createFakeProfile("test", SystemConstants.DEFAULT_PROFILE_TYPE_CODE); + User user = new User(); + user.setUsername("test"); + user.setProfile(profile); + userManagementAspect.addProfile(user); + Mockito.verify(userProfileManager, Mockito.times(1)).addProfile("test", profile); + } + + @Test + void testAddProfileWithNullUser() throws EntException { + userManagementAspect.addProfile(null); + Mockito.verifyNoInteractions(userProfileManager); + } + + @Test + void testInvokeAddProfileWithUserWithNullProfile() throws EntException { + User user = new User(); + user.setUsername("test"); + userManagementAspect.addProfile(user); + Mockito.verifyNoInteractions(userProfileManager); + } + + @Test + void testInvokeAddProfileWithError() { + try { + Mockito.doThrow(EntException.class).when(userProfileManager).addProfile(Mockito.anyString(), Mockito.any()); + User user = new User(); + user.setUsername("test"); + user.setProfile(Mockito.mock(IUserProfile.class)); + userManagementAspect.addProfile(user); + Mockito.verify(userProfileManager, Mockito.times(1)).addProfile(Mockito.anyString(), Mockito.any(IUserProfile.class)); + } catch (Exception e) { + Assertions.fail(); + } + } + + @Test + void testUpdateProfile() throws EntException { + IUserProfile profile = this.createFakeProfile("test", SystemConstants.DEFAULT_PROFILE_TYPE_CODE); + User user = new User(); + user.setUsername("test"); + user.setProfile(profile); + userManagementAspect.updateProfile(user); + Mockito.verify(userProfileManager, Mockito.times(1)).updateProfile("test", profile); + } + + @Test + void testUpdateProfileWithNullUSer() throws EntException { + userManagementAspect.updateProfile(null); + Mockito.verifyNoInteractions(userProfileManager); + } + + @Test + void testInvokeUpdateProfileWithUserWithNullProfile() throws EntException { + User user = new User(); + user.setUsername("test"); + userManagementAspect.updateProfile(user); + Mockito.verifyNoInteractions(userProfileManager); + } + + @Test + void testInvokeUpdateProfileWithError() { + try { + Mockito.doThrow(EntException.class).when(userProfileManager).updateProfile(Mockito.anyString(), Mockito.any()); + User user = new User(); + user.setUsername("test"); + user.setProfile(Mockito.mock(IUserProfile.class)); + userManagementAspect.updateProfile(user); + Mockito.verify(userProfileManager, Mockito.times(1)).updateProfile(Mockito.anyString(), Mockito.any(IUserProfile.class)); + } catch (Exception e) { + Assertions.fail(); + } + } + + @Test + void testDeleteUserDataFromUser() throws EntException { + User user = new User(); + user.setUsername("test"); + userManagementAspect.deleteUserData(user); + Mockito.verify(userProfileManager, Mockito.times(1)).deleteProfile("test"); + Mockito.verify(userPreferencesManager, Mockito.times(1)).deleteUserPreferences("test"); + Mockito.verify(avatarService, Mockito.times(1)).deleteAvatar("test"); + } + + @Test + void testDeleteUserDataFromUsername() throws EntException { + userManagementAspect.deleteUserData("test"); + Mockito.verify(userProfileManager, Mockito.times(1)).deleteProfile("test"); + Mockito.verify(userPreferencesManager, Mockito.times(1)).deleteUserPreferences("test"); + Mockito.verify(avatarService, Mockito.times(1)).deleteAvatar("test"); + } + + @Test + void testDeleteUserDataWithNullUsername() throws EntException { + userManagementAspect.deleteUserData(null); + Mockito.verifyNoInteractions(userProfileManager); + Mockito.verifyNoInteractions(userPreferencesManager); + Mockito.verifyNoInteractions(avatarService); + } + + @Test + void testDeleteUserDataWithErrorOnUserProfileManager() { + try { + Mockito.doThrow(EntException.class).when(userProfileManager).deleteProfile(Mockito.anyString()); + userManagementAspect.deleteUserData("username_error1"); + Mockito.verify(userProfileManager, Mockito.times(1)).deleteProfile("username_error1"); + Mockito.verify(userPreferencesManager, Mockito.times(1)).deleteUserPreferences("username_error1"); + Mockito.verify(avatarService, Mockito.times(1)).deleteAvatar("username_error1"); + } catch (Exception e) { + Assertions.fail(); + } + } + + @Test + void testDeleteUserDataWithErrorOnUserAvatarService() { + try { + Mockito.doThrow(EntException.class).when(avatarService).deleteAvatar(Mockito.anyString()); + userManagementAspect.deleteUserData("username_error2"); + Mockito.verify(userProfileManager, Mockito.times(1)).deleteProfile("username_error2"); + Mockito.verify(userPreferencesManager, Mockito.times(1)).deleteUserPreferences("username_error2"); + Mockito.verify(avatarService, Mockito.times(1)).deleteAvatar("username_error2"); + } catch (Exception e) { + Assertions.fail(); + } + } + + @Test + void testDeleteUserDataWithErrorOnUserPreference() { + try { + Mockito.doThrow(EntException.class).when(userPreferencesManager).deleteUserPreferences(Mockito.anyString()); + userManagementAspect.deleteUserData("username_error3"); + Mockito.verify(userProfileManager, Mockito.times(1)).deleteProfile("username_error3"); + Mockito.verify(userPreferencesManager, Mockito.times(1)).deleteUserPreferences("username_error3"); + Mockito.verify(avatarService, Mockito.times(1)).deleteAvatar("username_error3"); + } catch (Exception e) { + Assertions.fail(); + } + } + + private IUserProfile createFakeProfile(String username, String defaultProfileTypeCode) { + UserProfile userProfile = new UserProfile(); + userProfile.setId(username); + MonoTextAttribute monoTextAttribute = new MonoTextAttribute(); + monoTextAttribute.setName("Name"); + monoTextAttribute.setHandler(new MonoTextAttributeHandler()); + userProfile.addAttribute(monoTextAttribute); + userProfile.setTypeCode(defaultProfileTypeCode); + return userProfile; + } + +} diff --git a/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/UserProfileManagerAspectTest.java b/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/UserProfileManagerAspectTest.java deleted file mode 100644 index de4b52a53d..0000000000 --- a/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/UserProfileManagerAspectTest.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2018-Present Entando Inc. (http://www.entando.com) All rights reserved. - * - * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 2.1 of the License, or (at your option) - * any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - */ -package org.entando.entando.aps.system.services.userprofile; - -import static org.mockito.Mockito.when; - -import com.agiletec.aps.system.SystemConstants; -import com.agiletec.aps.system.common.entity.model.attribute.MonoTextAttribute; -import com.agiletec.aps.system.common.entity.parse.attribute.MonoTextAttributeHandler; -import com.agiletec.aps.system.services.user.User; -import org.entando.entando.aps.system.services.userprofile.model.IUserProfile; -import org.entando.entando.aps.system.services.userprofile.model.UserProfile; -import org.entando.entando.ent.exception.EntException; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class UserProfileManagerAspectTest { - - @InjectMocks - private UserProfileManagerAspect userProfileManagerAspect; - - @Mock - private UserProfileManager userProfileManager; - - @BeforeEach - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - } - - @Test - void testInjectProfile_1() throws EntException { - IUserProfile returned = this.createFakeProfile("test", SystemConstants.DEFAULT_PROFILE_TYPE_CODE); - when(userProfileManager.getProfile(Mockito.anyString())).thenReturn(returned); - - User user = new User(); - user.setUsername("test"); - Assertions.assertNull(user.getProfile()); - - userProfileManagerAspect.injectProfile(user); - Mockito.verify(userProfileManager, Mockito.times(1)).getProfile("test"); - IUserProfile profile = (IUserProfile) user.getProfile(); - Assertions.assertNotNull(profile); - Assertions.assertEquals("test", profile.getUsername()); - } - - @Test - void testInjectProfile_2() throws EntException { - when(userProfileManager.getProfile(Mockito.anyString())).thenThrow(EntException.class); - - User user = new User(); - user.setUsername("test"); - Assertions.assertNull(user.getProfile()); - - userProfileManagerAspect.injectProfile(user); - Mockito.verify(userProfileManager, Mockito.times(1)).getProfile("test"); - Assertions.assertNull(user.getProfile()); - } - - @Test - void testInjectProfile_3() throws EntException { - Mockito.lenient().when(userProfileManager.getProfile(Mockito.anyString())).thenThrow(EntException.class); - - IUserProfile profile = this.createFakeProfile("test", SystemConstants.DEFAULT_PROFILE_TYPE_CODE); - User user = new User(); - user.setUsername("test"); - user.setProfile(profile); - - userProfileManagerAspect.injectProfile(user); - Mockito.verify(userProfileManager, Mockito.times(0)).getProfile("test"); - Assertions.assertNotNull(user.getProfile()); - Assertions.assertSame(profile, user.getProfile()); - } - - @Test - void testAddProfile_1() throws EntException { - IUserProfile profile = this.createFakeProfile("test", SystemConstants.DEFAULT_PROFILE_TYPE_CODE); - User user = new User(); - user.setUsername("test"); - user.setProfile(profile); - userProfileManagerAspect.addProfile(user); - Mockito.verify(userProfileManager, Mockito.times(1)).addProfile("test", profile); - } - - @Test - void testAddProfile_2() throws EntException { - User user = new User(); - user.setUsername("test"); - userProfileManagerAspect.addProfile(user); - Mockito.verify(userProfileManager, Mockito.times(0)).addProfile(Mockito.anyString(), Mockito.any(IUserProfile.class)); - } - - @Test - void testUpdateProfile_1() throws EntException { - IUserProfile profile = this.createFakeProfile("test", SystemConstants.DEFAULT_PROFILE_TYPE_CODE); - User user = new User(); - user.setUsername("test"); - user.setProfile(profile); - userProfileManagerAspect.updateProfile(user); - Mockito.verify(userProfileManager, Mockito.times(1)).updateProfile("test", profile); - } - - @Test - void testUpdateProfile_2() throws EntException { - User user = new User(); - user.setUsername("test"); - userProfileManagerAspect.updateProfile(user); - Mockito.verify(userProfileManager, Mockito.times(0)).updateProfile(Mockito.anyString(), Mockito.any(IUserProfile.class)); - } - - @Test - void testDeleteProfile_1() throws EntException { - User user = new User(); - user.setUsername("test"); - userProfileManagerAspect.deleteProfile(user); - Mockito.verify(userProfileManager, Mockito.times(1)).deleteProfile("test"); - } - - @Test - void testDeleteProfile_2() throws EntException { - userProfileManagerAspect.deleteProfile("test"); - Mockito.verify(userProfileManager, Mockito.times(1)).deleteProfile("test"); - } - - @Test - void testDeleteProfile_3() throws EntException { - userProfileManagerAspect.deleteProfile(null); - Mockito.verify(userProfileManager, Mockito.times(0)).deleteProfile("test"); - } - - private IUserProfile createFakeProfile(String username, String defaultProfileTypeCode) { - UserProfile userProfile = new UserProfile(); - userProfile.setId(username); - MonoTextAttribute monoTextAttribute = new MonoTextAttribute(); - monoTextAttribute.setName("Name"); - monoTextAttribute.setHandler(new MonoTextAttributeHandler()); - userProfile.addAttribute(monoTextAttribute); - userProfile.setTypeCode(defaultProfileTypeCode); - return userProfile; - } - -} diff --git a/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/UserProfileManagerIntegrationTest.java b/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/UserProfileManagerIntegrationTest.java index 7d6791e045..1afa81bad6 100644 --- a/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/UserProfileManagerIntegrationTest.java +++ b/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/UserProfileManagerIntegrationTest.java @@ -279,7 +279,7 @@ private void verifyRecordOrder(List records, String[] order) { @Test void testLoadRoles() throws Exception { List roles = this.profileManager.getAttributeRoles(); - assertEquals(8, roles.size()); + assertEquals(7, roles.size()); AttributeRole role1 = this.profileManager.getAttributeRole("userprofile:surname"); assertNotNull(role1); assertEquals(1, role1.getAllowedAttributeTypes().size()); diff --git a/engine/src/test/java/org/entando/entando/aps/util/UrlUtilsTest.java b/engine/src/test/java/org/entando/entando/aps/util/UrlUtilsTest.java index ea2427a506..959500b4ed 100644 --- a/engine/src/test/java/org/entando/entando/aps/util/UrlUtilsTest.java +++ b/engine/src/test/java/org/entando/entando/aps/util/UrlUtilsTest.java @@ -21,13 +21,9 @@ import com.google.common.net.HttpHeaders; import java.net.URI; -import java.util.HashMap; -import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import org.entando.entando.aps.util.UrlUtils.EntUrlBuilder; -import org.entando.entando.test_utils.UnitTestUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -36,11 +32,17 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +import uk.org.webcompere.systemstubs.jupiter.SystemStub; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; -@ExtendWith(MockitoExtension.class) +@ExtendWith({MockitoExtension.class, SystemStubsExtension.class}) class UrlUtilsTest { @Mock private HttpServletRequest requestMock; + + @SystemStub + private EnvironmentVariables environmentVariables; @BeforeEach private void init() throws Exception { @@ -51,33 +53,26 @@ private void init() throws Exception { public void afterAll() throws Exception { Mockito.reset(requestMock); } - + @Test void shouldFetchSchemeWorksFineWithDifferentInputs() throws Exception { - // case1 - Map envsOrig = System.getenv().entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - Map envs = (HashMap) envsOrig.entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - envs.put("ENTANDO_APP_USE_TLS", "true"); - UnitTestUtils.setEnv(envs); - when(requestMock.getHeader(HttpHeaders.X_FORWARDED_PROTO)).thenReturn(HTTP_SCHEME); - when(requestMock.getScheme()).thenReturn(HTTP_SCHEME); - Assertions.assertEquals(HTTPS_SCHEME, UrlUtils.fetchScheme(requestMock)); - UnitTestUtils.setEnv(envsOrig); - - // case2 + // case0 Mockito.reset(requestMock); when(requestMock.getHeader(HttpHeaders.X_FORWARDED_PROTO)).thenReturn(HTTPS_SCHEME); when(requestMock.getScheme()).thenReturn(HTTP_SCHEME); Assertions.assertEquals(HTTPS_SCHEME, UrlUtils.fetchScheme(requestMock)); - // case3 + // case1 Mockito.reset(requestMock); when(requestMock.getHeader(HttpHeaders.X_FORWARDED_PROTO)).thenReturn(HTTP_SCHEME); when(requestMock.getScheme()).thenReturn(HTTP_SCHEME); Assertions.assertEquals(HTTP_SCHEME, UrlUtils.fetchScheme(requestMock)); + + // case3 + environmentVariables.set("ENTANDO_APP_USE_TLS", "true"); + when(requestMock.getHeader(HttpHeaders.X_FORWARDED_PROTO)).thenReturn(HTTP_SCHEME); + when(requestMock.getScheme()).thenReturn(HTTP_SCHEME); + Assertions.assertEquals(HTTPS_SCHEME, UrlUtils.fetchScheme(requestMock)); } @@ -114,31 +109,15 @@ void shouldFetchHostWorksFineWithDifferentInputs() throws Exception { @Test void shouldFetchPortWorksFineWithDifferentInputs() throws Exception { - // case 0 - Map envsOrig = System.getenv().entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - Map envs = (HashMap) envsOrig.entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - envs.put("ENTANDO_APP_ENGINE_EXTERNAL_PORT", "8888"); - UnitTestUtils.setEnv(envs); + // case0 when(requestMock.getHeader(HttpHeaders.X_FORWARDED_PORT)).thenReturn("443"); when(requestMock.getHeader(HttpHeaders.HOST)).thenReturn("test.com:4443"); when(requestMock.getServerPort()).thenReturn(8443); - Optional port = UrlUtils.fetchPort(requestMock); - Assertions.assertTrue(port.isPresent()); - Assertions.assertEquals(8888, port.get()); - UnitTestUtils.setEnv(envsOrig); - - // case1 - when(requestMock.getHeader(HttpHeaders.X_FORWARDED_PORT)).thenReturn("443"); - when(requestMock.getHeader(HttpHeaders.HOST)).thenReturn("test.com:4443"); - when(requestMock.getServerPort()).thenReturn(8443); - port = UrlUtils.fetchPort(requestMock); + Optional port = port = UrlUtils.fetchPort(requestMock); Assertions.assertTrue(port.isPresent()); Assertions.assertEquals(443, port.get()); - // case2-a + // case1-a Mockito.reset(requestMock); when(requestMock.getHeader(HttpHeaders.X_FORWARDED_PORT)).thenReturn(null); when(requestMock.getHeader(HttpHeaders.HOST)).thenReturn("test.com:4443"); @@ -148,7 +127,7 @@ void shouldFetchPortWorksFineWithDifferentInputs() throws Exception { Assertions.assertTrue(port.isPresent()); Assertions.assertEquals(4443, port.get()); - // case2-b + // case1-b Mockito.reset(requestMock); when(requestMock.getHeader(HttpHeaders.X_FORWARDED_PORT)).thenReturn(null); when(requestMock.getHeader(HttpHeaders.HOST)).thenReturn("test.com:4443"); @@ -158,7 +137,7 @@ void shouldFetchPortWorksFineWithDifferentInputs() throws Exception { Assertions.assertTrue(port.isPresent()); Assertions.assertEquals(8443, port.get()); - // case3 + // case2 Mockito.reset(requestMock); when(requestMock.getHeader(HttpHeaders.X_FORWARDED_PORT)).thenReturn(null); when(requestMock.getHeader(HttpHeaders.HOST)).thenReturn(null); @@ -167,7 +146,7 @@ void shouldFetchPortWorksFineWithDifferentInputs() throws Exception { Assertions.assertTrue(port.isPresent()); Assertions.assertEquals(8443, port.get()); - // case4 + // case3 Mockito.reset(requestMock); when(requestMock.getHeader(HttpHeaders.X_FORWARDED_PORT)).thenReturn(null); when(requestMock.getHeader(HttpHeaders.HOST)).thenReturn(null); @@ -175,6 +154,14 @@ void shouldFetchPortWorksFineWithDifferentInputs() throws Exception { port = UrlUtils.fetchPort(requestMock); Assertions.assertTrue(port.isEmpty()); + // case 4 + environmentVariables.set("ENTANDO_APP_ENGINE_EXTERNAL_PORT", "8888"); + when(requestMock.getHeader(HttpHeaders.X_FORWARDED_PORT)).thenReturn("443"); + when(requestMock.getHeader(HttpHeaders.HOST)).thenReturn("test.com:4443"); + when(requestMock.getServerPort()).thenReturn(8443); + port = UrlUtils.fetchPort(requestMock); + Assertions.assertTrue(port.isPresent()); + Assertions.assertEquals(8888, port.get()); } @Test diff --git a/engine/src/test/java/org/entando/entando/test_utils/UnitTestUtils.java b/engine/src/test/java/org/entando/entando/test_utils/UnitTestUtils.java deleted file mode 100644 index 00dd1224af..0000000000 --- a/engine/src/test/java/org/entando/entando/test_utils/UnitTestUtils.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.entando.entando.test_utils; - -import java.lang.reflect.Field; -import java.util.Collections; -import java.util.Map; - -public final class UnitTestUtils { - - private UnitTestUtils() { - } - - /** - * use the received map as source to inject some variables in the environment. - * - * @param envMap the map from which get data to populate the environment variables - * @throws Exception if an error occurr during the injection - */ - public static void setEnv(Map envMap) throws Exception { - - try { - Class processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment"); - Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment"); - theEnvironmentField.setAccessible(true); - Map env = (Map) theEnvironmentField.get(null); - env.putAll(envMap); - Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField( - "theCaseInsensitiveEnvironment"); - theCaseInsensitiveEnvironmentField.setAccessible(true); - Map cienv = (Map) theCaseInsensitiveEnvironmentField.get(null); - cienv.putAll(envMap); - } catch (NoSuchFieldException e) { - Class[] classes = Collections.class.getDeclaredClasses(); - Map env = System.getenv(); - for (Class cl : classes) { - if ("java.util.Collections$UnmodifiableMap".equals(cl.getName())) { - Field field = cl.getDeclaredField("m"); - field.setAccessible(true); - Object obj = field.get(env); - Map map = (Map) obj; - map.clear(); - map.putAll(envMap); - } - } - } - } -} - diff --git a/engine/src/test/java/org/entando/entando/web/database/DatabaseControllerTest.java b/engine/src/test/java/org/entando/entando/web/database/DatabaseControllerTest.java index 43da972a63..61f564edde 100644 --- a/engine/src/test/java/org/entando/entando/web/database/DatabaseControllerTest.java +++ b/engine/src/test/java/org/entando/entando/web/database/DatabaseControllerTest.java @@ -13,6 +13,8 @@ */ package org.entando.entando.web.database; +import static org.hamcrest.CoreMatchers.is; + import com.agiletec.aps.system.services.user.UserDetails; import org.entando.entando.aps.system.init.DatabaseManager; import org.entando.entando.aps.system.init.IComponentManager; @@ -36,6 +38,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.junit.jupiter.api.BeforeEach; @@ -73,6 +76,7 @@ public void setUp() throws Exception { .build(); databaseService.setDatabaseManager(this.databaseManager); databaseService.setComponentManager(this.componentManager); + databaseService.setRestoreEnabled(true); controller.setDatabaseService(databaseService); } @@ -125,7 +129,7 @@ void startBackup() throws Exception { } @Test - void startRestore_1() throws Exception { + void requireRestore() throws Exception { UserDetails user = new OAuth2TestUtils.UserBuilder("jack_bauer", "0x24").grantedToRoleAdmin().build(); String accessToken = mockOAuthInterceptor(user); String xml = null; @@ -140,7 +144,7 @@ void startRestore_1() throws Exception { } @Test - void startRestore_2() throws Exception { + void requireRestoreWithoutBackup() throws Exception { UserDetails user = new OAuth2TestUtils.UserBuilder("jack_bauer", "0x24").grantedToRoleAdmin().build(); String accessToken = mockOAuthInterceptor(user); when(databaseManager.getBackupReport(ArgumentMatchers.anyString())).thenReturn(null); @@ -151,6 +155,20 @@ void startRestore_2() throws Exception { Mockito.verify(databaseService, Mockito.times(1)).startDatabaseRestore("reportCode"); Mockito.verify(databaseManager, Mockito.times(0)).dropAndRestoreBackup("reportCode"); } + + @Test + void requireRestoreWithRestoreDisabled() throws Exception { + UserDetails user = new OAuth2TestUtils.UserBuilder("jack_bauer", "0x24").grantedToRoleAdmin().build(); + String accessToken = mockOAuthInterceptor(user); + databaseService.setRestoreEnabled(false); + ResultActions result = mockMvc.perform( + put("/database/restoreBackup/{reportCode}", "reportCode").content("{}") + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().isBadRequest()); + result.andExpect(jsonPath("$.errors.size()", is(1))); + result.andExpect(jsonPath("$.errors[0].code", is("2"))); + Mockito.verifyNoInteractions(this.databaseManager); + } @Test void deleteReport() throws Exception { diff --git a/engine/src/test/java/org/entando/entando/web/page/PageConfigurationControllerTest.java b/engine/src/test/java/org/entando/entando/web/page/PageConfigurationControllerTest.java new file mode 100644 index 0000000000..729767fd4b --- /dev/null +++ b/engine/src/test/java/org/entando/entando/web/page/PageConfigurationControllerTest.java @@ -0,0 +1,129 @@ +/* + * Copyright 2024-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ +package org.entando.entando.web.page; + +import static org.hamcrest.CoreMatchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.agiletec.aps.system.services.group.Group; +import com.agiletec.aps.system.services.page.IPage; +import com.agiletec.aps.system.services.page.PageMetadata; +import com.agiletec.aps.system.services.page.Widget; +import com.agiletec.aps.system.services.role.Permission; +import com.agiletec.aps.system.services.user.UserDetails; +import com.agiletec.aps.util.ApsProperties; +import org.entando.entando.aps.system.services.page.IPageAuthorizationService; +import org.entando.entando.aps.system.services.page.IPageService; +import org.entando.entando.aps.system.services.page.model.PageConfigurationDto; +import org.entando.entando.web.AbstractControllerTest; +import org.entando.entando.web.page.validator.PageConfigurationValidator; +import org.entando.entando.web.utils.OAuth2TestUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +@ExtendWith(MockitoExtension.class) +class PageConfigurationControllerTest extends AbstractControllerTest { + + @Mock + private PageConfigurationValidator validator; + + @Mock + private IPageService pageService; + + @Mock + private IPageAuthorizationService authorizationService; + + @InjectMocks + private PageConfigurationController controller; + + @BeforeEach + public void setUp() throws Exception { + mockMvc = MockMvcBuilders.standaloneSetup(controller) + .addInterceptors(entandoOauth2Interceptor) + .setMessageConverters(getMessageConverters()) + .setHandlerExceptionResolvers(createHandlerExceptionResolver()) + .build(); + } + + @Test + void shouldReturnWidgetsWithConfiguration() throws Exception { + UserDetails user = new OAuth2TestUtils.UserBuilder("jack_bauer", "0x24") + .withAuthorization(Group.FREE_GROUP_NAME, "managePages", Permission.MANAGE_PAGES) + .build(); + String accessToken = mockOAuthInterceptor(user); + + IPage page = Mockito.mock(IPage.class); + Mockito.when(page.getMetadata()).thenReturn(new PageMetadata()); + Widget widget = new Widget(); + widget.setTypeCode("custom_type"); + ApsProperties properties = new ApsProperties(); + properties.put("maxElemForItem",15); + properties.put("title_en", "all offices"); + properties.put("title_it", "Tutti gli uffici"); + properties.put("userFilters", "(attributeFilter=true;key=title)+(attributeFilter=true;key=arguments;value=;test)"); + properties.put("layout", 2); + properties.put("filters", "(attributeFilter=true;key=typology;value=amm_03)+(order=ASC;attributeFilter=true;key=title)"); + properties.put("contentType", "ORG"); + properties.put("modelId", "220021"); + widget.setConfig(properties); + Widget[] widgets = {widget}; + Mockito.when(page.getWidgets()).thenReturn(widgets); + PageConfigurationDto pageConfiguration = new PageConfigurationDto(page, IPageService.STATUS_DRAFT); + Mockito.when(this.authorizationService.canView(Mockito.any(UserDetails.class),Mockito.anyString(), Mockito.anyBoolean())).thenReturn(true); + Mockito.when(this.pageService.getPageConfiguration("test_page", IPageService.STATUS_DRAFT)).thenReturn(pageConfiguration); + + ResultActions result = mockMvc.perform( + get("/pages/{pageCode}/widgets", "test_page") + .param("status", IPageService.STATUS_DRAFT) + .header("Authorization", "Bearer " + accessToken) + ); + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.payload.size()", is(1))) + .andExpect(jsonPath("$.payload[0].code", is("custom_type"))) + .andExpect(jsonPath("$.payload[0].config.size()", is(8))) + .andExpect(jsonPath("$.payload[0].config.maxElemForItem", is("15"))) + .andExpect(jsonPath("$.payload[0].config.title_en", is("all offices"))) + .andExpect(jsonPath("$.payload[0].config.title_it", is("Tutti gli uffici"))) + .andExpect(jsonPath("$.payload[0].config.userFilters.size()", is(2))) + .andExpect(jsonPath("$.payload[0].config.userFilters[0].size()", is(2))) + .andExpect(jsonPath("$.payload[0].config.userFilters[0].attributeFilter", is(true))) + .andExpect(jsonPath("$.payload[0].config.userFilters[0].key", is("title"))) + .andExpect(jsonPath("$.payload[0].config.userFilters[1].size()", is(3))) + .andExpect(jsonPath("$.payload[0].config.userFilters[1].attributeFilter", is(true))) + .andExpect(jsonPath("$.payload[0].config.userFilters[1].value", is(""))) + .andExpect(jsonPath("$.payload[0].config.userFilters[1].key", is("arguments"))) + .andExpect(jsonPath("$.payload[0].config.layout", is("2"))) + .andExpect(jsonPath("$.payload[0].config.filters.size()", is(2))) + .andExpect(jsonPath("$.payload[0].config.filters[0].size()", is(3))) + .andExpect(jsonPath("$.payload[0].config.filters[0].attributeFilter", is(true))) + .andExpect(jsonPath("$.payload[0].config.filters[0].key", is("typology"))) + .andExpect(jsonPath("$.payload[0].config.filters[0].value", is("amm_03"))) + .andExpect(jsonPath("$.payload[0].config.filters[1].size()", is(3))) + .andExpect(jsonPath("$.payload[0].config.filters[1].order", is("ASC"))) + .andExpect(jsonPath("$.payload[0].config.filters[1].attributeFilter", is(true))) + .andExpect(jsonPath("$.payload[0].config.filters[1].key", is("title"))) + .andExpect(jsonPath("$.payload[0].config.contentType", is("ORG"))) + .andExpect(jsonPath("$.payload[0].config.modelId", is("220021"))); + } + +} diff --git a/engine/src/test/java/org/entando/entando/web/page/PageControllerTest.java b/engine/src/test/java/org/entando/entando/web/page/PageControllerTest.java index 6095163e97..80aa542985 100644 --- a/engine/src/test/java/org/entando/entando/web/page/PageControllerTest.java +++ b/engine/src/test/java/org/entando/entando/web/page/PageControllerTest.java @@ -306,8 +306,7 @@ void shouldValidatePutPathMismatch() throws Exception { .content(mockJsonResult) .header("Authorization", "Bearer " + accessToken) ); - - String response = result.andReturn().getResponse().getContentAsString(); + result.andExpect(status().isBadRequest()); result.andExpect(jsonPath("$.errors", hasSize(1))); result.andExpect(jsonPath("$.errors[0].code", is(PageValidator.ERRCODE_URINAME_MISMATCH))); @@ -366,7 +365,6 @@ void shouldValidateDeleteOnlinePage() throws EntException, Exception { .header("Authorization", "Bearer " + accessToken)); result.andExpect(status().isBadRequest()); - String response = result.andReturn().getResponse().getContentAsString(); result.andExpect(jsonPath("$.errors", hasSize(1))); result.andExpect(jsonPath("$.errors[0].code", is(PageValidator.ERRCODE_ONLINE_PAGE))); } @@ -378,6 +376,7 @@ void shouldValidateDeletePageWithChildren() throws EntException, Exception { Page page = new Page(); page.setCode("page_with_children"); + page.setParentCode("parent_page"); page.addChildCode("child"); when(authorizationService.canEdit(any(UserDetails.class), any(String.class))).thenReturn(true); when(this.controller.getPageValidator().getPageManager().getDraftPage(any(String.class))).thenReturn(page); @@ -386,11 +385,30 @@ void shouldValidateDeletePageWithChildren() throws EntException, Exception { .header("Authorization", "Bearer " + accessToken)); result.andExpect(status().isBadRequest()); - String response = result.andReturn().getResponse().getContentAsString(); result.andExpect(jsonPath("$.errors", hasSize(1))); result.andExpect(jsonPath("$.errors[0].code", is(PageValidator.ERRCODE_PAGE_HAS_CHILDREN))); } + @Test + void shouldValidateDeletePageRoot() throws EntException, Exception { + UserDetails user = new OAuth2TestUtils.UserBuilder("jack_bauer", "0x24").grantedToRoleAdmin().build(); + String accessToken = mockOAuthInterceptor(user); + + Page page = Mockito.mock(Page.class); + when(page.getChildrenCodes()).thenReturn(new String[]{}); + when(page.isRoot()).thenReturn(true); + + when(authorizationService.canEdit(any(UserDetails.class), any(String.class))).thenReturn(true); + when(this.controller.getPageValidator().getPageManager().getDraftPage(any(String.class))).thenReturn(page); + ResultActions result = mockMvc.perform( + delete("/pages/{pageCode}", "page_root") + .header("Authorization", "Bearer " + accessToken)); + + result.andExpect(status().isBadRequest()); + result.andExpect(jsonPath("$.errors", hasSize(1))); + result.andExpect(jsonPath("$.errors[0].code", is(PageValidator.ERRCODE_PAGE_ROOT))); + } + @Test void shouldValidateMovePageInvalidRequest() throws EntException, Exception { UserDetails user = new OAuth2TestUtils.UserBuilder("jack_bauer", "0x24").grantedToRoleAdmin().build(); @@ -408,7 +426,6 @@ void shouldValidateMovePageInvalidRequest() throws EntException, Exception { .header("Authorization", "Bearer " + accessToken)); result.andExpect(status().isBadRequest()); - String response = result.andReturn().getResponse().getContentAsString(); result.andExpect(jsonPath("$.errors", hasSize(1))); result.andExpect(jsonPath("$.errors[0].code", is("NotBlank"))); } @@ -585,7 +602,6 @@ void shouldValidateMovePageStatusMismatch() throws EntException, Exception { .header("Authorization", "Bearer " + accessToken)); result.andExpect(status().isBadRequest()); - String response = result.andReturn().getResponse().getContentAsString(); result.andExpect(jsonPath("$.errors", hasSize(1))); result.andExpect(jsonPath("$.errors[0].code", is(PageValidator.ERRCODE_STATUS_PAGE_MISMATCH))); } diff --git a/engine/src/test/java/org/entando/entando/web/userpreferences/UserPreferencesControllerIntegrationTest.java b/engine/src/test/java/org/entando/entando/web/userpreferences/UserPreferencesControllerIntegrationTest.java index 965434a258..2c18397a2b 100644 --- a/engine/src/test/java/org/entando/entando/web/userpreferences/UserPreferencesControllerIntegrationTest.java +++ b/engine/src/test/java/org/entando/entando/web/userpreferences/UserPreferencesControllerIntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved. + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -13,8 +13,7 @@ */ package org.entando.entando.web.userpreferences; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -22,18 +21,20 @@ import com.agiletec.aps.system.services.user.User; import com.agiletec.aps.system.services.user.UserDetails; import com.agiletec.aps.util.FileTextReader; +import com.fasterxml.jackson.databind.ObjectMapper; import java.io.InputStream; import java.util.Date; import org.entando.entando.aps.system.services.userpreferences.IUserPreferencesManager; import org.entando.entando.web.AbstractControllerIntegrationTest; +import org.entando.entando.web.userpreferences.model.UserPreferencesRequest; import org.entando.entando.web.utils.OAuth2TestUtils; import org.hamcrest.Matchers; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.result.MockMvcResultHandlers; class UserPreferencesControllerIntegrationTest extends AbstractControllerIntegrationTest { @@ -59,16 +60,56 @@ void testGetWithUnknownUser() throws Exception { .andExpect(jsonPath("$.errors[0].message", Matchers.is("a User with unknown_user code could not be found"))); } - + + @Test + void testAddDeleteUserWithPrefereces() throws Exception { + String username = "user_for_test_prefereces"; + try { + userManager.addUser(createUser(username)); + Assertions.assertNull(this.userPreferencesManager.getUserPreferences(username)); + UserDetails user = new OAuth2TestUtils.UserBuilder(username, "0x24").grantedToRoleAdmin().build(); + String accessToken = mockOAuthInterceptor(user); + + mockMvc.perform( + get("/userPreferences/{username}", username) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.payload.wizard", Matchers.is(true))) + .andExpect(jsonPath("$.payload.loadOnPageSelect", Matchers.is(true))) + .andExpect(jsonPath("$.payload.translationWarning", Matchers.is(true))); + + Assertions.assertNotNull(this.userPreferencesManager.getUserPreferences(username)); + + ObjectMapper mapper = new ObjectMapper(); + UserPreferencesRequest request = new UserPreferencesRequest(); + request.setWizard(false); + String payload = mapper.writeValueAsString(request); + + mockMvc.perform( + put("/userPreferences/{username}", username) + .content(payload) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.payload.wizard", Matchers.is(false))); + + Assertions.assertNotNull(this.userPreferencesManager.getUserPreferences(username)); + userManager.removeUser(username); + Assertions.assertNull(this.userPreferencesManager.getUserPreferences(username)); + } finally { + this.userManager.removeUser(username); + this.userPreferencesManager.deleteUserPreferences(username); + } + } + @Test void testGetUsersPreferencesWithAdminPrivileges() throws Exception { String username = "user_with_admin_privileges"; - try { userManager.addUser(createUser(username)); UserDetails user = new OAuth2TestUtils.UserBuilder(username, "0x24").grantedToRoleAdmin().build(); String accessToken = mockOAuthInterceptor(user); - mockMvc.perform( get("/userPreferences/{username}", username) .contentType(MediaType.APPLICATION_JSON_VALUE) @@ -87,12 +128,10 @@ void testGetUsersPreferencesWithAdminPrivileges() throws Exception { @Test void testGetUsersPreferencesWithoutPrivileges() throws Exception { String username = "user_without_privileges"; - try { userManager.addUser(createUser(username)); UserDetails user = new OAuth2TestUtils.UserBuilder(username, "0x24").build(); String accessToken = mockOAuthInterceptor(user); - mockMvc.perform( get("/userPreferences/{username}", username) .contentType(MediaType.APPLICATION_JSON_VALUE) diff --git a/engine/src/test/java/org/entando/entando/web/userprofile/12_POST_valid.json b/engine/src/test/java/org/entando/entando/web/userprofile/12_POST_valid.json deleted file mode 100644 index 7f34fde71a..0000000000 --- a/engine/src/test/java/org/entando/entando/web/userprofile/12_POST_valid.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "id": "new_user_2", - "typeCode":"OTH", - "typeDescription":"Other user profile", - "description":"Profile of user with profilepicture", - "mainGroup":"free", - "groups":[], - "attributes": [ - { - "code": "firstname", - "value": "Eric" - },{ - "code": "surname", - "value": "Brown" - },{ - "code": "email", - "value": "eric.brown@entando.com" - },{ - "code": "profilepicture", - "value": "picture.png" - } - ] -} diff --git a/engine/src/test/java/org/entando/entando/web/userprofile/12_PUT_valid.json b/engine/src/test/java/org/entando/entando/web/userprofile/12_PUT_valid.json deleted file mode 100644 index e444d38177..0000000000 --- a/engine/src/test/java/org/entando/entando/web/userprofile/12_PUT_valid.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "new_user_2", - "typeCode": "OTH", - "typeDescription": "Type for test OTH", - "description": "Profile of user", - "mainGroup": "free", - "groups": [ - "group1", - "group2" - ], - "attributes": [ - { - "code": "profilepicture", - "value": "picture2.png" - } - ] -} diff --git a/engine/src/test/java/org/entando/entando/web/userprofile/ProfileTypeControllerIntegrationTest.java b/engine/src/test/java/org/entando/entando/web/userprofile/ProfileTypeControllerIntegrationTest.java index 84f612b530..326f81e77e 100644 --- a/engine/src/test/java/org/entando/entando/web/userprofile/ProfileTypeControllerIntegrationTest.java +++ b/engine/src/test/java/org/entando/entando/web/userprofile/ProfileTypeControllerIntegrationTest.java @@ -376,7 +376,7 @@ void testGetUserProfileAttributeType_1() throws Exception { result.andExpect(jsonPath("$.payload.code", is("Monotext"))); result.andExpect(jsonPath("$.payload.multilingual", is(false))); result.andExpect(jsonPath("$.payload.dateFilterSupported", is(false))); - result.andExpect(jsonPath("$.payload.allowedRoles", Matchers.hasSize(7))); + result.andExpect(jsonPath("$.payload.allowedRoles", Matchers.hasSize(6))); result.andExpect(jsonPath("$.payload.simple", is(true))); result.andExpect(jsonPath("$.errors", Matchers.hasSize(0))); result.andExpect(jsonPath("$.metaData.size()", is(0))); @@ -417,7 +417,7 @@ void testGetUserProfileAttributeType_3() throws Exception { result.andExpect(jsonPath("$.payload.assignedRoles.size()", is(2))); result.andExpect(jsonPath("$.payload.assignedRoles.userprofile:fullname", is("fullname"))); result.andExpect(jsonPath("$.payload.assignedRoles.userprofile:email", is("email"))); - result.andExpect(jsonPath("$.payload.allowedRoles", Matchers.hasSize(7))); + result.andExpect(jsonPath("$.payload.allowedRoles", Matchers.hasSize(6))); result.andExpect(jsonPath("$.payload.dateFilterSupported", is(false))); result.andExpect(jsonPath("$.payload.simple", is(true))); result.andExpect(jsonPath("$.errors", Matchers.hasSize(0))); diff --git a/engine/src/test/java/org/entando/entando/web/userprofile/UserProfileControllerIntegrationTest.java b/engine/src/test/java/org/entando/entando/web/userprofile/UserProfileControllerIntegrationTest.java index 4dd91d152d..aa157c6f81 100644 --- a/engine/src/test/java/org/entando/entando/web/userprofile/UserProfileControllerIntegrationTest.java +++ b/engine/src/test/java/org/entando/entando/web/userprofile/UserProfileControllerIntegrationTest.java @@ -16,7 +16,6 @@ import com.agiletec.aps.system.SystemConstants; import com.agiletec.aps.system.common.entity.IEntityTypesConfigurer; import com.agiletec.aps.system.common.entity.model.attribute.ListAttribute; -import com.agiletec.aps.system.common.entity.model.attribute.MonoTextAttribute; import com.agiletec.aps.system.services.group.Group; import com.agiletec.aps.system.services.role.Permission; import com.agiletec.aps.system.services.user.IUserManager; @@ -26,7 +25,6 @@ import com.agiletec.aps.util.FileTextReader; import org.entando.entando.aps.system.common.entity.model.attribute.EmailAttribute; import org.entando.entando.aps.system.services.userprofile.IUserProfileManager; -import org.entando.entando.aps.system.services.userprofile.IUserProfileService; import org.entando.entando.aps.system.services.userprofile.model.IUserProfile; import org.entando.entando.web.AbstractControllerIntegrationTest; import org.entando.entando.web.utils.OAuth2TestUtils; @@ -48,13 +46,31 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import com.agiletec.aps.system.common.entity.model.attribute.ITextAttribute; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.jayway.jsonpath.JsonPath; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.apache.commons.io.IOUtils; +import org.entando.entando.aps.system.services.storage.IStorageManager; +import org.entando.entando.aps.system.services.userpreferences.IUserPreferencesManager; +import org.entando.entando.aps.system.services.userpreferences.UserPreferences; +import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; +import org.hamcrest.CoreMatchers; +import org.springframework.core.io.ClassPathResource; + class UserProfileControllerIntegrationTest extends AbstractControllerIntegrationTest { @Autowired - private IUserProfileService userProfileService; + private IUserProfileManager userProfileManager; @Autowired - private IUserProfileManager userProfileManager; + private IStorageManager storageManager; + + @Autowired + private IUserPreferencesManager userPreferencesManager; @Autowired private IUserManager userManager; @@ -71,6 +87,28 @@ void testGetUserProfileType() throws Exception { result.andExpect(status().isOk()); testCors("/userProfiles/editorCoach"); } + + @Test + void testGetFullUserProfileType() throws Exception { + String accessToken = this.createAccessToken(); + ResultActions result = mockMvc + .perform(get("/userProfiles/{username}", new Object[]{"supervisorCoach"}) + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().isOk()); + String resultString = result.andReturn().getResponse().getContentAsString(); + List> attributes = JsonPath.read(resultString, "$.payload.attributes"); + Map> attributeMap = attributes.stream().collect(Collectors.toMap(e -> e.get("code").toString(), Function.identity())); + Assertions.assertTrue(Boolean.parseBoolean(attributeMap.get("Boolean").get("value").toString())); + Assertions.assertFalse(Boolean.parseBoolean(attributeMap.get("CheckBox").get("value").toString())); + Assertions.assertEquals("2010-03-21 00:00:00", attributeMap.get("Date").get("value")); + Assertions.assertEquals("2012-03-21 00:00:00", attributeMap.get("Date2").get("value")); + Assertions.assertEquals("a", attributeMap.get("Enumerator").get("value")); + Assertions.assertEquals("02", attributeMap.get("EnumeratorMap").get("value")); + Assertions.assertEquals("02", ((Map) attributeMap.get("EnumeratorMap").get("values")).get("mapKey")); + Assertions.assertEquals("Value 2", ((Map) attributeMap.get("EnumeratorMap").get("values")).get("mapValue")); + Assertions.assertEquals("

text Hypertext

", ((Map) attributeMap.get("Hypertext").get("values")).get("it")); + Assertions.assertNull(((Map) attributeMap.get("Hypertext").get("values")).get("en")); + } @Test void testGetUserProfileTypePermission() throws Exception { @@ -494,45 +532,145 @@ void testPostMyProfileOk() throws Exception { } } } + + @Test + void shouldAddDeleteUserWithAvatar() throws Exception { + String username = "user_with_avatar"; + try { + userManager.addUser(createUser(username)); + Assertions.assertNull(this.userPreferencesManager.getUserPreferences(username)); + String accessToken = this.createAccessToken(username); + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("myFile.png", + IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream()), false); + ResultActions result = mockMvc.perform( + post("/userProfiles/avatar") + .content(new ObjectMapper().writeValueAsString(profileAvatarRequest)) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.payload.filename").value(username + ".png")); + UserPreferences userPreferences = this.userPreferencesManager.getUserPreferences(username); + Assertions.assertNotNull(userPreferences); + Assertions.assertFalse(userPreferences.isGravatar()); + + Assertions.assertTrue(this.storageManager.exists("static/profile/" + username + ".png", false)); + + this.userManager.removeUser(username); + Assertions.assertNull(this.userPreferencesManager.getUserPreferences(username)); + Assertions.assertFalse(this.storageManager.exists("static/profile/" + username + ".png", false)); + } finally { + this.userPreferencesManager.deleteUserPreferences(username); + this.userManager.removeUser(username); + this.storageManager.deleteFile("static/profile/" + username + ".png", true); + } + } + private UserDetails createUser(String username) { + User user = new User(); + user.setUsername(username); + user.setDisabled(false); + user.setLastAccess(new Date()); + user.setLastPasswordChange(new Date()); + user.setMaxMonthsSinceLastAccess(2); + user.setMaxMonthsSinceLastPasswordChange(1); + user.setPassword("password"); + return user; + } + @Test - void testAddUserProfileWithProfilePicture() throws Exception { + void shouldPostFileAvatarReturn200OnRightInput() throws Exception { + UserPreferences userPreferences = this.userPreferencesManager.getUserPreferences("jack_bauer"); + Assertions.assertNull(userPreferences); try { String accessToken = this.createAccessToken(); - - this.executeProfilePost("12_POST_valid.json", accessToken, status().isOk()).andDo(resultPrint()) - .andExpect(jsonPath("$.payload.id", is("new_user_2"))) - .andExpect(jsonPath("$.errors.size()", is(0))) - .andExpect(jsonPath("$.metaData.size()", is(0))); - - IUserProfile profile = this.userProfileManager.getProfile("new_user_2"); - Assertions.assertNotNull(profile); - MonoTextAttribute profilePicture = (MonoTextAttribute) profile.getAttribute("profilepicture"); - Assertions.assertEquals("picture.png", profilePicture.getText()); - - executeProfileGet("new_user_2", accessToken, status().isOk()) - .andExpect(jsonPath("$.payload.id", is("new_user_2"))) - .andExpect(jsonPath("$.payload.typeCode", is("OTH"))) - .andExpect(jsonPath("$.payload.attributes[0].value", is("Eric"))) - .andExpect(jsonPath("$.payload.attributes[1].value", is("Brown"))) - .andExpect(jsonPath("$.payload.attributes[2].value", is("eric.brown@entando.com"))) - .andExpect(jsonPath("$.payload.attributes[3].value", is("picture.png"))); - - executeProfilePut("12_PUT_valid.json", "new_user_2", accessToken, status().isOk()) - .andExpect(jsonPath("$.payload.id", is("new_user_2"))) - .andExpect(jsonPath("$.payload.typeCode", is("OTH"))) - .andExpect(jsonPath("$.payload.attributes[0].value", is("Eric"))) - .andExpect(jsonPath("$.payload.attributes[1].value", is("Brown"))) - .andExpect(jsonPath("$.payload.attributes[2].value", is("eric.brown@entando.com"))) - .andExpect(jsonPath("$.payload.attributes[3].value", is("picture2.png"))); + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("myFile.png", + IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream()), false); + ResultActions result = mockMvc.perform( + post("/userProfiles/avatar") + .content(new ObjectMapper().writeValueAsString(profileAvatarRequest)) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.payload.filename").value("jack_bauer.png")); + userPreferences = this.userPreferencesManager.getUserPreferences("jack_bauer"); + Assertions.assertNotNull(userPreferences); + Assertions.assertFalse(userPreferences.isGravatar()); } finally { - this.userProfileManager.deleteProfile("new_user_2"); - Assertions.assertNull(this.userProfileManager.getProfile("new_user_2")); + this.userPreferencesManager.deleteUserPreferences("jack_bauer"); } } - + + @Test + void shouldPostDeleteGravatarReturn200() throws Exception { + UserPreferences userPreferences = this.userPreferencesManager.getUserPreferences("jack_bauer"); + Assertions.assertNull(userPreferences); + try { + IUserProfile profile = this.userProfileManager.getDefaultProfileType(); + ITextAttribute emailAttribute = (ITextAttribute) profile.getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_MAIL); + emailAttribute.setText("jack_bauer@jack_bauer.com", "it"); + profile.setId("jack_bauer"); + this.userProfileManager.addProfile("jack_bauer", profile); + String accessToken = this.createAccessToken(); + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest(null, null, true); + ResultActions resultPost = mockMvc.perform( + post("/userProfiles/avatar") + .content(new ObjectMapper().writeValueAsString(profileAvatarRequest)) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header("Authorization", "Bearer " + accessToken)); + resultPost.andExpect(status().isOk()) + .andExpect(jsonPath("$.payload.useGravatar").value(true)); + userPreferences = this.userPreferencesManager.getUserPreferences("jack_bauer"); + Assertions.assertNotNull(userPreferences); + Assertions.assertTrue(userPreferences.isGravatar()); + + ResultActions resultGet = mockMvc.perform( + get("/userProfiles/avatar") + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header("Authorization", "Bearer " + accessToken)); + resultGet.andExpect(status().isOk()) + .andExpect(jsonPath("$.payload.useGravatar").value("true")); + + ResultActions resultDelete = mockMvc.perform( + delete("/userProfiles/avatar") + .header("Authorization", "Bearer " + accessToken)); + resultDelete.andExpect(status().isOk()) + .andExpect(jsonPath("$.payload.username").value("jack_bauer")) + .andExpect(jsonPath("$.errors.size()", CoreMatchers.is(0))) + .andExpect(jsonPath("$.metaData.size()", CoreMatchers.is(0))); + userPreferences = this.userPreferencesManager.getUserPreferences("jack_bauer"); + Assertions.assertNotNull(userPreferences); + Assertions.assertFalse(userPreferences.isGravatar()); + } finally { + this.userPreferencesManager.deleteUserPreferences("jack_bauer"); + this.userProfileManager.deleteProfile("jack_bauer"); + } + } + + @Test + void shouldReturnErrorOnPostGravatarWithNullProfile() throws Exception { + try { + String accessToken = this.createAccessToken(); + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest(null, null, true); + ResultActions result = mockMvc.perform( + post("/userProfiles/avatar") + .content(new ObjectMapper().writeValueAsString(profileAvatarRequest)) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().isBadRequest()); + UserPreferences userPreferences = this.userPreferencesManager.getUserPreferences("jack_bauer"); + Assertions.assertNull(userPreferences); + } finally { + this.userPreferencesManager.deleteUserPreferences("jack_bauer"); + this.userProfileManager.deleteProfile("jack_bauer"); + } + } + private String createAccessToken() throws Exception { - UserDetails user = new OAuth2TestUtils.UserBuilder("jack_bauer", "0x24") + return this.createAccessToken("jack_bauer"); + } + + private String createAccessToken(String username) throws Exception { + UserDetails user = new OAuth2TestUtils.UserBuilder(username, "0x24") .withAuthorization(Group.FREE_GROUP_NAME, "manageUserProfile", Permission.MANAGE_USER_PROFILES) .build(); return mockOAuthInterceptor(user); @@ -604,5 +742,11 @@ private ResultActions executeProfileTypePost(String fileName, String accessToken result.andDo(resultPrint()).andExpect(expected); return result; } - + + + + + + + } diff --git a/engine/src/test/java/org/entando/entando/web/userprofile/UserProfileControllerTest.java b/engine/src/test/java/org/entando/entando/web/userprofile/UserProfileControllerTest.java index 373b58e3b9..088af0dc7a 100644 --- a/engine/src/test/java/org/entando/entando/web/userprofile/UserProfileControllerTest.java +++ b/engine/src/test/java/org/entando/entando/web/userprofile/UserProfileControllerTest.java @@ -15,29 +15,43 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.agiletec.aps.system.SystemConstants; import com.agiletec.aps.system.services.user.IUserManager; import com.agiletec.aps.system.services.user.UserDetails; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.stream.Stream; +import org.apache.commons.io.IOUtils; +import org.entando.entando.aps.system.exception.ResourceNotFoundException; import org.entando.entando.aps.system.services.entity.model.EntityDto; +import org.entando.entando.aps.system.services.userprofile.IAvatarService; import org.entando.entando.aps.system.services.userprofile.IUserProfileManager; import org.entando.entando.aps.system.services.userprofile.IUserProfileService; +import org.entando.entando.aps.system.services.userprofile.model.AvatarDto; import org.entando.entando.aps.system.services.userprofile.model.IUserProfile; import org.entando.entando.web.AbstractControllerTest; +import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; +import org.entando.entando.web.userprofile.validator.ProfileAvatarValidator; import org.entando.entando.web.userprofile.validator.ProfileValidator; import org.entando.entando.web.utils.OAuth2TestUtils; +import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; +import org.springframework.core.io.ClassPathResource; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -58,12 +72,17 @@ class UserProfileControllerTest extends AbstractControllerTest { @Mock private IUserProfileManager userProfileManager; - @InjectMocks - private ProfileController controller; + private ProfileAvatarValidator profileAvatarValidator; + + @Mock + private IAvatarService avatarService; @BeforeEach public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); + profileAvatarValidator = new ProfileAvatarValidator(userProfileManager); + ProfileController controller = new ProfileController(userProfileService, profileValidator, + profileAvatarValidator, userManager, + userProfileManager, avatarService); mockMvc = MockMvcBuilders.standaloneSetup(controller) .addInterceptors(entandoOauth2Interceptor) .setMessageConverters(getMessageConverters()) @@ -127,6 +146,137 @@ void testUpdateProfile() throws Exception { result.andExpect(status().isOk()); } + @Test + void shouldGetAvatarReturn404IfImageNotExists() throws Exception { + String accessToken = this.createAccessToken(); + // simulate an image not found + when(avatarService.getAvatarData(any())).thenThrow( + new ResourceNotFoundException("1", "image", "static/profile/jack_bauer.png")); + + ResultActions result = mockMvc.perform( + get("/userProfiles/avatar?fileName=myFile.png") + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().isNotFound()) + .andExpect(jsonPath("$.errors[0].message").value( + "a image with static/profile/jack_bauer.png code could not be found")); + } + + @Test + void shouldGetAvatarReturn200AndWellFormedResponseIfImageExists() throws Exception { + String accessToken = this.createAccessToken(); + AvatarDto avatarDto = AvatarDto.builder() + .base64(new byte[0]) + .protectedFolder(false) + .currentPath("static/profile/jack_bauer.png") + .filename("jack_bauer.png") + .prevPath("static/profile") + .build(); + when(avatarService.getAvatarData(any())).thenReturn(avatarDto); + + ResultActions result = mockMvc.perform( + get("/userProfiles/avatar?fileName=myFile.png") + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.payload.protectedFolder").value(avatarDto.isProtectedFolder())) + .andExpect(jsonPath("$.payload.isDirectory").value(false)) + .andExpect(jsonPath("$.payload.path").value(avatarDto.getCurrentPath())) + .andExpect(jsonPath("$.payload.filename").value(avatarDto.getFilename())) + .andExpect(jsonPath("$.payload.base64").value("")) + .andExpect(jsonPath("$.metaData.prevPath").value(avatarDto.getPrevPath())); + + } + + @Test + void shouldPostAvatarReturn400OnIllegalInput() throws Exception { + String accessToken = this.createAccessToken(); + Answer ans = invocation -> { + Object[] args = invocation.getArguments(); + ((BindingResult) args[1]).rejectValue("filename", "1", new String[]{"fileName_without_extension"}, + "fileBrowser.filename.invalidFilename"); + return null; + }; + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("fileName_without_extension", + new byte[1], false); + ResultActions result = mockMvc.perform( + post("/userProfiles/avatar") + .content(new ObjectMapper().writeValueAsString(profileAvatarRequest)) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().isBadRequest()); + } + + @Test + void shouldPostAvatarReturn200OnRightInput() throws Exception { + String accessToken = this.createAccessToken(); + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("myFile.png", + IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream()), false); + when(avatarService.updateAvatar(any(), any(), any())).thenReturn("jack_bauer.png"); + ResultActions result = mockMvc.perform( + post("/userProfiles/avatar") + .content(new ObjectMapper().writeValueAsString(profileAvatarRequest)) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.payload.filename").value("jack_bauer.png")); + } + + @Test + void shouldPostAvatarReturn400OnFileServiceAddFailureIfFileAlreadyPresent() throws Exception { + String accessToken = this.createAccessToken(); + Answer ans = invocation -> { + Object[] args = invocation.getArguments(); + ((BindingResult) args[2]).reject("2", new String[]{"static/profile/jack-bauer.png", "false"}, + "fileBrowser.file.exists"); + return null; + }; + Mockito.lenient().doAnswer(ans).when(avatarService).updateAvatar(any(), any(), any()); + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("image.png", + new byte[1], false); + ResultActions result = mockMvc.perform( + post("/userProfiles/avatar") + .content(new ObjectMapper().writeValueAsString(profileAvatarRequest)) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().isBadRequest()); + } + + + @ParameterizedTest + @MethodSource("provideValuesFor400") + void shouldPostAvatarReturn400(String request, String expectedErrorCode) throws Exception { + String accessToken = this.createAccessToken(); + ResultActions result = mockMvc.perform( + post("/userProfiles/avatar") + .content(request) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.errors[0].code").value(expectedErrorCode)); + } + + @Test + void shouldDeleteAvatarReturn200() throws Exception { + String accessToken = this.createAccessToken(); + ResultActions result = mockMvc.perform( + delete("/userProfiles/avatar") + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.payload.username").value("jack_bauer")) + .andExpect(jsonPath("$.errors.size()", CoreMatchers.is(0))) + .andExpect(jsonPath("$.metaData.size()", CoreMatchers.is(0))); + } + + private static Stream provideValuesFor400() { + return Stream.of( + Arguments.of("{\"filenam\":\"image.png\",\"base64\":\"AA==\"}", "52"), + Arguments.of("{\"base64\":\"AA==\"}", "52"), + Arguments.of("{\"filename\":\"\",\"base64\":\"AA==\"}", "52"), + Arguments.of("{\"filename\":\"image.png\",\"base6\":\"AA==\"}", "53"), + Arguments.of("{\"filename\":\"image.png\"}", "53"), + Arguments.of("{\"filename\":\"image.png\",\"base64\":\"\"}", "2") + ); + } + private ResultActions performGetUserProfiles(String username) throws Exception { String accessToken = this.createAccessToken(); return mockMvc.perform( @@ -157,4 +307,5 @@ private String createAccessToken() throws Exception { UserDetails user = new OAuth2TestUtils.UserBuilder("jack_bauer", "0x24").grantedToRoleAdmin().build(); return mockOAuthInterceptor(user); } + } diff --git a/engine/src/test/java/org/entando/entando/web/userprofile/validator/ProfileAvatarValidatorTest.java b/engine/src/test/java/org/entando/entando/web/userprofile/validator/ProfileAvatarValidatorTest.java new file mode 100644 index 0000000000..5b281291f9 --- /dev/null +++ b/engine/src/test/java/org/entando/entando/web/userprofile/validator/ProfileAvatarValidatorTest.java @@ -0,0 +1,140 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ +package org.entando.entando.web.userprofile.validator; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; + +import com.agiletec.aps.system.SystemConstants; +import com.agiletec.aps.system.services.user.UserDetails; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import javax.imageio.ImageIO; +import org.apache.commons.io.IOUtils; +import org.entando.entando.aps.system.services.userprofile.IUserProfileManager; +import org.entando.entando.aps.system.services.userprofile.model.IUserProfile; +import org.entando.entando.ent.exception.EntException; +import org.entando.entando.ent.exception.EntRuntimeException; +import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ClassPathResource; +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.validation.FieldError; + +@ExtendWith(MockitoExtension.class) +class ProfileAvatarValidatorTest { + + @Mock + private IUserProfileManager userProfileManager; + + @InjectMocks + private ProfileAvatarValidator profileAvatarValidator; + + @Test + void shouldSupportOnlyProfileAvatarRequest() { + assertTrue(profileAvatarValidator.supports(ProfileAvatarRequest.class)); + assertFalse(profileAvatarValidator.supports(Object.class)); + } + + @Test + void shouldNotValidateFileNamesMissingExtensions() throws IOException { + ProfileAvatarRequest request = new ProfileAvatarRequest("missing_extension_filename", + IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream()), false); + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); + profileAvatarValidator.validate(request, Mockito.mock(UserDetails.class), errors); + assertEquals(1, errors.getErrorCount()); + assertEquals("fileBrowser.filename.invalidFilename", errors.getAllErrors().get(0).getDefaultMessage()); + assertEquals("missing_extension_filename", ((FieldError) errors.getAllErrors().get(0)).getRejectedValue()); + } + + @Test + void shouldNotValidateUserInCaseOfMissingProfile() throws Exception { + ProfileAvatarRequest request = new ProfileAvatarRequest(null, null, true); + UserDetails user = Mockito.mock(UserDetails.class); + Mockito.when(user.getUsername()).thenReturn("username_test"); + Mockito.when(userProfileManager.getProfile("username_test")).thenReturn(null); + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); + profileAvatarValidator.validate(request, user, errors); + assertEquals(1, errors.getErrorCount()); + assertEquals("avatar.emailAttribute.missing", errors.getAllErrors().get(0).getDefaultMessage()); + assertEquals("useGravatar", ((FieldError) errors.getAllErrors().get(0)).getField()); + } + + @Test + void shouldNotValidateUserInCaseOfMissingEmailAttribute() throws Exception { + ProfileAvatarRequest request = new ProfileAvatarRequest(null, null, true); + UserDetails user = Mockito.mock(UserDetails.class); + Mockito.when(user.getUsername()).thenReturn("username_test"); + IUserProfile userProfile = Mockito.mock(IUserProfile.class); + Mockito.when(userProfileManager.getProfile("username_test")).thenReturn(userProfile); + Mockito.when(userProfile.getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_MAIL)).thenReturn(null); + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); + profileAvatarValidator.validate(request, user, errors); + assertEquals(1, errors.getErrorCount()); + assertEquals("avatar.emailAttribute.missing", errors.getAllErrors().get(0).getDefaultMessage()); + assertEquals("useGravatar", ((FieldError) errors.getAllErrors().get(0)).getField()); + } + + @Test + void shouldThrowServerErrorInCaseOfErrorGettingUserProfile() throws Exception { + ProfileAvatarRequest request = new ProfileAvatarRequest(null, null, true); + UserDetails user = Mockito.mock(UserDetails.class); + Mockito.when(user.getUsername()).thenReturn("username_test"); + Mockito.when(userProfileManager.getProfile("username_test")).thenThrow(EntException.class); + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); + assertThrows(EntRuntimeException.class, () -> profileAvatarValidator.validate(request, user, errors)); + } + + @Test + void shouldNotValidateFilesOtherThanImages() { + String notValidBase64Image = "bm90IGFuIGltYWdl"; + ProfileAvatarRequest request = new ProfileAvatarRequest("valid_filename.txt", notValidBase64Image.getBytes(), false); + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); + profileAvatarValidator.validate(request, Mockito.mock(UserDetails.class), errors); + assertEquals(1, errors.getErrorCount()); + assertEquals("fileBrowser.file.invalidType", errors.getAllErrors().get(0).getDefaultMessage()); + assertEquals("base64", ((FieldError) errors.getAllErrors().get(0)).getField()); + } + + @Test + void shouldThrowUncheckedIOExceptionIfImageReadingFails() throws IOException { + ProfileAvatarRequest request = new ProfileAvatarRequest("image.png", + IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream()), false); + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); + try (MockedStatic mockStatic = Mockito.mockStatic(ImageIO.class)) { + mockStatic.when(() -> ImageIO.read(any(InputStream.class))).thenThrow(IOException.class); + assertThrows(UncheckedIOException.class, () -> profileAvatarValidator.validate(request, Mockito.mock(UserDetails.class), errors)); + } + } + + @Test + void shouldValidateAcceptValidImageWithValidFileName() throws IOException { + ProfileAvatarRequest request = new ProfileAvatarRequest("image.png", + IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream()), false); + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); + profileAvatarValidator.validate(request, Mockito.mock(UserDetails.class), errors); + assertTrue(errors.getAllErrors().isEmpty()); + } + +} diff --git a/engine/src/test/resources/userprofile/image.png b/engine/src/test/resources/userprofile/image.png new file mode 100644 index 0000000000..81745f4bb1 Binary files /dev/null and b/engine/src/test/resources/userprofile/image.png differ diff --git a/keycloak-plugin/core/pom.xml b/keycloak-plugin/core/pom.xml index 198ef12e9e..2c02fd49b4 100644 --- a/keycloak-plugin/core/pom.xml +++ b/keycloak-plugin/core/pom.xml @@ -83,73 +83,4 @@ - - - - org.apache.maven.plugins - maven-surefire-plugin - - - org.apache.maven.plugins - maven-failsafe-plugin - 3.0.0-M3 - - ${skipIntegrationTests} - 1 - - - - - integration-test - verify - - - - - - org.jacoco - jacoco-maven-plugin - 0.8.3 - - - **/KeycloakService.* - - - - - prepare-agent - - prepare-agent - - - - report - report - - - - - org.apache.maven.plugins - maven-source-plugin - 3.0.1 - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.0 - - ${maven.compiler.source} - ${maven.compiler.target} - - - - \ No newline at end of file diff --git a/keycloak-plugin/pom.xml b/keycloak-plugin/pom.xml index ca9321dacf..424ea1a107 100644 --- a/keycloak-plugin/pom.xml +++ b/keycloak-plugin/pom.xml @@ -5,7 +5,7 @@ app-engine org.entando - 7.3.0 + 7.3.1 org.entando.entando entando-keycloak-auth diff --git a/mail-plugin/pom.xml b/mail-plugin/pom.xml index aa15fd8615..4b5a387979 100644 --- a/mail-plugin/pom.xml +++ b/mail-plugin/pom.xml @@ -4,7 +4,7 @@ org.entando app-engine - 7.3.0 + 7.3.1 entando-plugin-jpmail org.entando.entando.plugins diff --git a/pom.xml b/pom.xml index 87e703be0f..18140194c8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,12 +5,12 @@ org.entando entando-maven-root - 7.2.0 + 7.3.1-ENG-5347-PR-22 org.entando app-engine - 7.3.0 + 7.3.1 pom @@ -64,13 +64,14 @@ HEAD - 11 - 11 + 17 + 17 true **/*.js + true org.apache.derby.jdbc.EmbeddedDriver localhost @@ -79,16 +80,14 @@ agile jdbc:derby:memory:testPort;create=true jdbc:derby:memory:testServ;create=true - 11 - 11 - 5.3.27 - 5.5.7 + 5.3.33 + 5.7.12 2.5.2.RELEASE - 2.5.31 + 2.5.33 2.12.7 2.12.7.1 - 6.0.20.Final + 6.2.0.Final 8.9.0 2.6.0 2.10.5 @@ -106,15 +105,15 @@ 1.2.5 3.20.2 2.1 - 1.2.7 + 1.2.13 0.1.5 0.1.5 1.9.7 4.40 - 4.4.3 + 4.8.0 1.7.31 2.20.0 - 1.4.1 + 1.5.4 4.5.13 3.0.1 3.0.1 @@ -131,6 +130,7 @@ 3.11.2 5.7.2 1.17.6 + 2.1.3 4.0.1 1.3.2 1.1.3 @@ -154,7 +154,7 @@ 0.8 2.18.0 4.4 - 1.18.20 + 1.18.30 3.3.0 3.0.0-M4 2.5 @@ -167,12 +167,12 @@ 2.0.1 2.3.1 1.1.1 - 1.21 + 1.26.0 2.1.9.RELEASE 2.7.0 - 6.1.5.RELEASE + 6.2.5.RELEASE 1.5.20 - 2.6.0 + 2.9.0 2.3.2 2.15.0.1 2.1.6 @@ -191,9 +191,9 @@ 3.19.6 2.12.2 7.4.1 - 4.1.77.Final + 4.1.94.Final 1.5 - 8.11.1 + 9.4.0 1.5.5 @@ -1014,6 +1014,12 @@ ${hamcrest-all.version} test
+ + uk.org.webcompere + system-stubs-jupiter + ${system-stubs-jupiter.version} + test + org.apache.tomcat tomcat-servlet-api diff --git a/portal-ui/pom.xml b/portal-ui/pom.xml index db604591fc..01106a4a3f 100644 --- a/portal-ui/pom.xml +++ b/portal-ui/pom.xml @@ -4,7 +4,7 @@ org.entando app-engine - 7.3.0 + 7.3.1 org.entando.entando entando-portal-ui diff --git a/portal-ui/src/main/java/com/agiletec/aps/system/services/controller/control/AbstractControlService.java b/portal-ui/src/main/java/com/agiletec/aps/system/services/controller/control/AbstractControlService.java index 5690e02be9..8f10450ec3 100644 --- a/portal-ui/src/main/java/com/agiletec/aps/system/services/controller/control/AbstractControlService.java +++ b/portal-ui/src/main/java/com/agiletec/aps/system/services/controller/control/AbstractControlService.java @@ -39,6 +39,11 @@ public abstract class AbstractControlService implements ControlServiceInterface private transient IURLManager urlManager; private transient IPageManager pageManager; + + @Override + public void afterPropertiesSet() throws Exception { + _logger.debug("{} ready", this.getClass().getName()); + } /** * Imposta i parametri di una redirezione. diff --git a/portal-ui/src/main/java/com/agiletec/aps/system/services/controller/control/Authenticator.java b/portal-ui/src/main/java/com/agiletec/aps/system/services/controller/control/Authenticator.java index 8b062a3167..9b6aff776c 100644 --- a/portal-ui/src/main/java/com/agiletec/aps/system/services/controller/control/Authenticator.java +++ b/portal-ui/src/main/java/com/agiletec/aps/system/services/controller/control/Authenticator.java @@ -38,12 +38,6 @@ public class Authenticator extends AbstractControlService { private static final Logger _logger = LoggerFactory.getLogger(Authenticator.class); private final transient JavaSecS5145 logSanitizer = new JavaSecS5145<>(); - - - @Override - public void afterPropertiesSet() throws Exception { - _logger.debug("{} ready", this.getClass().getName()); - } /** * Esecuzione. diff --git a/portal-ui/src/main/java/com/agiletec/aps/system/services/controller/control/ErrorManager.java b/portal-ui/src/main/java/com/agiletec/aps/system/services/controller/control/ErrorManager.java index a87bf38486..5098c435e0 100644 --- a/portal-ui/src/main/java/com/agiletec/aps/system/services/controller/control/ErrorManager.java +++ b/portal-ui/src/main/java/com/agiletec/aps/system/services/controller/control/ErrorManager.java @@ -31,11 +31,6 @@ public class ErrorManager extends AbstractControlService { private static final Logger _logger = LoggerFactory.getLogger(ErrorManager.class); - @Override - public void afterPropertiesSet() throws Exception { - _logger.debug("{} : initialized", this.getClass().getName()); - } - @Override public int service(RequestContext reqCtx, int status) { if (status == ControllerManager.CONTINUE || status == ControllerManager.OUTPUT) { diff --git a/portal-ui/src/main/java/com/agiletec/aps/system/services/controller/control/RequestAuthorizator.java b/portal-ui/src/main/java/com/agiletec/aps/system/services/controller/control/RequestAuthorizator.java index 4e5ae510b0..c1c4fd6d89 100644 --- a/portal-ui/src/main/java/com/agiletec/aps/system/services/controller/control/RequestAuthorizator.java +++ b/portal-ui/src/main/java/com/agiletec/aps/system/services/controller/control/RequestAuthorizator.java @@ -44,11 +44,6 @@ public class RequestAuthorizator extends AbstractControlService { private static final Logger _logger = LoggerFactory.getLogger(RequestAuthorizator.class); - @Override - public void afterPropertiesSet() throws Exception { - _logger.debug("{} : initialized", this.getClass().getName()); - } - /** * Verifica che l'utente in sessione sia abilitato all'accesso alla pagina richiesta. * Se è autorizzato il metodo termina con CONTINUE, altrimenti diff --git a/portal-ui/src/main/java/com/agiletec/aps/system/services/controller/control/RequestValidator.java b/portal-ui/src/main/java/com/agiletec/aps/system/services/controller/control/RequestValidator.java index a8c97b473b..bc439aa342 100644 --- a/portal-ui/src/main/java/com/agiletec/aps/system/services/controller/control/RequestValidator.java +++ b/portal-ui/src/main/java/com/agiletec/aps/system/services/controller/control/RequestValidator.java @@ -50,43 +50,30 @@ public class RequestValidator extends AbstractControlService { private static final Logger _logger = LoggerFactory.getLogger(RequestValidator.class); - @Override - public void afterPropertiesSet() throws Exception { - _logger.debug("{} ready", this.getClass().getName()); - } - - /** - * Esecuzione. Le operazioni sono descritte nella documentazione della - * classe. - * - * @param reqCtx Il contesto di richiesta - * @param status Lo stato di uscita del servizio precedente - * @return Lo stato di uscita - */ @Override public int service(RequestContext reqCtx, int status) { _logger.debug("{} invoked", this.getClass().getName()); int retStatus = ControllerManager.INVALID_STATUS; - // Se si è verificato un errore in un altro sottoservizio, termina - // subito if (status == ControllerManager.ERROR) { return status; } - try { // non devono essere rilanciate eccezioni + try { boolean ok = this.isRightPath(reqCtx); - if (ok) { - if (null == reqCtx.getExtraParam(SystemConstants.EXTRAPAR_CURRENT_PAGE)) { - IPage page = this.getPageManager().getOnlinePage(this.getNotFoundPageCode()); - reqCtx.addExtraParam(SystemConstants.EXTRAPAR_CURRENT_PAGE, page); - } - if (null == reqCtx.getExtraParam(SystemConstants.EXTRAPAR_CURRENT_PAGE) - || null == reqCtx.getExtraParam(SystemConstants.EXTRAPAR_CURRENT_LANG)) { - retStatus = this.redirect(this.getErrorPageCode(), reqCtx); - } else { - retStatus = ControllerManager.CONTINUE; - } - } else { + Runnable addNotFoundPageParam = () -> { + IPage page = this.getPageManager().getOnlinePage(this.getNotFoundPageCode()); + reqCtx.addExtraParam(SystemConstants.EXTRAPAR_CURRENT_PAGE, page); + }; + if (!ok || null == reqCtx.getExtraParam(SystemConstants.EXTRAPAR_CURRENT_LANG)) { + addNotFoundPageParam.run(); + reqCtx.addExtraParam(SystemConstants.EXTRAPAR_CURRENT_LANG, this.getLangManager().getDefaultLang()); + } else if (null == reqCtx.getExtraParam(SystemConstants.EXTRAPAR_CURRENT_PAGE)) { + addNotFoundPageParam.run(); + } + if (null == reqCtx.getExtraParam(SystemConstants.EXTRAPAR_CURRENT_PAGE) + || null == reqCtx.getExtraParam(SystemConstants.EXTRAPAR_CURRENT_LANG)) { retStatus = this.redirect(this.getErrorPageCode(), reqCtx); + } else { + retStatus = ControllerManager.CONTINUE; } } catch (Throwable t) { retStatus = ControllerManager.SYS_ERROR; diff --git a/portal-ui/src/main/webapp/404.jsp b/portal-ui/src/main/webapp/404.jsp new file mode 100644 index 0000000000..26cbcb24ba --- /dev/null +++ b/portal-ui/src/main/webapp/404.jsp @@ -0,0 +1,4 @@ +<%@ taglib uri="/aps-core" prefix="wp" %> + + + diff --git a/portal-ui/src/main/webapp/META-INF/context.xml b/portal-ui/src/main/webapp/META-INF/context.xml deleted file mode 100644 index a12e6dd701..0000000000 --- a/portal-ui/src/main/webapp/META-INF/context.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/portal-ui/src/test/java/org/entando/entando/aps/system/services/controller/control/TestRequestValidator.java b/portal-ui/src/test/java/org/entando/entando/aps/system/services/controller/control/TestRequestValidator.java index 0f1ba2da06..f79601709e 100644 --- a/portal-ui/src/test/java/org/entando/entando/aps/system/services/controller/control/TestRequestValidator.java +++ b/portal-ui/src/test/java/org/entando/entando/aps/system/services/controller/control/TestRequestValidator.java @@ -102,7 +102,7 @@ void testServiceWithNewPage(String langCode, String pageCode, boolean breadcrumb } @Test - void testServiceFailure_1() throws EntException { + void testServiceFailureWhenRequestPageThatDoesNotExist() throws EntException { String notFoundPageCode = this.pageManager.getConfig(IPageManager.CONFIG_PARAM_NOT_FOUND_PAGE_CODE); Map paramsToUpgrade = new HashMap<>(); try { @@ -130,23 +130,25 @@ void testServiceFailure_1() throws EntException { } @Test - void testServiceFailure_2() throws EntException { + void testServiceFailureWhenRequestAWrongPath() throws EntException { + String notFoundPageCode = this.pageManager.getConfig(IPageManager.CONFIG_PARAM_NOT_FOUND_PAGE_CODE); RequestContext reqCtx = this.getRequestContext(); ((MockHttpServletRequest) reqCtx.getRequest()).setServletPath("/wrongpath.wp");//wrong path int status = this.requestValidator.service(reqCtx, ControllerManager.CONTINUE); - assertEquals(ControllerManager.REDIRECT, status); - String redirectUrl = (String) reqCtx.getExtraParam(RequestContext.EXTRAPAR_REDIRECT_URL); - assertEquals("http://www.entando.com/Entando/it/errorpage.page?redirectflag=1", redirectUrl); + IPage page = (IPage) reqCtx.getExtraParam(SystemConstants.EXTRAPAR_CURRENT_PAGE); + assertEquals(notFoundPageCode, page.getCode()); + assertEquals(ControllerManager.CONTINUE, status); } @Test - void testServiceFailure_3() throws EntException { + void testServiceFailureWhenRequestLangThatDoesNotExist() throws EntException { + String notFoundPageCode = this.pageManager.getConfig(IPageManager.CONFIG_PARAM_NOT_FOUND_PAGE_CODE); RequestContext reqCtx = this.getRequestContext(); ((MockHttpServletRequest) reqCtx.getRequest()).setServletPath("/cc/homepage.wp");//lang does not exist int status = this.requestValidator.service(reqCtx, ControllerManager.CONTINUE); - assertEquals(ControllerManager.REDIRECT, status); - String redirectUrl = (String) reqCtx.getExtraParam(RequestContext.EXTRAPAR_REDIRECT_URL); - assertEquals("http://www.entando.com/Entando/it/errorpage.page?redirectflag=1", redirectUrl); + IPage page = (IPage) reqCtx.getExtraParam(SystemConstants.EXTRAPAR_CURRENT_PAGE); + assertEquals(notFoundPageCode, page.getCode()); + assertEquals(ControllerManager.CONTINUE, status); } @BeforeEach @@ -155,6 +157,9 @@ void init() throws Exception { this.requestValidator = (ControlServiceInterface) this.getApplicationContext().getBean("RequestValidatorControlService"); this.pageManager = this.getApplicationContext().getBean(SystemConstants.PAGE_MANAGER, IPageManager.class); this.pageModelManager = this.getApplicationContext().getBean(IPageModelManager.class); + RequestContext reqCtx = this.getRequestContext(); + reqCtx.removeExtraParam(SystemConstants.EXTRAPAR_CURRENT_PAGE); + reqCtx.removeExtraParam(SystemConstants.EXTRAPAR_CURRENT_LANG); } catch (Throwable e) { throw new Exception(e); } diff --git a/redis-plugin/pom.xml b/redis-plugin/pom.xml index e8eb4441ae..698b21a58a 100644 --- a/redis-plugin/pom.xml +++ b/redis-plugin/pom.xml @@ -5,7 +5,7 @@ org.entando app-engine - 7.3.0 + 7.3.1 entando-plugin-jpredis org.entando.entando.plugins @@ -103,10 +103,6 @@ - - org.jacoco - jacoco-maven-plugin - org.apache.maven.plugins maven-surefire-plugin diff --git a/seo-plugin/README.md b/seo-plugin/README.md index a2ae27761a..bc1c641d05 100644 --- a/seo-plugin/README.md +++ b/seo-plugin/README.md @@ -15,19 +15,6 @@ entando-plugin-jpseo The SEO plugin enables some functionality inside the page configuration (new parameters and friendly url), in the content handling (new role attribute to pilot friendly url) and to extract the sitemap. -## Installation - -In order to install the SEO plugin, you must insert the following dependency in the pom.xml file of your project: - -``` - - org.entando.entando.plugins - entando-plugin-jpseo - ${entando.version} - war - -``` - ## How to use ###### Modify of web.xml diff --git a/seo-plugin/pom.xml b/seo-plugin/pom.xml index bdcc29ba4a..59146b9208 100644 --- a/seo-plugin/pom.xml +++ b/seo-plugin/pom.xml @@ -4,7 +4,7 @@ org.entando app-engine - 7.3.0 + 7.3.1 entando-plugin-jpseo org.entando.entando.plugins diff --git a/solr-plugin/docker-compose.yaml b/solr-plugin/docker-compose.yaml deleted file mode 100644 index fc51119812..0000000000 --- a/solr-plugin/docker-compose.yaml +++ /dev/null @@ -1,11 +0,0 @@ -version: '2.1' - -services: - solr: - container_name: solr_test - image: "solr:latest" - ports: - - "8984:8983" - command: - - solr-precreate - - entando diff --git a/solr-plugin/pom.xml b/solr-plugin/pom.xml index 768f2fd40c..471b2c058b 100644 --- a/solr-plugin/pom.xml +++ b/solr-plugin/pom.xml @@ -5,7 +5,7 @@ org.entando app-engine - 7.3.0 + 7.3.1 entando-plugin-jpsolr org.entando.entando.plugins @@ -80,6 +80,8 @@ org.entando.entando.plugins entando-plugin-jacms ${project.version} + classes + provided org.entando.entando @@ -156,6 +158,11 @@ mockito-junit-jupiter test + + uk.org.webcompere + system-stubs-jupiter + test + org.springframework spring-test diff --git a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/ISolrIndexStatus.java b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/ISolrIndexStatus.java index ab79337d1d..708463d8b4 100644 --- a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/ISolrIndexStatus.java +++ b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/ISolrIndexStatus.java @@ -1,3 +1,16 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ package org.entando.entando.plugins.jpsolr.aps.system.solr; public interface ISolrIndexStatus { diff --git a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/ISolrIndexerDAO.java b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/ISolrIndexerDAO.java index 0372dda340..831bd9ed5d 100644 --- a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/ISolrIndexerDAO.java +++ b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/ISolrIndexerDAO.java @@ -1,3 +1,16 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ package org.entando.entando.plugins.jpsolr.aps.system.solr; import com.agiletec.aps.system.common.entity.model.IApsEntity; diff --git a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/ISolrProxyTenantAware.java b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/ISolrProxyTenantAware.java index 296a46da16..48869e5a96 100644 --- a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/ISolrProxyTenantAware.java +++ b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/ISolrProxyTenantAware.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-Present Entando Inc. (http://www.entando.com) All rights reserved. + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free diff --git a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/ISolrResourcesAccessor.java b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/ISolrResourcesAccessor.java index 1cc9944717..439ad0b057 100644 --- a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/ISolrResourcesAccessor.java +++ b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/ISolrResourcesAccessor.java @@ -1,3 +1,16 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ package org.entando.entando.plugins.jpsolr.aps.system.solr; /** diff --git a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/ISolrSchemaDAO.java b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/ISolrSchemaDAO.java index c4f18cf068..6ef04653b0 100644 --- a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/ISolrSchemaDAO.java +++ b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/ISolrSchemaDAO.java @@ -1,3 +1,16 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ package org.entando.entando.plugins.jpsolr.aps.system.solr; import java.util.List; diff --git a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SearchEngineManagerFactory.java b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SearchEngineManagerFactory.java index 32db852669..cf0db92f4c 100644 --- a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SearchEngineManagerFactory.java +++ b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SearchEngineManagerFactory.java @@ -1,3 +1,16 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ package org.entando.entando.plugins.jpsolr.aps.system.solr; import com.agiletec.aps.system.common.notify.INotifyManager; @@ -11,9 +24,9 @@ import com.agiletec.plugins.jacms.aps.system.services.searchengine.SearchEngineManager; import lombok.Setter; import org.apache.http.impl.client.HttpClientBuilder; -import org.entando.entando.aps.system.services.cache.ICacheInfoManager; import org.entando.entando.aps.system.services.searchengine.SolrEnvironmentVariables; import org.entando.entando.aps.system.services.tenants.ITenantManager; +import org.entando.entando.plugins.jpsolr.aps.system.solr.cache.ISolrSearchEngineCacheWrapper; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; @@ -33,7 +46,7 @@ public class SearchEngineManagerFactory implements BeanFactoryAware { @Setter private ITenantManager tenantManager; @Setter - private ICacheInfoManager cacheInfoManager; + private ISolrSearchEngineCacheWrapper cacheWrapper; @Setter private HttpClientBuilder solrHttpClientBuilder; @Setter @@ -51,7 +64,7 @@ public ICmsSearchEngineManager createSearchEngineManager() throws Exception { solrSearchEngineManager.setSolrProxy(solrProxy); solrSearchEngineManager.setContentManager(contentManager); solrSearchEngineManager.setLangManager(langManager); - solrSearchEngineManager.setCacheInfoManager(cacheInfoManager); + solrSearchEngineManager.setCacheWrapper(cacheWrapper); solrSearchEngineManager.setNotifyManager(notifyManager); solrSearchEngineManager.setBeanFactory(beanFactory); solrSearchEngineManager.setBeanName(JacmsSystemConstants.SEARCH_ENGINE_MANAGER); diff --git a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrFieldsChecker.java b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrFieldsChecker.java index 976c744488..9437e8fe3f 100644 --- a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrFieldsChecker.java +++ b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrFieldsChecker.java @@ -1,3 +1,16 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ package org.entando.entando.plugins.jpsolr.aps.system.solr; import static org.entando.entando.plugins.jpsolr.aps.system.solr.model.SolrFields.SOLR_FIELD_MULTIVALUED; @@ -14,6 +27,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import lombok.extern.slf4j.Slf4j; import org.entando.entando.plugins.jpsolr.aps.system.solr.model.SolrFields; @@ -89,7 +103,9 @@ private void checkLangFields() { private void checkAttribute(AttributeInterface attribute, Lang lang) { attribute.setRenderingLang(lang.getCode()); if (attribute instanceof IndexableAttributeInterface - || ((attribute instanceof DateAttribute || attribute instanceof NumberAttribute) + || ((attribute instanceof DateAttribute + || attribute instanceof NumberAttribute + || attribute instanceof BooleanAttribute) && attribute.isSearchable())) { String type; if (attribute instanceof DateAttribute) { @@ -124,7 +140,6 @@ private void checkField(String fieldName, String type, boolean multiValue) { newField.put(SOLR_FIELD_NAME, fieldName); newField.put(SOLR_FIELD_TYPE, type); newField.put(SOLR_FIELD_MULTIVALUED, multiValue); - fields.stream() .filter(f -> f.get(SOLR_FIELD_NAME).equals(fieldName)) .findFirst().ifPresentOrElse( @@ -134,20 +149,18 @@ private void checkField(String fieldName, String type, boolean multiValue) { private void replaceField(Map currentField, Map newField, String fieldName, String type, boolean multiValue) { + Boolean cfmv = Optional.ofNullable(currentField.get(SOLR_FIELD_MULTIVALUED)).map(o -> Boolean.valueOf(o.toString())).orElse(null); if (currentField.get(SOLR_FIELD_TYPE).equals(type) - && ((null == currentField.get(SOLR_FIELD_MULTIVALUED) && multiValue) || ( - null != currentField.get(SOLR_FIELD_MULTIVALUED) && currentField.get(SOLR_FIELD_MULTIVALUED) - .equals(multiValue)))) { + && ((null == cfmv && multiValue) || (null != cfmv && Boolean.valueOf(multiValue).equals(cfmv)))) { return; } else { log.warn( "Field '{}' already exists but with different configuration! - type '{}' to '{}' - multiValued '{}' to '{}'", - fieldName, currentField.get(SOLR_FIELD_TYPE), type, currentField.get(SOLR_FIELD_MULTIVALUED), - multiValue); + fieldName, currentField.get(SOLR_FIELD_TYPE), type, cfmv, multiValue); } - if (!type.equals(currentField.get(SOLR_FIELD_TYPE)) || !Boolean.valueOf(multiValue) - .equals(currentField.get(SOLR_FIELD_MULTIVALUED))) { + if (!type.equals(currentField.get(SOLR_FIELD_TYPE)) || !Boolean.valueOf(multiValue).equals(cfmv)) { schemaUpdateRequest.replaceField(fieldName, newField); } } + } diff --git a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrHttpClientBuilderConfiguration.java b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrHttpClientBuilderConfiguration.java index ed5f295751..6c254739e4 100644 --- a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrHttpClientBuilderConfiguration.java +++ b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrHttpClientBuilderConfiguration.java @@ -1,3 +1,16 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ package org.entando.entando.plugins.jpsolr.aps.system.solr; import org.apache.http.client.config.RequestConfig; @@ -21,7 +34,7 @@ public class SolrHttpClientBuilderConfiguration { @Value("${SOLR_CONN_SOCKET_TIMEOUT_MS:25000}") private int connSocketTimeout; - @Bean("solrHttpClientBuilder") + @Bean("jpsolrSolrHttpClientBuilder") public HttpClientBuilder solrHttpClientBuilder() { return HttpClients.custom() .setMaxConnPerRoute(maxConnPerRoute) diff --git a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrIndexStatus.java b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrIndexStatus.java index 6ae2a25b68..e508f75333 100644 --- a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrIndexStatus.java +++ b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrIndexStatus.java @@ -1,3 +1,16 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ package org.entando.entando.plugins.jpsolr.aps.system.solr; import static com.agiletec.plugins.jacms.aps.system.services.searchengine.ICmsSearchEngineManager.STATUS_NEED_TO_RELOAD_INDEXES; diff --git a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrResourcesAccessor.java b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrResourcesAccessor.java index 9d459b2d17..91774f5af3 100644 --- a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrResourcesAccessor.java +++ b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrResourcesAccessor.java @@ -1,3 +1,16 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ package org.entando.entando.plugins.jpsolr.aps.system.solr; import com.agiletec.aps.system.services.category.ICategoryManager; @@ -7,7 +20,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.solr.client.solrj.SolrClient; -import org.apache.solr.client.solrj.impl.HttpSolrClient; +import org.apache.solr.client.solrj.impl.Http2SolrClient; @Slf4j public class SolrResourcesAccessor implements ISolrResourcesAccessor { @@ -30,9 +43,7 @@ public SolrResourcesAccessor(String solrAddress, String solrCore, ILangManager l ICategoryManager categoryManager, HttpClientBuilder httpClientBuilder) { log.debug("Creating Solr resources for {}", solrCore); this.solrCore = solrCore; - this.solrClient = new HttpSolrClient.Builder(solrAddress) - .withHttpClient(httpClientBuilder.build()) - .build(); + this.solrClient = new Http2SolrClient.Builder(solrAddress).build(); this.indexerDAO = new IndexerDAO(solrClient, solrCore); this.indexerDAO.setLangManager(langManager); diff --git a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSchemaDAO.java b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSchemaDAO.java index a1e5c4e3c9..0368b81586 100644 --- a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSchemaDAO.java +++ b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSchemaDAO.java @@ -1,9 +1,26 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ package org.entando.entando.plugins.jpsolr.aps.system.solr; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrServerException; @@ -11,6 +28,7 @@ import org.apache.solr.client.solrj.request.schema.SchemaRequest.MultiUpdate; import org.apache.solr.client.solrj.request.schema.SchemaRequest.Update; import org.apache.solr.common.util.SimpleOrderedMap; +import org.entando.entando.plugins.jpsolr.aps.system.solr.model.SolrFields; @Slf4j public class SolrSchemaDAO implements ISolrSchemaDAO { @@ -28,8 +46,7 @@ public SolrSchemaDAO(SolrClient solrClient, String solrCore) { SchemaRequest.Fields getFieldsRequest = new SchemaRequest.Fields(); List> fields = new ArrayList<>(); try { - List> items = (List>) - solrClient.request(getFieldsRequest, this.solrCore).get("fields"); + List> items = (List>) solrClient.request(getFieldsRequest, this.solrCore).get("fields"); for (SimpleOrderedMap item : items) { fields.add(item.asMap()); } @@ -39,7 +56,8 @@ public SolrSchemaDAO(SolrClient solrClient, String solrCore) { return fields; } - public boolean updateFields(List> fieldsToAdd, List> fieldsToReplace) { + @Override + public synchronized boolean updateFields(List> fieldsToAdd, List> fieldsToReplace) { try { List updates = new ArrayList<>(); for (Map fieldToAdd : fieldsToAdd) { @@ -47,16 +65,45 @@ public boolean updateFields(List> fieldsToAdd, List fieldToReplace : fieldsToReplace) { - SchemaRequest.ReplaceField replaceFieldRequest = - new SchemaRequest.ReplaceField((Map) fieldToReplace); + SchemaRequest.ReplaceField replaceFieldRequest + = new SchemaRequest.ReplaceField((Map) fieldToReplace); updates.add(replaceFieldRequest); } SchemaRequest.MultiUpdate multiUpdateRequest = new MultiUpdate(updates); solrClient.request(multiUpdateRequest, this.solrCore); + if (null != fieldsToAdd) { + CompletableFuture future = CompletableFuture.runAsync(() -> { + List addedFields = fieldsToAdd.stream().map(m -> m.get(SolrFields.SOLR_FIELD_NAME).toString()).collect(Collectors.toList()); + int counter = 0; + while (counter < 10 && !checkCondition(addedFields)) { + try { + TimeUnit.MILLISECONDS.sleep(100); + log.info("Check of added fields - fields {} - counter {}", addedFields, counter); + } catch (InterruptedException e) { + log.error("Error sleeping", e); + Thread.currentThread().interrupt(); + } finally { + counter++; + } + } + }); + future.get(); + } + } catch (ExecutionException | InterruptedException ex) { + log.error("Error executing check of fields", ex); + Thread.currentThread().interrupt(); + return false; } catch (SolrServerException | IOException ex) { log.error("Error executing Solr multi-update request", ex); return false; } return true; } + + private boolean checkCondition(List addedFields) { + List> fields = this.getFields(); + List fieldsAlreadyPresent = fields.stream().map(m -> m.get(SolrFields.SOLR_FIELD_NAME).toString()).collect(Collectors.toList()); + return fieldsAlreadyPresent.containsAll(addedFields); + } + } diff --git a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSearchEngineManager.java b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSearchEngineManager.java index 1cc685429c..a25155a7ef 100644 --- a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSearchEngineManager.java +++ b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSearchEngineManager.java @@ -22,8 +22,8 @@ import com.agiletec.aps.system.common.entity.model.attribute.AttributeInterface; import com.agiletec.aps.system.services.lang.ILangManager; import com.agiletec.aps.system.services.lang.Lang; -import com.agiletec.aps.util.ApsTenantApplicationUtils; import com.agiletec.aps.util.DateConverter; +import com.agiletec.plugins.jacms.aps.system.services.content.event.PublicContentChangedEvent; import com.agiletec.plugins.jacms.aps.system.services.content.event.PublicContentChangedObserver; import com.agiletec.plugins.jacms.aps.system.services.content.model.Content; import com.agiletec.plugins.jacms.aps.system.services.searchengine.ICmsSearchEngineManager; @@ -44,11 +44,11 @@ import java.util.stream.Collectors; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import org.entando.entando.aps.system.services.cache.ICacheInfoManager; import org.entando.entando.aps.system.services.searchengine.SearchEngineFilter; import org.entando.entando.ent.exception.EntException; import org.entando.entando.ent.exception.EntRuntimeException; import org.entando.entando.plugins.jpsolr.aps.system.solr.SolrFieldsChecker.CheckFieldsResult; +import org.entando.entando.plugins.jpsolr.aps.system.solr.cache.ISolrSearchEngineCacheWrapper; import org.entando.entando.plugins.jpsolr.aps.system.solr.model.ContentTypeSettings; import org.entando.entando.plugins.jpsolr.aps.system.solr.model.SolrFacetedContentsResult; import org.entando.entando.plugins.jpsolr.aps.system.solr.model.SolrFields; @@ -63,12 +63,11 @@ public class SolrSearchEngineManager extends SearchEngineManager InitializingBean { public static final String RELOAD_FIELDS_NAME = "RELOAD_FIELDS"; - private static final String LAST_RELOAD_CACHE_PARAM_NAME = "SolrSearchEngine_lastReloadInfo"; @Setter private transient ILangManager langManager; @Setter - private transient ICacheInfoManager cacheInfoManager; + private transient ISolrSearchEngineCacheWrapper cacheWrapper; @Setter private transient ISolrProxyTenantAware solrProxy; @@ -102,6 +101,7 @@ public void refresh() throws Throwable { @Override protected void release() { try { + this.cacheWrapper.release(); this.solrProxy.close(); } catch (Exception ex) { log.error("Error closing Solr resources {}", ex); @@ -116,17 +116,19 @@ public void destroy() { log.error("Error destroying Solr resources {}", ex); } } - + private void refreshAllTenantsFields() { for (ISolrResourcesAccessor tenantResources : this.solrProxy.getAllSolrTenantsResources()) { Lock lock = tenantsLock.get(tenantResources.getSolrCore()); lock.lock(); try { + List smallTypes = this.getContentManager().getSmallEntityTypes(); boolean refresh = this.getContentTypesSettings(tenantResources.getSolrSchemaDAO()) .stream().anyMatch(settings -> !settings.isValid()); if (refresh) { log.info("Refreshing CMS fields for core '{}'", tenantResources.getSolrCore()); refreshCmsFields(tenantResources); + smallTypes.stream().forEach(set -> this.cacheWrapper.markContentTypeStatusSchema(set.getCode())); } } catch (EntException ex) { log.error("Error refreshing CMS fields", ex); @@ -219,7 +221,8 @@ public void refreshContentType(String typeCode) throws EntException { lock.lock(); try { List> fields = this.solrProxy.getSolrSchemaDAO().getFields(); - refreshFields(fields, getAttributesToCheck(typeCode)); + this.refreshFields(fields, getAttributesToCheck(typeCode)); + this.cacheWrapper.markContentTypeStatusSchema(typeCode); } catch (Exception ex) { throw new EntException("Error refreshing contentType " + typeCode, ex); } finally { @@ -247,6 +250,19 @@ public void deleteIndexedEntity(String entityId) throws EntException { } } + @Override + public void updateFromPublicContentChanged(PublicContentChangedEvent event) { + String typeCode = event.getContentId().substring(0, 3); + try { + if (!this.cacheWrapper.isTypeMarked(typeCode)) { + this.refreshContentType(typeCode); + } + super.updateFromPublicContentChanged(event); + } catch (EntException e) { + log.error("Error refresh type " + typeCode, e); + } + } + @Override public void updateFromEntityTypesChanging(EntityTypesChangingEvent event) { Lock lock = tenantsLock.get(this.solrProxy.getSolrCore()); @@ -263,17 +279,14 @@ public void updateFromEntityTypesChanging(EntityTypesChangingEvent event) { attributes = List.of(); } refreshFields(fields, attributes); + this.cacheWrapper.markContentTypeStatusSchema(event.getNewEntityType().getTypeCode()); } finally { lock.unlock(); } } - + private void refreshFields(List> fields, List attributes) { - refreshFields(fields, attributes, solrProxy.getSolrSchemaDAO()); - } - - private void refreshFields(List> fields, List attributes, - ISolrSchemaDAO schemaDAO) { + ISolrSchemaDAO schemaDAO = solrProxy.getSolrSchemaDAO(); SolrFieldsChecker fieldsChecker = new SolrFieldsChecker(fields, attributes, langManager.getLangs()); CheckFieldsResult result = fieldsChecker.checkFields(); if (result.needsUpdate()) { @@ -352,21 +365,20 @@ public SolrFacetedContentsResult searchFacetedEntities(SearchEngineFilter[][] fi @Override public void notifyEndingIndexLoading(LastReloadInfo info, IIndexerDAO newIndexerDAO) { - this.cacheInfoManager.putInCache(ICacheInfoManager.DEFAULT_CACHE_NAME, this.getLastReloadCacheKey(), info); + this.setLastReloadInfo(info); this.solrProxy.getIndexStatus().setReadyIfPossible(); } @Override - public LastReloadInfo getLastReloadInfo() { - return (SolrLastReloadInfo) this.cacheInfoManager.getFromCache(ICacheInfoManager.DEFAULT_CACHE_NAME, - this.getLastReloadCacheKey()); + protected void setLastReloadInfo(LastReloadInfo lastReloadInfo) { + this.cacheWrapper.setLastReloadInfo(lastReloadInfo); } - private String getLastReloadCacheKey() { - String suffix = ApsTenantApplicationUtils.getTenant().orElse("_primary_"); - return LAST_RELOAD_CACHE_PARAM_NAME + "_" + suffix; + @Override + public LastReloadInfo getLastReloadInfo() { + return this.cacheWrapper.getLastReloadInfo(); } - + @Override public int getStatus() { return this.solrProxy.getIndexStatus().getValue(); @@ -376,4 +388,5 @@ public int getStatus() { protected void setStatus(int status) { this.solrProxy.getIndexStatus().setValue(status); } + } diff --git a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/cache/ISolrSearchEngineCacheWrapper.java b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/cache/ISolrSearchEngineCacheWrapper.java new file mode 100644 index 0000000000..2c1e3240ab --- /dev/null +++ b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/cache/ISolrSearchEngineCacheWrapper.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ +package org.entando.entando.plugins.jpsolr.aps.system.solr.cache; + +import com.agiletec.aps.system.common.ICacheWrapper; +import com.agiletec.plugins.jacms.aps.system.services.searchengine.LastReloadInfo; + +public interface ISolrSearchEngineCacheWrapper extends ICacheWrapper { + + public static final String LAST_RELOAD_CACHE_PARAM_NAME = "SolrSearchEngine_lastReloadInfo"; + + public static final String SOLR_SE_MANAGER_CACHE_NAME = "Entando_SolrSEManager"; + public static final String CONTENT_TYPE_STATUS_CACHE_ENTRY_PREFIX = "SolrSEManager_type_"; + + public void setLastReloadInfo(LastReloadInfo lastReloadInfo); + + public LastReloadInfo getLastReloadInfo(); + + public void markContentTypeStatusSchema(String typeCode); + + public boolean isTypeMarked(String typeCode); + +} diff --git a/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/cache/SolrSearchEngineCacheWrapper.java b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/cache/SolrSearchEngineCacheWrapper.java new file mode 100644 index 0000000000..bbe1c7004f --- /dev/null +++ b/solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/cache/SolrSearchEngineCacheWrapper.java @@ -0,0 +1,57 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ +package org.entando.entando.plugins.jpsolr.aps.system.solr.cache; + +import com.agiletec.aps.system.common.AbstractCacheWrapper; +import com.agiletec.plugins.jacms.aps.system.services.searchengine.LastReloadInfo; +import java.util.Optional; +import org.entando.entando.plugins.jpsolr.aps.system.solr.SolrLastReloadInfo; +import org.springframework.stereotype.Component; + +@Component("jpsolrSolrSearchEngineCacheWrapper") +public class SolrSearchEngineCacheWrapper extends AbstractCacheWrapper implements ISolrSearchEngineCacheWrapper { + + @Override + public void release() { + super.getCache().clear(); + } + + @Override + protected String getCacheName() { + return SOLR_SE_MANAGER_CACHE_NAME; + } + + @Override + public void setLastReloadInfo(LastReloadInfo lastReloadInfo) { + this.getCache().put(LAST_RELOAD_CACHE_PARAM_NAME, lastReloadInfo); + } + + @Override + public LastReloadInfo getLastReloadInfo() { + return this.get(LAST_RELOAD_CACHE_PARAM_NAME, SolrLastReloadInfo.class); + } + + @Override + public void markContentTypeStatusSchema(String typeCode) { + String entryKey = CONTENT_TYPE_STATUS_CACHE_ENTRY_PREFIX + typeCode; + this.getCache().put(entryKey, "true"); + } + + @Override + public boolean isTypeMarked(String typeCode) { + String entryKey = CONTENT_TYPE_STATUS_CACHE_ENTRY_PREFIX + typeCode; + return Optional.ofNullable(this.get(entryKey, String.class)).isPresent(); + } + +} diff --git a/solr-plugin/src/main/resources/spring/plugins/jpsolr/aps/baseManagersConfig.xml b/solr-plugin/src/main/resources/spring/plugins/jpsolr/aps/baseManagersConfig.xml index 8be5751ec5..62a5ca49ce 100644 --- a/solr-plugin/src/main/resources/spring/plugins/jpsolr/aps/baseManagersConfig.xml +++ b/solr-plugin/src/main/resources/spring/plugins/jpsolr/aps/baseManagersConfig.xml @@ -1,12 +1,24 @@ - - + + + + + + + + + + + + @@ -17,8 +29,8 @@ - - + + diff --git a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/SolrBaseTestCase.java b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/SolrBaseTestCase.java new file mode 100644 index 0000000000..5ba0ff8c2a --- /dev/null +++ b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/SolrBaseTestCase.java @@ -0,0 +1,60 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ +package org.entando.entando.plugins.jpsolr; + +import com.agiletec.aps.BaseTestCase; +import javax.servlet.ServletContext; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.FileSystemResourceLoader; +import org.springframework.mock.web.MockServletContext; +import org.testcontainers.containers.GenericContainer; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +import uk.org.webcompere.systemstubs.jupiter.SystemStub; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; + +@ExtendWith(SystemStubsExtension.class) +public abstract class SolrBaseTestCase { + + private static GenericContainer solrContainer; + + private static ApplicationContext applicationContext; + + @SystemStub + private static EnvironmentVariables environmentVariables; + + public static ApplicationContext getApplicationContext() { + return applicationContext; + } + + public static void setApplicationContext(ApplicationContext applicationContextToSet) { + applicationContext = applicationContextToSet; + } + + @BeforeAll + public static void startUp() throws Exception { + solrContainer = SolrTestUtils.startContainer(solrContainer, environmentVariables); + ServletContext srvCtx = new MockServletContext("", new FileSystemResourceLoader()); + applicationContext = new CustomConfigTestUtils().createApplicationContext(srvCtx); + setApplicationContext(applicationContext); + } + + @AfterAll + public static void tearDown() throws Exception { + BaseTestCase.tearDown(); + } + +} diff --git a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/SolrTestExtension.java b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/SolrTestExtension.java deleted file mode 100644 index 5aa0c6db9d..0000000000 --- a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/SolrTestExtension.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.entando.entando.plugins.jpsolr; - -import java.lang.reflect.Field; -import java.util.Map; -import org.apache.solr.client.solrj.SolrClient; -import org.apache.solr.client.solrj.SolrServerException; -import org.apache.solr.client.solrj.impl.HttpSolrClient; -import org.apache.solr.client.solrj.request.schema.SchemaRequest; -import org.apache.solr.common.SolrException; -import org.entando.entando.aps.system.services.searchengine.SolrEnvironmentVariables; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.testcontainers.containers.GenericContainer; - -public class SolrTestExtension implements BeforeAllCallback { - - public static final String RECREATE_CORE = "RECREATE_CORE"; - - private static final int SOLR_PORT = 8983; - private static final String SOLR_IMAGE = "solr:9"; - private static final String SOLR_CORE = "entando"; - - private static GenericContainer solrContainer; - - @Override - public void beforeAll(ExtensionContext extensionContext) throws Exception { - if (solrContainer == null) { - solrContainer = new GenericContainer(SOLR_IMAGE).withExposedPorts(SOLR_PORT) - .withCommand("solr-precreate", SOLR_CORE); - solrContainer.start(); - - updateEnv("SOLR_ADDRESS", "http://localhost:" + solrContainer.getMappedPort(SOLR_PORT) + "/solr"); - } - - if (extensionContext.getTags().contains(RECREATE_CORE)) { - solrContainer.execInContainer("bin/solr", "delete", "-c", SOLR_CORE); - solrContainer.execInContainer("bin/solr", "create_core", "-c", SOLR_CORE); - } - - waitSolrReady(); - } - - /** - * Sometimes Solr is not ready even if the container is ready. This method attempts to read the schema fields. - */ - private void waitSolrReady() throws Exception { - int attempt = 0; - do { - SolrClient solrClient = new HttpSolrClient.Builder(SolrEnvironmentVariables.solrAddress()) - .withConnectionTimeout(10000) - .withSocketTimeout(60000) - .build(); - try { - solrClient.request(new SchemaRequest.Fields(), SolrEnvironmentVariables.solrCore()); - return; - } catch (SolrServerException | SolrException ex) { - attempt++; - } finally { - solrClient.close(); - } - } while (attempt < 10); - } - - /** - * Some tests that use this extension start instances of NotifyingThread where we need to override the SOLR_ADDRESS - * value. Unfortunately Mockito.mockStatic doesn't work in secondary threads, so we can't mock - * SolrEnvironmentVariables static methods as done in RedisTestExtension. The following method is a workaround that - * manipulates the env map directly. - */ - private static void updateEnv(String name, String val) throws Exception { - Map env = System.getenv(); - Field field = env.getClass().getDeclaredField("m"); - field.setAccessible(true); - ((Map) field.get(env)).put(name, val); - } -} diff --git a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/SolrTestUtils.java b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/SolrTestUtils.java index b8fef61e64..4b505e13b7 100644 --- a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/SolrTestUtils.java +++ b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/SolrTestUtils.java @@ -14,11 +14,23 @@ package org.entando.entando.plugins.jpsolr; import com.agiletec.aps.system.common.notify.NotifyManager; +import org.apache.solr.client.solrj.SolrClient; +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.impl.HttpSolrClient; +import org.apache.solr.client.solrj.request.schema.SchemaRequest; +import org.apache.solr.common.SolrException; +import org.entando.entando.aps.system.services.searchengine.SolrEnvironmentVariables; +import org.testcontainers.containers.GenericContainer; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; /** * @author E.Santoboni */ public class SolrTestUtils { + + private static final int SOLR_PORT = 8983; + private static final String SOLR_IMAGE = "solr:9"; + private static final String SOLR_CORE = "entando"; public static void waitNotifyingThread() throws InterruptedException { waitThreads(NotifyManager.NOTIFYING_THREAD_NAME); @@ -35,5 +47,41 @@ public static void waitThreads(String threadNamePrefix) throws InterruptedExcept } } } + + public static GenericContainer startContainer(GenericContainer solrContainer, EnvironmentVariables environmentVariables) throws Exception { + if (solrContainer == null) { + solrContainer = new GenericContainer(SOLR_IMAGE).withExposedPorts(SOLR_PORT) + .withCommand("solr-precreate", SOLR_CORE); + solrContainer.start(); + environmentVariables.set("SOLR_ADDRESS", "http://localhost:" + solrContainer.getMappedPort(SOLR_PORT) + "/solr"); + } + waitSolrReady(); + return solrContainer; + } + + /** + * Sometimes Solr is not ready even if the container is ready. This method attempts to read the schema fields. + */ + private static void waitSolrReady() throws Exception { + int attempt = 0; + do { + SolrClient solrClient = new HttpSolrClient.Builder(SolrEnvironmentVariables.solrAddress()) + .withConnectionTimeout(10000) + .withSocketTimeout(60000) + .build(); + try { + solrClient.request(new SchemaRequest.Fields(), SolrEnvironmentVariables.solrCore()); + return; + } catch (SolrServerException | SolrException ex) { + attempt++; + } finally { + solrClient.close(); + } + } while (attempt < 10); + } + + + + } diff --git a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/AdvContentSearchTest.java b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/AdvContentSearchTest.java index 2a760c4a04..e899956ca9 100644 --- a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/AdvContentSearchTest.java +++ b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/AdvContentSearchTest.java @@ -18,7 +18,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.agiletec.aps.BaseTestCase; import java.util.ArrayList; import java.util.List; import com.agiletec.aps.system.common.entity.model.EntitySearchFilter; @@ -34,27 +33,18 @@ import java.util.Collection; import java.util.Date; import java.util.stream.Collectors; -import javax.servlet.ServletContext; import org.entando.entando.aps.system.services.searchengine.FacetedContentsResult; import org.entando.entando.aps.system.services.searchengine.SearchEngineFilter; -import org.entando.entando.plugins.jpsolr.CustomConfigTestUtils; -import org.entando.entando.plugins.jpsolr.SolrTestExtension; +import org.entando.entando.plugins.jpsolr.SolrBaseTestCase; import org.entando.entando.plugins.jpsolr.SolrTestUtils; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.context.ApplicationContext; -import org.springframework.core.io.FileSystemResourceLoader; -import org.springframework.mock.web.MockServletContext; /** * Rewriting of some default tests for content manager * @author E.Santoboni */ -@ExtendWith(SolrTestExtension.class) -class AdvContentSearchTest { +class AdvContentSearchTest extends SolrBaseTestCase { private IContentManager contentManager = null; private ISolrSearchEngineManager searchEngineManager = null; @@ -62,28 +52,6 @@ class AdvContentSearchTest { private List allowedGroup = new ArrayList<>(); - private static ApplicationContext applicationContext; - - public static ApplicationContext getApplicationContext() { - return applicationContext; - } - - public static void setApplicationContext(ApplicationContext applicationContext) { - AdvContentSearchTest.applicationContext = applicationContext; - } - - @BeforeAll - public static void startUp() throws Exception { - ServletContext srvCtx = new MockServletContext("", new FileSystemResourceLoader()); - applicationContext = new CustomConfigTestUtils().createApplicationContext(srvCtx); - setApplicationContext(applicationContext); - } - - @AfterAll - public static void tearDown() throws Exception { - BaseTestCase.tearDown(); - } - @BeforeEach protected void init() throws Exception { try { diff --git a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/FacetSearchEngineManagerIntegrationTest.java b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/FacetSearchEngineManagerIntegrationTest.java index eb0e6a306e..9a4e2f7ce0 100644 --- a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/FacetSearchEngineManagerIntegrationTest.java +++ b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/FacetSearchEngineManagerIntegrationTest.java @@ -13,13 +13,14 @@ */ package org.entando.entando.plugins.jpsolr.aps.system.solr; +import org.entando.entando.plugins.jpsolr.SolrBaseTestCase; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.agiletec.aps.BaseTestCase; import com.agiletec.aps.system.common.FieldSearchFilter; import com.agiletec.aps.system.common.entity.model.attribute.ITextAttribute; import com.agiletec.aps.system.services.category.Category; @@ -34,52 +35,18 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import javax.servlet.ServletContext; import org.entando.entando.aps.system.services.searchengine.FacetedContentsResult; import org.entando.entando.aps.system.services.searchengine.SearchEngineFilter; -import org.entando.entando.plugins.jpsolr.CustomConfigTestUtils; -import org.entando.entando.plugins.jpsolr.SolrTestExtension; import org.entando.entando.plugins.jpsolr.SolrTestUtils; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.context.ApplicationContext; -import org.springframework.core.io.FileSystemResourceLoader; -import org.springframework.mock.web.MockServletContext; -/** - * @author eu - */ -@ExtendWith(SolrTestExtension.class) -class FacetSearchEngineManagerIntegrationTest { +class FacetSearchEngineManagerIntegrationTest extends SolrBaseTestCase { private IContentManager contentManager = null; private ICmsSearchEngineManager searchEngineManager = null; private ICategoryManager categoryManager; - private static ApplicationContext applicationContext; - - public static ApplicationContext getApplicationContext() { - return applicationContext; - } - - public static void setApplicationContext(ApplicationContext applicationContext) { - FacetSearchEngineManagerIntegrationTest.applicationContext = applicationContext; - } - - @BeforeAll - public static void startUp() throws Exception { - ServletContext srvCtx = new MockServletContext("", new FileSystemResourceLoader()); - applicationContext = new CustomConfigTestUtils().createApplicationContext(srvCtx); - setApplicationContext(applicationContext); - } - - @AfterAll - public static void tearDown() throws Exception { - BaseTestCase.tearDown(); - } @BeforeEach protected void init() throws Exception { @@ -88,7 +55,7 @@ protected void init() throws Exception { this.categoryManager = getApplicationContext().getBean(ICategoryManager.class); ((ISolrSearchEngineManager) this.searchEngineManager).refreshCmsFields(); } - + @Test void testSearchAllContents() throws Exception { Thread thread = this.searchEngineManager.startReloadContentsReferences(); diff --git a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrFieldsCheckerTest.java b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrFieldsCheckerTest.java new file mode 100644 index 0000000000..4ebbd59ac6 --- /dev/null +++ b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrFieldsCheckerTest.java @@ -0,0 +1,180 @@ +/* + * Copyright 2023-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + */ +package org.entando.entando.plugins.jpsolr.aps.system.solr; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.agiletec.aps.system.common.entity.model.attribute.AttributeInterface; +import com.agiletec.aps.system.common.entity.model.attribute.BooleanAttribute; +import com.agiletec.aps.system.common.entity.model.attribute.DateAttribute; +import com.agiletec.aps.system.common.entity.model.attribute.NumberAttribute; +import com.agiletec.aps.system.common.entity.model.attribute.TextAttribute; +import com.agiletec.aps.system.services.lang.Lang; +import org.junit.Before; + + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.entando.entando.plugins.jpsolr.aps.system.solr.model.SolrFields; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@ExtendWith(MockitoExtension.class) +class SolrFieldsCheckerTest { + + private static final List defaultFields = List.of(SolrFields.SOLR_CONTENT_ID_FIELD_NAME, + SolrFields.SOLR_CONTENT_TYPE_CODE_FIELD_NAME, + SolrFields.SOLR_CONTENT_GROUP_FIELD_NAME, + SolrFields.SOLR_CONTENT_DESCRIPTION_FIELD_NAME, + SolrFields.SOLR_CONTENT_MAIN_GROUP_FIELD_NAME, + SolrFields.SOLR_CONTENT_CATEGORY_FIELD_NAME, + SolrFields.SOLR_CONTENT_CREATION_FIELD_NAME, + SolrFields.SOLR_CONTENT_LAST_MODIFY_FIELD_NAME); + + private SolrFieldsChecker solrFieldsChecker; + private List> fields; + private List attributes; + private List languages; + + @BeforeEach + void setUp() { + fields = new ArrayList<>(); + attributes = new ArrayList<>(); + languages = new ArrayList<>(); + Lang mockLang = mock(Lang.class); + when(mockLang.getCode()).thenReturn("en"); + languages.add(mockLang); + solrFieldsChecker = new SolrFieldsChecker(fields, attributes, languages); + } + + @Test + void testCheckFields() { + TextAttribute mockAttribute = mock(TextAttribute.class); + when(mockAttribute.getName()).thenReturn("mockAttribute"); + when(mockAttribute.getRoles()).thenReturn(new String[]{"role1", "role2"}); + attributes.add(mockAttribute); + + SolrFieldsChecker.CheckFieldsResult result = solrFieldsChecker.checkFields(); + + assertTrue(result.needsUpdate()); + List expectedAddedFieldCodes = new ArrayList<>(defaultFields); + expectedAddedFieldCodes.addAll(List.of("en_mockAttribute", "en", "en_role2", "en_role1")); + assertEquals(expectedAddedFieldCodes.size(), result.getFieldsToAdd().size()); + assertEquals(0, result.getFieldsToReplace().size()); + List addedFieldCodesRes = result.getFieldsToAdd() + .stream().map(m -> m.get(SolrFields.SOLR_FIELD_NAME).toString()).collect(Collectors.toList()); + assertTrue(addedFieldCodesRes.containsAll(expectedAddedFieldCodes)); + + List> addedFieldRes = result.getFieldsToAdd().stream() + .filter(f -> f.get(SolrFields.SOLR_FIELD_NAME).toString().contains("mockAttribute")).collect(Collectors.toList()); + assertEquals(1, addedFieldRes.size()); + assertEquals("en_mockAttribute", addedFieldRes.get(0).get(SolrFields.SOLR_FIELD_NAME)); + assertEquals(SolrFields.TYPE_TEXT_GEN_SORT, addedFieldRes.get(0).get(SolrFields.SOLR_FIELD_TYPE)); + assertFalse((boolean) addedFieldRes.get(0).get(SolrFields.SOLR_FIELD_MULTIVALUED)); + } + + @Test + void testReplaceFieldWithSameType() { + Map existingField = new HashMap<>(); + existingField.put(SolrFields.SOLR_FIELD_NAME, "en_existingField"); + existingField.put(SolrFields.SOLR_FIELD_TYPE, SolrFields.TYPE_TEXT_GEN_SORT); + existingField.put(SolrFields.SOLR_FIELD_MULTIVALUED, false); + fields.add(existingField); + + TextAttribute mockAttribute = mock(TextAttribute.class); + when(mockAttribute.getName()).thenReturn("existingField"); + attributes.add(mockAttribute); + + SolrFieldsChecker.CheckFieldsResult result = solrFieldsChecker.checkFields(); + + assertTrue(result.needsUpdate()); + assertEquals(9, result.getFieldsToAdd().size()); + assertEquals(0, result.getFieldsToReplace().size()); + + List expectedAddedFieldCodes = new ArrayList<>(defaultFields); + expectedAddedFieldCodes.addAll(List.of("en")); + List addedFieldCodesRes = result.getFieldsToAdd() + .stream().map(m -> m.get(SolrFields.SOLR_FIELD_NAME).toString()).collect(Collectors.toList()); + assertTrue(addedFieldCodesRes.containsAll(expectedAddedFieldCodes)); + } + + @Test + void testReplaceFieldWithDifferentType() { + Map existingField = new HashMap<>(); + existingField.put(SolrFields.SOLR_FIELD_NAME, "en_existingField"); + existingField.put(SolrFields.SOLR_FIELD_TYPE, SolrFields.TYPE_TEXT_GEN_SORT); + existingField.put(SolrFields.SOLR_FIELD_MULTIVALUED, false); + fields.add(existingField); + + BooleanAttribute mockAttribute = mock(BooleanAttribute.class); + when(mockAttribute.getName()).thenReturn("existingField"); + when(mockAttribute.isSearchable()).thenReturn(true); + attributes.add(mockAttribute); + + SolrFieldsChecker.CheckFieldsResult result = solrFieldsChecker.checkFields(); + + assertTrue(result.needsUpdate()); + assertEquals(9, result.getFieldsToAdd().size()); + assertEquals(1, result.getFieldsToReplace().size()); + + List expectedAddedFieldCodes = new ArrayList<>(defaultFields); + expectedAddedFieldCodes.addAll(List.of("en")); + List addedFieldCodesRes = result.getFieldsToAdd() + .stream().map(m -> m.get(SolrFields.SOLR_FIELD_NAME).toString()).collect(Collectors.toList()); + assertTrue(addedFieldCodesRes.containsAll(expectedAddedFieldCodes)); + + Map replacedField = result.getFieldsToReplace().get(0); + assertEquals("en_existingField", replacedField.get(SolrFields.SOLR_FIELD_NAME)); + assertEquals(SolrFields.TYPE_BOOLEAN, replacedField.get(SolrFields.SOLR_FIELD_TYPE)); + assertFalse((boolean) replacedField.get(SolrFields.SOLR_FIELD_MULTIVALUED)); + } + + @Test + void testReplaceFieldWithDifferentMultifieldValue() { + Map existingField = new HashMap<>(); + existingField.put(SolrFields.SOLR_FIELD_NAME, "en_existingDateField"); + existingField.put(SolrFields.SOLR_FIELD_TYPE, SolrFields.TYPE_PDATES); + existingField.put(SolrFields.SOLR_FIELD_MULTIVALUED, true); + fields.add(existingField); + + DateAttribute mockAttribute = mock(DateAttribute.class); + when(mockAttribute.getName()).thenReturn("existingDateField"); + attributes.add(mockAttribute); + + SolrFieldsChecker.CheckFieldsResult result = solrFieldsChecker.checkFields(); + + assertTrue(result.needsUpdate()); + assertEquals(9, result.getFieldsToAdd().size()); + assertEquals(1, result.getFieldsToReplace().size()); + + List expectedAddedFieldCodes = new ArrayList<>(defaultFields); + expectedAddedFieldCodes.addAll(List.of("en")); + List addedFieldCodesRes = result.getFieldsToAdd() + .stream().map(m -> m.get(SolrFields.SOLR_FIELD_NAME).toString()).collect(Collectors.toList()); + assertTrue(addedFieldCodesRes.containsAll(expectedAddedFieldCodes)); + + Map replacedField = result.getFieldsToReplace().get(0); + assertEquals("en_existingDateField", replacedField.get(SolrFields.SOLR_FIELD_NAME)); + assertEquals(SolrFields.TYPE_PDATES, replacedField.get(SolrFields.SOLR_FIELD_TYPE)); + assertFalse((boolean) replacedField.get(SolrFields.SOLR_FIELD_MULTIVALUED)); + } + +} diff --git a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSchemaDaoTest.java b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSchemaDaoTest.java index b6da40a1d6..fc97546975 100644 --- a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSchemaDaoTest.java +++ b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSchemaDaoTest.java @@ -53,6 +53,9 @@ void shouldReturnEmptyListIfGetFieldsFail() throws Exception { @Test void shouldUpdateFields() throws Exception { + NamedList mockedResult = new NamedList<>(); + mockedResult.add("fields", getMockedFields()); + Mockito.lenient().when(solrClient.request(any(SchemaRequest.Fields.class), anyString())).thenReturn(mockedResult); List> fieldsToAdd = List.of(Map.of("name", "field1")); List> fieldsToReplace = List.of(Map.of("name", "field2")); Assertions.assertTrue(solrSchemaDAO.updateFields(fieldsToAdd, fieldsToReplace)); @@ -78,4 +81,5 @@ private List> getMockedFields() { fields.add(field); return fields; } + } diff --git a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSearchEngineManagerIntegrationTest.java b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSearchEngineManagerIntegrationTest.java index d07617300e..a309450459 100644 --- a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSearchEngineManagerIntegrationTest.java +++ b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSearchEngineManagerIntegrationTest.java @@ -52,13 +52,11 @@ import java.util.Set; import java.util.function.BiConsumer; import java.util.stream.IntStream; -import javax.servlet.ServletContext; import org.entando.entando.aps.system.services.searchengine.FacetedContentsResult; import org.entando.entando.aps.system.services.searchengine.SearchEngineFilter; import org.entando.entando.aps.system.services.searchengine.SearchEngineFilter.TextSearchOption; import org.entando.entando.ent.exception.EntRuntimeException; -import org.entando.entando.plugins.jpsolr.CustomConfigTestUtils; -import org.entando.entando.plugins.jpsolr.SolrTestExtension; +import org.entando.entando.plugins.jpsolr.SolrBaseTestCase; import org.entando.entando.plugins.jpsolr.SolrTestUtils; import org.entando.entando.plugins.jpsolr.aps.system.solr.model.SolrSearchEngineFilter; import org.junit.jupiter.api.AfterAll; @@ -67,18 +65,13 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.context.ApplicationContext; -import org.springframework.core.io.FileSystemResourceLoader; -import org.springframework.mock.web.MockServletContext; /** * Test del servizio detentore delle operazioni sul motore di ricerca. * * @author E.Santoboni */ -@ExtendWith(SolrTestExtension.class) -class SolrSearchEngineManagerIntegrationTest { +class SolrSearchEngineManagerIntegrationTest extends SolrBaseTestCase { private static final String ROLE_FOR_TEST = "jacmstest:date"; @@ -87,22 +80,19 @@ class SolrSearchEngineManagerIntegrationTest { private ISolrSearchEngineManager searchEngineManager = null; private ICategoryManager categoryManager; - private static ApplicationContext applicationContext; - - public static ApplicationContext getApplicationContext() { - return applicationContext; - } - - public static void setApplicationContext(ApplicationContext applicationContext) { - SolrSearchEngineManagerIntegrationTest.applicationContext = applicationContext; - } + //private static ApplicationContext applicationContext; @BeforeAll public static void startUp() throws Exception { + /* ServletContext srvCtx = new MockServletContext("", new FileSystemResourceLoader()); ApplicationContext applicationContext = new CustomConfigTestUtils().createApplicationContext(srvCtx); setApplicationContext(applicationContext); - IContentManager contentManager = applicationContext.getBean(IContentManager.class); + */ + SolrBaseTestCase.startUp(); + + + IContentManager contentManager = getApplicationContext().getBean(IContentManager.class); AttributeRole role = contentManager.getAttributeRole(ROLE_FOR_TEST); Assertions.assertNotNull(role); Content artType = contentManager.createContentType("ART"); @@ -139,7 +129,7 @@ public static void startUp() throws Exception { dateAttrEnv.setRoles(new String[]{ROLE_FOR_TEST}); ((IEntityTypesConfigurer) contentManager).updateEntityPrototype(evnType); } - ISolrSearchEngineManager solrSearchEngineManager = applicationContext.getBean(ISolrSearchEngineManager.class); + ISolrSearchEngineManager solrSearchEngineManager = getApplicationContext().getBean(ISolrSearchEngineManager.class); solrSearchEngineManager.refreshCmsFields(); } @@ -169,7 +159,7 @@ public void accept(List attributes, String name) { dateAttrEnv.setRoles(new String[0]); ((IEntityTypesConfigurer) contentManager).updateEntityPrototype(evnType); } finally { - BaseTestCase.tearDown(); + SolrBaseTestCase.tearDown(); } } diff --git a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSearchEngineManagerTest.java b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSearchEngineManagerTest.java index 6b4d38d9d7..3606facef7 100644 --- a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSearchEngineManagerTest.java +++ b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSearchEngineManagerTest.java @@ -3,20 +3,20 @@ import static com.agiletec.plugins.jacms.aps.system.services.searchengine.ICmsSearchEngineManager.STATUS_NEED_TO_RELOAD_INDEXES; import static com.agiletec.plugins.jacms.aps.system.services.searchengine.ICmsSearchEngineManager.STATUS_READY; import static com.agiletec.plugins.jacms.aps.system.services.searchengine.ICmsSearchEngineManager.STATUS_RELOADING_INDEXES_IN_PROGRESS; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import com.agiletec.aps.system.services.category.ICategoryManager; import com.agiletec.aps.system.services.lang.ILangManager; import com.agiletec.plugins.jacms.aps.system.services.content.IContentManager; +import com.agiletec.plugins.jacms.aps.system.services.content.event.PublicContentChangedEvent; import java.util.List; import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.solr.client.solrj.impl.HttpSolrClient; +import org.apache.solr.client.solrj.impl.Http2SolrClient; import org.apache.solr.client.solrj.request.schema.SchemaRequest; import org.apache.solr.common.util.NamedList; -import org.entando.entando.aps.system.services.cache.ICacheInfoManager; import org.entando.entando.aps.system.services.tenants.ITenantManager; import org.entando.entando.ent.exception.EntException; +import org.entando.entando.plugins.jpsolr.aps.system.solr.cache.ISolrSearchEngineCacheWrapper; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -41,20 +41,19 @@ class SolrSearchEngineManagerTest { @Mock private IContentManager contentManager; @Mock - private ICacheInfoManager cacheInfoManager; + private ISolrSearchEngineCacheWrapper cacheWrapper; private SolrProxyTenantAware solrProxy; private SolrSearchEngineManager solrSearchEngineManager; - private MockedConstruction mockedConstructionSolrClientBuilder; - private HttpSolrClient solrClient; + private MockedConstruction mockedConstructionSolrClientBuilder; + private Http2SolrClient solrClient; @BeforeEach void setUp() throws Exception { - mockedConstructionSolrClientBuilder = Mockito.mockConstruction(HttpSolrClient.Builder.class, + mockedConstructionSolrClientBuilder = Mockito.mockConstruction(Http2SolrClient.Builder.class, (builder, context) -> { - solrClient = Mockito.mock(HttpSolrClient.class); - Mockito.when(builder.withHttpClient(any())).thenReturn(builder); + solrClient = Mockito.mock(Http2SolrClient.class); Mockito.when(builder.build()).thenReturn(solrClient); }); solrProxy = new SolrProxyTenantAware( @@ -65,13 +64,26 @@ void setUp() throws Exception { solrSearchEngineManager.setSolrProxy(solrProxy); solrSearchEngineManager.setContentManager(contentManager); solrSearchEngineManager.setLangManager(langManager); - solrSearchEngineManager.setCacheInfoManager(cacheInfoManager); + solrSearchEngineManager.setCacheWrapper(cacheWrapper); } @AfterEach void tearDown() { mockedConstructionSolrClientBuilder.close(); } + + @Test + void shouldReleaseCacheWhenrefreshService() throws Throwable { + this.solrSearchEngineManager.refresh(); + Mockito.verify(cacheWrapper, Mockito.times(1)).release(); + } + + @Test + void shouldExecuteNotifyWithInternalError() { + PublicContentChangedEvent event = new PublicContentChangedEvent(); + event.setContentId("ART123"); + Assertions.assertDoesNotThrow(() -> this.solrSearchEngineManager.updateFromPublicContentChanged(event)); + } @Test void shouldRollbackIndexStatusIfReloadThreadDoesNotStart() throws Exception { diff --git a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/web/AbstractControllerIntegrationTest.java b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/web/AbstractControllerIntegrationTest.java index 347c1ebf68..0c88b3b183 100644 --- a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/web/AbstractControllerIntegrationTest.java +++ b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/web/AbstractControllerIntegrationTest.java @@ -21,6 +21,7 @@ import com.agiletec.aps.system.services.user.UserDetails; import org.entando.entando.TestEntandoJndiUtils; import org.entando.entando.aps.system.services.oauth2.IApiOAuth2TokenManager; +import org.entando.entando.plugins.jpsolr.SolrTestUtils; import org.entando.entando.web.AuthRequestBuilder; import org.entando.entando.web.common.interceptor.EntandoOauth2Interceptor; import org.entando.entando.web.utils.OAuth2TestUtils; @@ -39,8 +40,12 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.filter.CorsFilter; +import org.testcontainers.containers.GenericContainer; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +import uk.org.webcompere.systemstubs.jupiter.SystemStub; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; -@ExtendWith(SpringExtension.class) +@ExtendWith({SystemStubsExtension.class, SpringExtension.class}) @ContextConfiguration(locations = { "classpath*:spring/testpropertyPlaceholder.xml", "classpath*:spring/baseSystemConfig.xml", @@ -53,7 +58,7 @@ "classpath*:spring/web/**.xml",}) @WebAppConfiguration(value = "") public abstract class AbstractControllerIntegrationTest { - + protected MockMvc mockMvc; private String accessToken; @@ -76,9 +81,15 @@ public abstract class AbstractControllerIntegrationTest { @Autowired @InjectMocks protected EntandoOauth2Interceptor entandoOauth2Interceptor; + + private static GenericContainer solrContainer; + @SystemStub + private static EnvironmentVariables environmentVariables; + @BeforeAll public static void setup() throws Exception { + solrContainer = SolrTestUtils.startContainer(solrContainer, environmentVariables); TestEntandoJndiUtils.setupJndi(); } diff --git a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/web/content/AdvContentSearchControllerTest.java b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/web/content/AdvContentSearchControllerTest.java index 1cc7a37ff1..982ee432c1 100644 --- a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/web/content/AdvContentSearchControllerTest.java +++ b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/web/content/AdvContentSearchControllerTest.java @@ -37,7 +37,6 @@ import java.util.List; import java.util.Map; import org.entando.entando.plugins.jacms.aps.system.services.content.IContentService; -import org.entando.entando.plugins.jpsolr.SolrTestExtension; import org.entando.entando.plugins.jpsolr.aps.system.solr.ISolrSearchEngineManager; import org.entando.entando.plugins.jpsolr.web.AbstractControllerIntegrationTest; import org.entando.entando.web.utils.OAuth2TestUtils; @@ -47,7 +46,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.ResultActions; @@ -55,7 +53,6 @@ /** * @author E.Santoboni */ -@ExtendWith(SolrTestExtension.class) class AdvContentSearchControllerTest extends AbstractControllerIntegrationTest { @Autowired diff --git a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/web/content/SearchByDoubleFiltersControllerTest.java b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/web/content/SearchByDoubleFiltersControllerTest.java index 30c7329169..49616cbb5b 100644 --- a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/web/content/SearchByDoubleFiltersControllerTest.java +++ b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/web/content/SearchByDoubleFiltersControllerTest.java @@ -36,7 +36,6 @@ import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.entando.entando.plugins.jpsolr.SolrTestExtension; import org.entando.entando.plugins.jpsolr.aps.system.solr.ISolrSearchEngineManager; import org.entando.entando.plugins.jpsolr.web.AbstractControllerIntegrationTest; import org.entando.entando.web.utils.OAuth2TestUtils; @@ -45,14 +44,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.web.servlet.ResultActions; /** * @author E.Santoboni */ -@ExtendWith(SolrTestExtension.class) public class SearchByDoubleFiltersControllerTest extends AbstractControllerIntegrationTest { private static final String TEXT_FOR_TEST = "Entando is the leading modular application platform for building enterprise applications on Kubernetes"; diff --git a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/web/content/SearchByResourceControllerTest.java b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/web/content/SearchByResourceControllerTest.java index e26825251b..8d4ed58be5 100644 --- a/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/web/content/SearchByResourceControllerTest.java +++ b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/web/content/SearchByResourceControllerTest.java @@ -58,11 +58,6 @@ public class SearchByResourceControllerTest extends AbstractControllerIntegratio @Autowired private IResourceManager resourceManager; - @BeforeAll - public static void setup() throws Exception { - AbstractControllerIntegrationTest.setup(); - } - @Override @BeforeEach public void setUp() throws Exception { diff --git a/versioning-plugin/README.md b/versioning-plugin/README.md index 637c0b188e..b3c443f772 100644 --- a/versioning-plugin/README.md +++ b/versioning-plugin/README.md @@ -19,18 +19,6 @@ This plugin adds new administration interfaces related to the content tracking; Please also be aware that a resource referenced by older versions of the content cannot be deleted to avoid inconsistency on restore. Though the plugin installation is not difficult at all, we are going to modify the system tables, so a backup of your database is highly recommended. Furthermore, you may be required to customize the scripts to your needs before installation. -**Installation** - -In order to install the Versioning Plugin, you must insert the following dependency in the pom.xml file of your project: -``` - - org.entando.entando.plugins - entando-plugin-jpversioning - ${entando.version} - war - -``` - **Configuration** You can access to Versioning Plugin’s functionality through the usual left menu: _Plugins_ -> _Versioning_ in the Administration Area. diff --git a/versioning-plugin/pom.xml b/versioning-plugin/pom.xml index 1c61641b55..46ec437ce2 100644 --- a/versioning-plugin/pom.xml +++ b/versioning-plugin/pom.xml @@ -4,7 +4,7 @@ org.entando app-engine - 7.3.0 + 7.3.1 entando-plugin-jpversioning org.entando.entando.plugins diff --git a/versioning-plugin/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java b/versioning-plugin/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java index 232e5783d2..60956e8a3e 100644 --- a/versioning-plugin/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java +++ b/versioning-plugin/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java @@ -44,6 +44,7 @@ import com.agiletec.plugins.jacms.aps.system.services.content.model.ContentRecordVO; import com.agiletec.plugins.jpversioning.aps.system.JpversioningSystemConstants; import java.io.IOException; +import java.util.Optional; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.lang3.StringUtils; import org.xml.sax.SAXException; @@ -63,8 +64,8 @@ public void init() throws Exception { @Override public void initTenantAware() throws Exception { - String deleteMidVersions = this.getConfigManager().getParam(JpversioningSystemConstants.CONFIG_PARAM_DELETE_MID_VERSIONS); - this.setDeleteMidVersions("true".equalsIgnoreCase(deleteMidVersions)); + this.setDeleteMidVersions(Optional.ofNullable(this.getConfigManager().getParam(JpversioningSystemConstants.CONFIG_PARAM_DELETE_MID_VERSIONS)) + .map(Boolean::parseBoolean).orElse(Boolean.TRUE)); } @Before("execution(* com.agiletec.plugins.jacms.aps.system.services.content.IContentManager.saveContent(..)) && args(content)") diff --git a/versioning-plugin/src/main/resources/liquibase/jpversioning/changeSetPort.xml b/versioning-plugin/src/main/resources/liquibase/jpversioning/changeSetPort.xml index 7a3f4bc921..d10168f236 100644 --- a/versioning-plugin/src/main/resources/liquibase/jpversioning/changeSetPort.xml +++ b/versioning-plugin/src/main/resources/liquibase/jpversioning/changeSetPort.xml @@ -9,4 +9,6 @@ + + diff --git a/versioning-plugin/src/main/resources/liquibase/jpversioning/port/20240308000000_jpversioning_resource_descr.xml b/versioning-plugin/src/main/resources/liquibase/jpversioning/port/20240308000000_jpversioning_resource_descr.xml new file mode 100644 index 0000000000..daa067365d --- /dev/null +++ b/versioning-plugin/src/main/resources/liquibase/jpversioning/port/20240308000000_jpversioning_resource_descr.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/versioning-plugin/src/main/webapp/WEB-INF/plugins/jpversioning/apsadmin/jsp/config/versioningSysConfig.jsp b/versioning-plugin/src/main/webapp/WEB-INF/plugins/jpversioning/apsadmin/jsp/config/versioningSysConfig.jsp index 356ce907a4..207ba9e947 100644 --- a/versioning-plugin/src/main/webapp/WEB-INF/plugins/jpversioning/apsadmin/jsp/config/versioningSysConfig.jsp +++ b/versioning-plugin/src/main/webapp/WEB-INF/plugins/jpversioning/apsadmin/jsp/config/versioningSysConfig.jsp @@ -77,10 +77,16 @@
- " id="" + + + + + + + " id="" name="" /> - " id="ch_" class="bootstrap-switch" - checked="checked" /> + " id="ch_" class="bootstrap-switch" + checked="checked" />
diff --git a/versioning-plugin/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningDAO.java b/versioning-plugin/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningDAOTest.java similarity index 99% rename from versioning-plugin/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningDAO.java rename to versioning-plugin/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningDAOTest.java index aae6048300..caeeee9973 100644 --- a/versioning-plugin/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningDAO.java +++ b/versioning-plugin/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningDAOTest.java @@ -43,7 +43,7 @@ /** * @author G.Cocco */ -public class TestVersioningDAO extends BaseTestCase { +class VersioningDAOTest extends BaseTestCase { @Test void testGetVersions() throws Throwable { diff --git a/versioning-plugin/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java b/versioning-plugin/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManagerIntegrationTest.java similarity index 97% rename from versioning-plugin/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java rename to versioning-plugin/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManagerIntegrationTest.java index e443263c05..6905bb07b7 100644 --- a/versioning-plugin/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java +++ b/versioning-plugin/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManagerIntegrationTest.java @@ -47,17 +47,19 @@ import java.util.Map; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; /** * @author G.Cocco */ -public class TestVersioningManager extends BaseTestCase { +class VersioningManagerIntegrationTest extends BaseTestCase { private ConfigInterface configManager; private IContentManager contentManager; private IVersioningManager versioningManager; private JpversioningTestHelper helper; + @Test void testGetVersions() throws Throwable { List versions = this.versioningManager.getVersions("CNG12"); assertNull(versions); @@ -66,6 +68,7 @@ void testGetVersions() throws Throwable { this.checkVersionIds(new long[]{1, 2, 3}, versions); } + @Test void testGetLastVersions() throws Throwable { List versions = this.versioningManager.getLastVersions("CNG", null); assertTrue(versions.isEmpty()); @@ -74,6 +77,7 @@ void testGetLastVersions() throws Throwable { this.checkVersionIds(new long[]{3}, versions); } + @Test void testGetVersion() throws Throwable { ContentVersion contentVersion = this.versioningManager.getVersion(10000); assertNull(contentVersion); @@ -92,6 +96,7 @@ void testGetVersion() throws Throwable { assertEquals("admin", contentVersion.getUsername()); } + @Test void testGetLastVersion() throws Throwable { ContentVersion contentVersion = this.versioningManager.getLastVersion("CNG12"); assertNull(contentVersion); @@ -109,6 +114,7 @@ void testGetLastVersion() throws Throwable { assertEquals("mainEditor", contentVersion.getUsername()); } + @Test void testSaveGetDeleteVersion() throws Throwable { ((VersioningManager) this.versioningManager).saveContentVersion("ART102"); ContentVersion contentVersion = this.versioningManager.getLastVersion("ART102"); @@ -128,7 +134,8 @@ void testSaveGetDeleteVersion() throws Throwable { assertNull(this.versioningManager.getLastVersion("ART102")); } - public void deleteWorkVersions() throws Throwable { + @Test + void deleteWorkVersions() throws Throwable { List versions = this.versioningManager.getVersions("ART1"); this.checkVersionIds(new long[]{1, 2, 3}, versions); this.versioningManager.deleteWorkVersions("ART1", 0); @@ -145,11 +152,13 @@ private void checkVersionIds(long[] expected, List received) { } } + @Test void testContentVersionToIgnore_1() throws Exception { this.testContentVersionToIgnore(false, true); this.testContentVersionToIgnore(true, true); } + @Test void testContentVersionToIgnore_2() throws Exception { this.testContentVersionToIgnore(false, false); this.testContentVersionToIgnore(true, false); diff --git a/versioning-plugin/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManagerTest.java b/versioning-plugin/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManagerTest.java new file mode 100644 index 0000000000..82e63566a4 --- /dev/null +++ b/versioning-plugin/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManagerTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2024-Present Entando Inc. (http://www.entando.com) All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.agiletec.plugins.jpversioning.aps.system.services.versioning; + +import com.agiletec.aps.system.services.baseconfig.ConfigInterface; +import com.agiletec.plugins.jpversioning.aps.system.JpversioningSystemConstants; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class VersioningManagerTest { + + @Mock + private ConfigInterface configManager; + + @InjectMocks + private VersioningManager versioningManager; + + @Test + void checkRightConfiguration() throws Exception { + this.checkRightConfiguration(null, true); + this.checkRightConfiguration("false", false); + this.checkRightConfiguration("true", true); + this.checkRightConfiguration("True", true); + this.checkRightConfiguration("TRUE", true); + this.checkRightConfiguration("False", false); + this.checkRightConfiguration("FALSE", false); + } + + private void checkRightConfiguration(String deleteMidVersion, boolean expected) throws Exception { + String paramName = JpversioningSystemConstants.CONFIG_PARAM_DELETE_MID_VERSIONS; + Mockito.when(configManager.getParam(paramName)).thenReturn(deleteMidVersion); + versioningManager.init(); + Assertions.assertEquals(expected, versioningManager.isDeleteMidVersions()); + } + +} diff --git a/webapp/pom.xml b/webapp/pom.xml index 268f1e3685..8d10971ce9 100644 --- a/webapp/pom.xml +++ b/webapp/pom.xml @@ -5,7 +5,7 @@ org.entando app-engine - 7.3.0 + 7.3.1 org.entando.entando webapp @@ -826,7 +826,7 @@ 3.0.0 - 11 + 17 @@ -837,8 +837,8 @@ org.apache.maven.plugins maven-compiler-plugin - 11 - 11 + 17 + 17 diff --git a/webapp/src/main/webapp/META-INF/context.xml b/webapp/src/main/webapp/META-INF/context.xml new file mode 100644 index 0000000000..1e49cf5ea3 --- /dev/null +++ b/webapp/src/main/webapp/META-INF/context.xml @@ -0,0 +1,2 @@ + + diff --git a/webapp/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/webapp/src/main/webapp/WEB-INF/jboss-deployment-structure.xml deleted file mode 100644 index 66dc3f16b9..0000000000 --- a/webapp/src/main/webapp/WEB-INF/jboss-deployment-structure.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file