From 8f9248789efed27298bb2754a7b5307c3fc7e9d6 Mon Sep 17 00:00:00 2001 From: "m.panagrosso" Date: Tue, 31 Oct 2023 17:45:39 +0100 Subject: [PATCH 01/82] ENG-5270: avatar end points --- .../web/userprofile/ProfileController.java | 118 ++++++++++++++++++ .../model/ProfileAvatarRequest.java | 45 +++++++ .../validator/ProfileAvatarValidator.java | 63 ++++++++++ .../main/resources/rest/messages.properties | 1 + 4 files changed, 227 insertions(+) create mode 100644 engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarRequest.java create mode 100644 engine/src/main/java/org/entando/entando/web/userprofile/validator/ProfileAvatarValidator.java 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..7c55036e10 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 @@ -17,9 +17,19 @@ import com.agiletec.aps.system.services.user.IUserManager; import com.agiletec.aps.system.services.user.UserDetails; import com.fasterxml.jackson.core.JsonProcessingException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import javax.imageio.ImageIO; 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.storage.IFileBrowserService; 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; @@ -28,8 +38,12 @@ 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.filebrowser.model.FileBrowserFileRequest; +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; @@ -54,12 +68,26 @@ public class ProfileController { @Autowired private ProfileValidator profileValidator; + @Autowired + private ProfileAvatarValidator profileAvatarValidator; + @Autowired private IUserManager userManager; @Autowired private IUserProfileManager userProfileManager; + @Autowired + private IFileBrowserService fileBrowserService; + + public static final String FILE_NAME = "fileName"; + + public static final String PROTECTED_FOLDER = "protectedFolder"; + + private static final Object DEFAULT_AVATAR_PATH = "static/profile"; + + public static final String PREV_PATH = "prevPath"; + protected IUserProfileService getUserProfileService() { return userProfileService; } @@ -72,6 +100,10 @@ public ProfileValidator getProfileValidator() { return profileValidator; } + public IFileBrowserService getFileBrowserService() { + return fileBrowserService; + } + public void setProfileValidator(ProfileValidator profileValidator) { this.profileValidator = profileValidator; } @@ -179,4 +211,90 @@ public ResponseEntity> updateMyUserProfile(@Reques return new ResponseEntity<>(new SimpleRestResponse<>(response), HttpStatus.OK); } + @RequestMapping(value = "/userProfiles/avatar", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity, Map>> getFile( + @RequestParam(value = FILE_NAME, required = false, defaultValue = "") String fileName) throws IOException { + + // set fixed params + boolean protectedFolder = false; + String currentPath = DEFAULT_AVATAR_PATH + "/" + fileName; + // validate fileName using java NIO2 api (to avoid for instance \0) + Paths.get(currentPath); + // validate fileName to check if contains path to avoid directory listing + if (fileName.contains("/")) { + throw new IllegalArgumentException("The requested file name is not valid"); + } + // get file from volume or else throw exception + byte[] base64 = this.getFileBrowserService().getFileStream(currentPath, protectedFolder); + // check if the desired file is an image, otherwise throw exception + if (ImageIO.read(new ByteArrayInputStream(base64)) == null) { + throw new IllegalArgumentException("The requested file is not an image"); + } + + // prepare output + Map result = new HashMap<>(); + result.put(PROTECTED_FOLDER, protectedFolder); + result.put("isDirectory", false); + result.put("path", currentPath); + result.put("filename", fileName); + result.put("base64", base64); + + Map metadata = new HashMap<>(); + metadata.put(PREV_PATH, DEFAULT_AVATAR_PATH); + return new ResponseEntity<>(new RestResponse<>(result, metadata), HttpStatus.OK); + } + + + @RequestMapping(value = "/userProfiles/avatar", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity, Map>> addFile( + @Valid @RequestBody ProfileAvatarRequest request, + BindingResult bindingResult) { + return executeUpsert(request, bindingResult, this.getFileBrowserService()::addFile); + + } + + @RequestMapping(value = "/userProfiles/avatar", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity, Map>> updateFile( + @Valid @RequestBody ProfileAvatarRequest request, BindingResult bindingResult) { + return executeUpsert(request, bindingResult, this.getFileBrowserService()::updateFile); + } + + private ResponseEntity, Map>> executeUpsert( + ProfileAvatarRequest request, BindingResult bindingResult, + BiConsumer upsertFunction) { + if (bindingResult.hasErrors()) { + throw new ValidationGenericException(bindingResult); + } + // validate input dto to check for consistency of input + profileAvatarValidator.validate(request, bindingResult); + if (bindingResult.hasErrors()) { + throw new ValidationGenericException(bindingResult); + } + // prepare a FileBrowserFileRequest to use the api already available in the system + FileBrowserFileRequest fileBrowserFileRequest = convertToFileBrowserFileRequest(request); + // add the file to the volume + upsertFunction.accept(fileBrowserFileRequest,bindingResult); + // prepare and return a consistent response + return this.composeAvatarUpsertResponse(request); + } + + private static FileBrowserFileRequest convertToFileBrowserFileRequest(ProfileAvatarRequest request) { + FileBrowserFileRequest fileBrowserFileRequest = new FileBrowserFileRequest(); + fileBrowserFileRequest.setFilename(request.getFilename()); + fileBrowserFileRequest.setPath(DEFAULT_AVATAR_PATH + "/" + request.getFilename()); + fileBrowserFileRequest.setProtectedFolder(false); + fileBrowserFileRequest.setBase64(request.getBase64()); + return fileBrowserFileRequest; + } + + public ResponseEntity, Map>> composeAvatarUpsertResponse( + ProfileAvatarRequest request) { + Map result = new HashMap<>(); + result.put(PROTECTED_FOLDER, false); + result.put("path", DEFAULT_AVATAR_PATH + "/" + request.getFilename()); + result.put("filename", request.getFilename()); + Map metadata = new HashMap<>(); + metadata.put(PREV_PATH, DEFAULT_AVATAR_PATH); + return new ResponseEntity<>(new RestResponse<>(result, metadata), 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..5dd5e34741 --- /dev/null +++ b/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarRequest.java @@ -0,0 +1,45 @@ +/* + * 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 javax.validation.constraints.NotNull; +import org.entando.entando.web.filebrowser.model.FileBrowserRequest; +import org.hibernate.validator.constraints.NotBlank; + +/** + * @author E.Santoboni + */ +public class ProfileAvatarRequest { + + @NotBlank(message = "fileBrowser.filename.notBlank") + private String filename; + @NotNull(message = "fileBrowser.base64.notBlank") + private byte[] base64; + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public byte[] getBase64() { + return base64; + } + + public void setBase64(byte[] base64) { + this.base64 = base64; + } + +} 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..1672398a80 --- /dev/null +++ b/engine/src/main/java/org/entando/entando/web/userprofile/validator/ProfileAvatarValidator.java @@ -0,0 +1,63 @@ +/* + * 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.validator; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import javax.imageio.ImageIO; +import org.entando.entando.ent.util.EntLogging.EntLogFactory; +import org.entando.entando.ent.util.EntLogging.EntLogger; +import org.entando.entando.web.filebrowser.model.FileBrowserFileRequest; +import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; +import org.springframework.stereotype.Component; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; + +/** + * @author eu + */ +@Component +public class ProfileAvatarValidator implements Validator { + + private final EntLogger logger = EntLogFactory.getSanitizedLogger(this.getClass()); + + public static final String ERRCODE_INVALID_FILE_NAME = "1"; + public static final String ERRCODE_INVALID_FILE_TYPE = "2"; + + @Override + public boolean supports(Class paramClass) { + return (FileBrowserFileRequest.class.equals(paramClass)); + } + + @Override + public void validate(Object target, Errors errors) { + ProfileAvatarRequest request = (ProfileAvatarRequest) target; + String filename = request.getFilename(); + if (filename.contains("/")) { + errors.rejectValue("path", ERRCODE_INVALID_FILE_NAME, new String[]{filename}, "fileBrowser.filename.invalidFilename"); + return; + } + + try { + if (ImageIO.read(new ByteArrayInputStream(request.getBase64())) == null) { + errors.rejectValue("path", ERRCODE_INVALID_FILE_TYPE,"fileBrowser.file.invalidType"); + throw new IllegalArgumentException("The requested file is not an image"); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + +} diff --git a/engine/src/main/resources/rest/messages.properties b/engine/src/main/resources/rest/messages.properties index 7eda70d3d0..a5f4c8efcb 100644 --- a/engine/src/main/resources/rest/messages.properties +++ b/engine/src/main/resources/rest/messages.properties @@ -346,6 +346,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 From 79c542c59f6ad8ab969b7b2b82feda852805b708 Mon Sep 17 00:00:00 2001 From: "m.panagrosso" Date: Thu, 2 Nov 2023 12:11:58 +0100 Subject: [PATCH 02/82] ENG-5270: fix code smell --- .../web/userprofile/ProfileController.java | 16 +++++++--------- .../userprofile/model/ProfileAvatarRequest.java | 3 +-- .../validator/ProfileAvatarValidator.java | 13 +++++++------ 3 files changed, 15 insertions(+), 17 deletions(-) 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 7c55036e10..75fb4f0da7 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 @@ -23,8 +23,6 @@ import java.util.HashMap; import java.util.Map; import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; import javax.imageio.ImageIO; import org.entando.entando.aps.system.exception.ResourceNotFoundException; import org.entando.entando.aps.system.exception.RestServerError; @@ -84,7 +82,7 @@ public class ProfileController { public static final String PROTECTED_FOLDER = "protectedFolder"; - private static final Object DEFAULT_AVATAR_PATH = "static/profile"; + private static final String DEFAULT_AVATAR_PATH = "static/profile"; public static final String PREV_PATH = "prevPath"; @@ -103,7 +101,7 @@ public ProfileValidator getProfileValidator() { public IFileBrowserService getFileBrowserService() { return fileBrowserService; } - + public void setProfileValidator(ProfileValidator profileValidator) { this.profileValidator = profileValidator; } @@ -211,7 +209,7 @@ public ResponseEntity> updateMyUserProfile(@Reques return new ResponseEntity<>(new SimpleRestResponse<>(response), HttpStatus.OK); } - @RequestMapping(value = "/userProfiles/avatar", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + @GetMapping(path = "/userProfiles/avatar", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity, Map>> getFile( @RequestParam(value = FILE_NAME, required = false, defaultValue = "") String fileName) throws IOException { @@ -245,7 +243,7 @@ public ResponseEntity, Map>> ge } - @RequestMapping(value = "/userProfiles/avatar", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @PostMapping(path = "/userProfiles/avatar", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity, Map>> addFile( @Valid @RequestBody ProfileAvatarRequest request, BindingResult bindingResult) { @@ -253,7 +251,7 @@ public ResponseEntity, Map>> ad } - @RequestMapping(value = "/userProfiles/avatar", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE) + @PutMapping(path = "/userProfiles/avatar", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity, Map>> updateFile( @Valid @RequestBody ProfileAvatarRequest request, BindingResult bindingResult) { return executeUpsert(request, bindingResult, this.getFileBrowserService()::updateFile); @@ -281,7 +279,7 @@ private ResponseEntity, Map>> e private static FileBrowserFileRequest convertToFileBrowserFileRequest(ProfileAvatarRequest request) { FileBrowserFileRequest fileBrowserFileRequest = new FileBrowserFileRequest(); fileBrowserFileRequest.setFilename(request.getFilename()); - fileBrowserFileRequest.setPath(DEFAULT_AVATAR_PATH + "/" + request.getFilename()); + fileBrowserFileRequest.setPath(Paths.get(DEFAULT_AVATAR_PATH, request.getFilename()).toString()); fileBrowserFileRequest.setProtectedFolder(false); fileBrowserFileRequest.setBase64(request.getBase64()); return fileBrowserFileRequest; @@ -291,7 +289,7 @@ public ResponseEntity, Map>> co ProfileAvatarRequest request) { Map result = new HashMap<>(); result.put(PROTECTED_FOLDER, false); - result.put("path", DEFAULT_AVATAR_PATH + "/" + request.getFilename()); + result.put("path", Paths.get(DEFAULT_AVATAR_PATH, request.getFilename()).toString()); result.put("filename", request.getFilename()); Map metadata = new HashMap<>(); metadata.put(PREV_PATH, DEFAULT_AVATAR_PATH); 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 index 5dd5e34741..833ed8ef60 100644 --- 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 @@ -13,9 +13,8 @@ */ package org.entando.entando.web.userprofile.model; +import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; -import org.entando.entando.web.filebrowser.model.FileBrowserRequest; -import org.hibernate.validator.constraints.NotBlank; /** * @author E.Santoboni 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 index 1672398a80..db169deb39 100644 --- 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 @@ -21,6 +21,8 @@ import org.entando.entando.ent.util.EntLogging.EntLogger; import org.entando.entando.web.filebrowser.model.FileBrowserFileRequest; import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; +import org.springframework.lang.NonNull; +import org.springframework.lang.NonNullApi; import org.springframework.stereotype.Component; import org.springframework.validation.Errors; import org.springframework.validation.Validator; @@ -31,28 +33,27 @@ @Component public class ProfileAvatarValidator implements Validator { - private final EntLogger logger = EntLogFactory.getSanitizedLogger(this.getClass()); - public static final String ERRCODE_INVALID_FILE_NAME = "1"; public static final String ERRCODE_INVALID_FILE_TYPE = "2"; @Override - public boolean supports(Class paramClass) { + public boolean supports(@NonNull Class paramClass) { return (FileBrowserFileRequest.class.equals(paramClass)); } @Override - public void validate(Object target, Errors errors) { + public void validate(@NonNull Object target, @NonNull Errors errors) { ProfileAvatarRequest request = (ProfileAvatarRequest) target; String filename = request.getFilename(); if (filename.contains("/")) { - errors.rejectValue("path", ERRCODE_INVALID_FILE_NAME, new String[]{filename}, "fileBrowser.filename.invalidFilename"); + errors.rejectValue("path", ERRCODE_INVALID_FILE_NAME, new String[]{filename}, + "fileBrowser.filename.invalidFilename"); return; } try { if (ImageIO.read(new ByteArrayInputStream(request.getBase64())) == null) { - errors.rejectValue("path", ERRCODE_INVALID_FILE_TYPE,"fileBrowser.file.invalidType"); + errors.rejectValue("path", ERRCODE_INVALID_FILE_TYPE, "fileBrowser.file.invalidType"); throw new IllegalArgumentException("The requested file is not an image"); } } catch (IOException e) { From dc61ade14667ddfdd888d7273e126a2b439fdf35 Mon Sep 17 00:00:00 2001 From: "m.panagrosso" Date: Thu, 2 Nov 2023 12:52:00 +0100 Subject: [PATCH 03/82] ENG-5270: fix more code smells --- .../web/userprofile/ProfileController.java | 79 ++++++++----------- .../model/ProfileAvatarRequest.java | 17 +--- .../validator/ProfileAvatarValidator.java | 3 - .../UserProfileControllerTest.java | 16 +++- 4 files changed, 46 insertions(+), 69 deletions(-) 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 75fb4f0da7..04590c6c7e 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 @@ -24,6 +24,8 @@ import java.util.Map; import java.util.function.BiConsumer; import javax.imageio.ImageIO; +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; @@ -43,40 +45,41 @@ 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.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.RequestParam; +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 ProfileAvatarValidator profileAvatarValidator; + private final ProfileAvatarValidator profileAvatarValidator; - @Autowired - private IUserManager userManager; + private final IUserManager userManager; - @Autowired - private IUserProfileManager userProfileManager; + private final IUserProfileManager userProfileManager; - @Autowired - private IFileBrowserService fileBrowserService; + private final IFileBrowserService fileBrowserService; public static final String FILE_NAME = "fileName"; @@ -86,26 +89,6 @@ public class ProfileController { public static final String PREV_PATH = "prevPath"; - protected IUserProfileService getUserProfileService() { - return userProfileService; - } - - public void setUserProfileService(IUserProfileService userProfileService) { - this.userProfileService = userProfileService; - } - - public ProfileValidator getProfileValidator() { - return profileValidator; - } - - public IFileBrowserService getFileBrowserService() { - return fileBrowserService; - } - - public void setProfileValidator(ProfileValidator profileValidator) { - this.profileValidator = profileValidator; - } - @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 { @@ -125,7 +108,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); @@ -134,7 +117,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; } @@ -166,11 +149,11 @@ public ResponseEntity> addUserProfile(@Valid @Requ 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); } @@ -185,8 +168,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); } @@ -198,11 +181,11 @@ public ResponseEntity> updateUserProfile(@PathVari public ResponseEntity> updateMyUserProfile(@RequestAttribute("user") UserDetails user, @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 = this.getUserProfileService().updateUserProfile(bodyRequest, bindingResult); + EntityDto response = userProfileService.updateUserProfile(bodyRequest, bindingResult); if (bindingResult.hasErrors()) { throw new ValidationGenericException(bindingResult); } @@ -215,7 +198,7 @@ public ResponseEntity, Map>> ge // set fixed params boolean protectedFolder = false; - String currentPath = DEFAULT_AVATAR_PATH + "/" + fileName; + String currentPath = Paths.get(DEFAULT_AVATAR_PATH, fileName).toString(); // validate fileName using java NIO2 api (to avoid for instance \0) Paths.get(currentPath); // validate fileName to check if contains path to avoid directory listing @@ -223,7 +206,7 @@ public ResponseEntity, Map>> ge throw new IllegalArgumentException("The requested file name is not valid"); } // get file from volume or else throw exception - byte[] base64 = this.getFileBrowserService().getFileStream(currentPath, protectedFolder); + byte[] base64 = fileBrowserService.getFileStream(currentPath, protectedFolder); // check if the desired file is an image, otherwise throw exception if (ImageIO.read(new ByteArrayInputStream(base64)) == null) { throw new IllegalArgumentException("The requested file is not an image"); @@ -247,14 +230,14 @@ public ResponseEntity, Map>> ge public ResponseEntity, Map>> addFile( @Valid @RequestBody ProfileAvatarRequest request, BindingResult bindingResult) { - return executeUpsert(request, bindingResult, this.getFileBrowserService()::addFile); + return executeUpsert(request, bindingResult, fileBrowserService::addFile); } @PutMapping(path = "/userProfiles/avatar", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity, Map>> updateFile( @Valid @RequestBody ProfileAvatarRequest request, BindingResult bindingResult) { - return executeUpsert(request, bindingResult, this.getFileBrowserService()::updateFile); + return executeUpsert(request, bindingResult, fileBrowserService::updateFile); } private ResponseEntity, Map>> executeUpsert( 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 index 833ed8ef60..56cbc396b7 100644 --- 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 @@ -15,30 +15,17 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; +import lombok.Data; /** * @author E.Santoboni */ +@Data public class ProfileAvatarRequest { @NotBlank(message = "fileBrowser.filename.notBlank") private String filename; @NotNull(message = "fileBrowser.base64.notBlank") private byte[] base64; - public String getFilename() { - return filename; - } - - public void setFilename(String filename) { - this.filename = filename; - } - - public byte[] getBase64() { - return base64; - } - - public void setBase64(byte[] base64) { - this.base64 = base64; - } } 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 index db169deb39..999a0e1a1b 100644 --- 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 @@ -17,12 +17,9 @@ import java.io.IOException; import java.io.UncheckedIOException; import javax.imageio.ImageIO; -import org.entando.entando.ent.util.EntLogging.EntLogFactory; -import org.entando.entando.ent.util.EntLogging.EntLogger; import org.entando.entando.web.filebrowser.model.FileBrowserFileRequest; import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; import org.springframework.lang.NonNull; -import org.springframework.lang.NonNullApi; import org.springframework.stereotype.Component; import org.springframework.validation.Errors; import org.springframework.validation.Validator; 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..89fabd3e0b 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 @@ -14,6 +14,8 @@ package org.entando.entando.web.userprofile; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; 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; @@ -24,10 +26,12 @@ import com.agiletec.aps.system.services.user.IUserManager; import com.agiletec.aps.system.services.user.UserDetails; import org.entando.entando.aps.system.services.entity.model.EntityDto; +import org.entando.entando.aps.system.services.storage.IFileBrowserService; 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.AbstractControllerTest; +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.junit.jupiter.api.BeforeEach; @@ -38,6 +42,7 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -58,12 +63,17 @@ class UserProfileControllerTest extends AbstractControllerTest { @Mock private IUserProfileManager userProfileManager; - @InjectMocks - private ProfileController controller; + @Mock + private ProfileAvatarValidator profileAvatarValidator; + + @Mock + private IFileBrowserService fileBrowserService; @BeforeEach public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); + ProfileController controller = new ProfileController(userProfileService, profileValidator, + profileAvatarValidator, userManager, + userProfileManager, fileBrowserService); mockMvc = MockMvcBuilders.standaloneSetup(controller) .addInterceptors(entandoOauth2Interceptor) .setMessageConverters(getMessageConverters()) From 826cf5f8e2970dc64516e4465182b2f7ddff0ba5 Mon Sep 17 00:00:00 2001 From: "m.panagrosso" Date: Thu, 2 Nov 2023 12:52:40 +0100 Subject: [PATCH 04/82] ENG-5270: fix on formatting --- .../entando/web/userprofile/ProfileController.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) 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 04590c6c7e..5d601e108e 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 @@ -91,7 +91,8 @@ public class ProfileController { @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) + throws JsonProcessingException { logger.debug("Requested profile -> {}", username); final EntityDto dto = getUserProfileEntityDto(username); logger.debug("Main Response -> {}", dto); @@ -144,7 +145,8 @@ 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); @@ -179,7 +181,7 @@ 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); profileValidator.validateBodyName(user.getUsername(), bodyRequest, bindingResult); if (bindingResult.hasErrors()) { @@ -198,7 +200,7 @@ public ResponseEntity, Map>> ge // set fixed params boolean protectedFolder = false; - String currentPath = Paths.get(DEFAULT_AVATAR_PATH, fileName).toString(); + String currentPath = Paths.get(DEFAULT_AVATAR_PATH, fileName).toString(); // validate fileName using java NIO2 api (to avoid for instance \0) Paths.get(currentPath); // validate fileName to check if contains path to avoid directory listing @@ -254,7 +256,7 @@ private ResponseEntity, Map>> e // prepare a FileBrowserFileRequest to use the api already available in the system FileBrowserFileRequest fileBrowserFileRequest = convertToFileBrowserFileRequest(request); // add the file to the volume - upsertFunction.accept(fileBrowserFileRequest,bindingResult); + upsertFunction.accept(fileBrowserFileRequest, bindingResult); // prepare and return a consistent response return this.composeAvatarUpsertResponse(request); } From e7c431cd134371dca84443e905f52185b7df7c23 Mon Sep 17 00:00:00 2001 From: "m.panagrosso" Date: Thu, 2 Nov 2023 20:52:26 +0100 Subject: [PATCH 05/82] ENG-5270: fix and test coverage --- .../web/userprofile/ProfileController.java | 20 ++- .../model/ProfileAvatarRequest.java | 10 +- .../validator/ProfileAvatarValidator.java | 5 +- .../UserProfileControllerTest.java | 136 ++++++++++++++++++ 4 files changed, 154 insertions(+), 17 deletions(-) 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 5d601e108e..1b38471882 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,7 +16,6 @@ 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.io.ByteArrayInputStream; import java.io.IOException; import java.nio.file.Paths; @@ -91,8 +90,7 @@ public class ProfileController { @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); @@ -195,22 +193,22 @@ public ResponseEntity> updateMyUserProfile(@Reques } @GetMapping(path = "/userProfiles/avatar", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity, Map>> getFile( + public ResponseEntity, Map>> getAvatar( @RequestParam(value = FILE_NAME, required = false, defaultValue = "") String fileName) throws IOException { // set fixed params boolean protectedFolder = false; String currentPath = Paths.get(DEFAULT_AVATAR_PATH, fileName).toString(); - // validate fileName using java NIO2 api (to avoid for instance \0) - Paths.get(currentPath); // validate fileName to check if contains path to avoid directory listing if (fileName.contains("/")) { + logger.error("Directory listing attempt in an avatar GET request"); throw new IllegalArgumentException("The requested file name is not valid"); } // get file from volume or else throw exception byte[] base64 = fileBrowserService.getFileStream(currentPath, protectedFolder); // check if the desired file is an image, otherwise throw exception if (ImageIO.read(new ByteArrayInputStream(base64)) == null) { + logger.error("Attempt to request a file that is not an image in an avatar GET request"); throw new IllegalArgumentException("The requested file is not an image"); } @@ -221,7 +219,6 @@ public ResponseEntity, Map>> ge result.put("path", currentPath); result.put("filename", fileName); result.put("base64", base64); - Map metadata = new HashMap<>(); metadata.put(PREV_PATH, DEFAULT_AVATAR_PATH); return new ResponseEntity<>(new RestResponse<>(result, metadata), HttpStatus.OK); @@ -229,15 +226,14 @@ public ResponseEntity, Map>> ge @PostMapping(path = "/userProfiles/avatar", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity, Map>> addFile( + public ResponseEntity, Map>> addAvatar( @Valid @RequestBody ProfileAvatarRequest request, BindingResult bindingResult) { return executeUpsert(request, bindingResult, fileBrowserService::addFile); - } @PutMapping(path = "/userProfiles/avatar", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity, Map>> updateFile( + public ResponseEntity, Map>> updateAvatar( @Valid @RequestBody ProfileAvatarRequest request, BindingResult bindingResult) { return executeUpsert(request, bindingResult, fileBrowserService::updateFile); } @@ -258,7 +254,7 @@ private ResponseEntity, Map>> e // add the file to the volume upsertFunction.accept(fileBrowserFileRequest, bindingResult); // prepare and return a consistent response - return this.composeAvatarUpsertResponse(request); + return composeAvatarUpsertResponse(request); } private static FileBrowserFileRequest convertToFileBrowserFileRequest(ProfileAvatarRequest request) { @@ -270,7 +266,7 @@ private static FileBrowserFileRequest convertToFileBrowserFileRequest(ProfileAva return fileBrowserFileRequest; } - public ResponseEntity, Map>> composeAvatarUpsertResponse( + private ResponseEntity, Map>> composeAvatarUpsertResponse( ProfileAvatarRequest request) { Map result = new HashMap<>(); result.put(PROTECTED_FOLDER, false); 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 index 56cbc396b7..56e73b0d04 100644 --- 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 @@ -15,12 +15,18 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; -import lombok.Data; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; /** * @author E.Santoboni */ -@Data +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter public class ProfileAvatarRequest { @NotBlank(message = "fileBrowser.filename.notBlank") 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 index 999a0e1a1b..bade613260 100644 --- 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 @@ -43,15 +43,14 @@ public void validate(@NonNull Object target, @NonNull Errors errors) { ProfileAvatarRequest request = (ProfileAvatarRequest) target; String filename = request.getFilename(); if (filename.contains("/")) { - errors.rejectValue("path", ERRCODE_INVALID_FILE_NAME, new String[]{filename}, + errors.rejectValue("filename", ERRCODE_INVALID_FILE_NAME, new String[]{filename}, "fileBrowser.filename.invalidFilename"); return; } try { if (ImageIO.read(new ByteArrayInputStream(request.getBase64())) == null) { - errors.rejectValue("path", ERRCODE_INVALID_FILE_TYPE, "fileBrowser.file.invalidType"); - throw new IllegalArgumentException("The requested file is not an image"); + errors.rejectValue("base64", ERRCODE_INVALID_FILE_TYPE, "fileBrowser.file.invalidType"); } } catch (IOException e) { throw new UncheckedIOException(e); 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 89fabd3e0b..5a341f84b6 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,6 +15,8 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -25,12 +27,21 @@ 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.awt.image.BufferedImage; +import java.io.InputStream; +import java.util.Arrays; +import javax.imageio.ImageIO; +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.storage.IFileBrowserService; 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.AbstractControllerTest; +import org.entando.entando.web.common.exceptions.ValidationConflictException; +import org.entando.entando.web.filebrowser.validator.FileBrowserValidator; +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; @@ -39,9 +50,11 @@ 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.MockitoAnnotations; import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.ResultActions; @@ -137,6 +150,129 @@ void testUpdateProfile() throws Exception { result.andExpect(status().isOk()); } + @Test + void shouldGetAvatarReturn500OnDirectoryListing() throws Exception { + String accessToken = this.createAccessToken(); + ResultActions result = mockMvc.perform( + get("/userProfiles/avatar?fileName=some/dir/myFile.png") + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().is5xxServerError()); + } + + @Test + void shouldGetAvatarReturn500OnWrongFileType() throws Exception { + String accessToken = this.createAccessToken(); + String notImageBase64 = "SGVsbG8="; + when(fileBrowserService.getFileStream(any(), any())).thenReturn(notImageBase64.getBytes()); + + ResultActions result = mockMvc.perform( + get("/userProfiles/avatar?fileName=myFile.txt") + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().is5xxServerError()); + } + + @Test + void shouldGetAvatarReturn404IfImageNotExists() throws Exception { + String accessToken = this.createAccessToken(); + when(fileBrowserService.getFileStream(any(), any())).thenThrow( + new ResourceNotFoundException("1", "File", "static/profile/myFile.png")); + + ResultActions result = mockMvc.perform( + get("/userProfiles/avatar?fileName=myFile.png") + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().isNotFound()); + } + + @Test + void shouldGetAvatarReturn200AndWellFormedResponseIfImageExists() throws Exception { + String accessToken = this.createAccessToken(); + String pretendIsAnImageBase64 = "cHJldGVuZCBpcyBhbiBpbWFnZQ=="; + when(fileBrowserService.getFileStream(any(), any())).thenReturn(pretendIsAnImageBase64.getBytes()); + + try (MockedStatic utilities = Mockito.mockStatic(ImageIO.class)) { + utilities.when(() -> ImageIO.read(any(InputStream.class))) + .thenReturn(mock(BufferedImage.class)); + + ResultActions result = mockMvc.perform( + get("/userProfiles/avatar?fileName=myFile.png") + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().isOk()); + } + } + + @Test + void shouldPostAvatarReturn409IfImageAlreadyExists() throws Exception { + String accessToken = this.createAccessToken(); + doThrow(new ValidationConflictException(mock(BindingResult.class))).when(fileBrowserService) + .addFile(any(), any()); + + byte[] pretendIsAnImageBase64 = "cHJldGVuZCBpcyBhbiBpbWFnZQ==".getBytes(); + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("myFile.png", pretendIsAnImageBase64); + ResultActions result = mockMvc.perform( + post("/userProfiles/avatar") + .content(new ObjectMapper().writeValueAsString(profileAvatarRequest)) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().isConflict()); + } + + @Test + void shouldPostAvatarReturn400OnIllegalInput() throws Exception { + String accessToken = this.createAccessToken(); + String notImageBase64 = "SGVsbG8="; + + Answer ans = invocation -> { + Object[] args = invocation.getArguments(); + ((BindingResult) args[1]).rejectValue("filename", "1", new String[]{"any/dir/fileName.png"}, + "fileBrowser.filename.invalidFilename"); + return null; + }; + doAnswer(ans).when(profileAvatarValidator).validate(any(), any()); + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("myFile.png", notImageBase64.getBytes()); + + 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 shouldPostAvatarReturn200OnRightInputIfFileIsNotAlreadyPresent() throws Exception { + String accessToken = this.createAccessToken(); + String pretendIsAnImageBase64 = "cHJldGVuZCBpcyBhbiBpbWFnZQ=="; + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("myFile.png", + pretendIsAnImageBase64.getBytes()); + + 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()); + } + + + @Test + void shouldPutAvatarReturn404IfImageNotExists() throws Exception { + String accessToken = this.createAccessToken(); + + doThrow(new ResourceNotFoundException("1", "File", "static/profile/notAlreadyExistingImage.png")).when( + fileBrowserService) + .updateFile(any(), any()); + byte[] pretendIsAnImageBase64 = "cHJldGVuZCBpcyBhbiBpbWFnZQ==".getBytes(); + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("notAlreadyExistingImage.png", + pretendIsAnImageBase64); + + ResultActions result = mockMvc.perform( + put("/userProfiles/avatar") + .content(new ObjectMapper().writeValueAsString(profileAvatarRequest)) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().isNotFound()); + } + private ResultActions performGetUserProfiles(String username) throws Exception { String accessToken = this.createAccessToken(); return mockMvc.perform( From 981117aaeeecf8713e9a4f26a9ba9728a5aeb5ff Mon Sep 17 00:00:00 2001 From: "m.panagrosso" Date: Fri, 3 Nov 2023 15:10:04 +0100 Subject: [PATCH 06/82] ENG-5270: profile validator test coverage --- .../validator/ProfileAvatarValidatorTest.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 engine/src/test/java/org/entando/entando/web/userprofile/validator/ProfileAvatarValidatorTest.java 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..b90622bcc4 --- /dev/null +++ b/engine/src/test/java/org/entando/entando/web/userprofile/validator/ProfileAvatarValidatorTest.java @@ -0,0 +1,67 @@ +package org.entando.entando.web.userprofile.validator; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import javax.imageio.ImageIO; +import org.apache.derby.iapi.types.SQLTinyint; +import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.validation.FieldError; + +class ProfileAvatarValidatorTest { + + private final static String validBase64Image = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAABhGlDQ1BJQ0MgcHJvZml" + + "sZQAAKJF9kT1Iw0AcxV9bpaItDnYQ6ZChOlkQLeKoVShChVArtOpgcukXNGlIUlwcBdeCgx+LVQcXZ10dXAVB8APE1cVJ0UVK/F9SaBH" + + "jwXE/3t173L0D/M0qU82eCUDVLCOTSgq5/KoQfMUAAggjioTETH1OFNPwHF/38PH1Ls6zvM/9OcJKwWSATyCeZbphEW8QT29aOud94gg" + + "rSwrxOfG4QRckfuS67PIb55LDfp4ZMbKZeeIIsVDqYrmLWdlQiRPEMUXVKN+fc1nhvMVZrdZZ+578haGCtrLMdZpRpLCIJYgQIKOOCqq" + + "wEKdVI8VEhvaTHv4Rxy+SSyZXBYwcC6hBheT4wf/gd7dmcWrSTQolgd4X2/4YBYK7QKth29/Htt06AQLPwJXW8deawMwn6Y2OFjsCBre" + + "Bi+uOJu8BlzvA8JMuGZIjBWj6i0Xg/Yy+KQ8M3QL9a25v7X2cPgBZ6ip9AxwcAmMlyl73eHdfd2//nmn39wN8yXKr1FLmsAAAAAlwSFl" + + "zAAAuIwAALiMBeKU/dgAAAAd0SU1FB+cLAw0dHTZCjPMAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAADElEQVQ" + + "I12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC"; + + private final static String notValidBase64Image = "bm90IGFuIGltYWdl"; + + @Test + void shouldNotValidateFileNamesThatContainsSlash() { + ProfileAvatarRequest request = new ProfileAvatarRequest("not/valid/filename.png", validBase64Image.getBytes()); + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); + new ProfileAvatarValidator().validate(request, errors); + assertEquals(1, errors.getErrorCount()); + assertEquals("fileBrowser.filename.invalidFilename", errors.getAllErrors().get(0).getDefaultMessage()); + assertEquals("not/valid/filename.png", ((FieldError) errors.getAllErrors().get(0)).getRejectedValue()); + } + + @Test + void shouldNotValidateFilesOtherThanImages() { + ProfileAvatarRequest request = new ProfileAvatarRequest("valid_filename.txt", notValidBase64Image.getBytes()); + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); + new ProfileAvatarValidator().validate(request, 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() { + ProfileAvatarRequest request = new ProfileAvatarRequest("valid_filename.png", validBase64Image.getBytes()); + 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, () -> new ProfileAvatarValidator().validate(request, errors)); + } + + } +} From 7466f065c3c1a237375ec81c4bc09ec1ef26ee99 Mon Sep 17 00:00:00 2001 From: "m.panagrosso" Date: Fri, 3 Nov 2023 15:59:37 +0100 Subject: [PATCH 07/82] ENG-5270: improved tests --- .../validator/ProfileAvatarValidatorTest.java | 40 +++++++++--------- .../src/test/resources/userprofile/image.png | Bin 0 -> 546 bytes 2 files changed, 21 insertions(+), 19 deletions(-) create mode 100644 engine/src/test/resources/userprofile/image.png 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 index b90622bcc4..7e1fc70a39 100644 --- 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 @@ -2,39 +2,29 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import javax.imageio.ImageIO; -import org.apache.derby.iapi.types.SQLTinyint; +import org.apache.commons.io.IOUtils; import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; -import org.hamcrest.MatcherAssert; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatchers; import org.mockito.MockedStatic; import org.mockito.Mockito; +import org.springframework.core.io.ClassPathResource; import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.validation.FieldError; class ProfileAvatarValidatorTest { - private final static String validBase64Image = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAABhGlDQ1BJQ0MgcHJvZml" - + "sZQAAKJF9kT1Iw0AcxV9bpaItDnYQ6ZChOlkQLeKoVShChVArtOpgcukXNGlIUlwcBdeCgx+LVQcXZ10dXAVB8APE1cVJ0UVK/F9SaBH" - + "jwXE/3t173L0D/M0qU82eCUDVLCOTSgq5/KoQfMUAAggjioTETH1OFNPwHF/38PH1Ls6zvM/9OcJKwWSATyCeZbphEW8QT29aOud94gg" - + "rSwrxOfG4QRckfuS67PIb55LDfp4ZMbKZeeIIsVDqYrmLWdlQiRPEMUXVKN+fc1nhvMVZrdZZ+578haGCtrLMdZpRpLCIJYgQIKOOCqq" - + "wEKdVI8VEhvaTHv4Rxy+SSyZXBYwcC6hBheT4wf/gd7dmcWrSTQolgd4X2/4YBYK7QKth29/Htt06AQLPwJXW8deawMwn6Y2OFjsCBre" - + "Bi+uOJu8BlzvA8JMuGZIjBWj6i0Xg/Yy+KQ8M3QL9a25v7X2cPgBZ6ip9AxwcAmMlyl73eHdfd2//nmn39wN8yXKr1FLmsAAAAAlwSFl" - + "zAAAuIwAALiMBeKU/dgAAAAd0SU1FB+cLAw0dHTZCjPMAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAADElEQVQ" - + "I12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC"; - - private final static String notValidBase64Image = "bm90IGFuIGltYWdl"; @Test - void shouldNotValidateFileNamesThatContainsSlash() { - ProfileAvatarRequest request = new ProfileAvatarRequest("not/valid/filename.png", validBase64Image.getBytes()); + void shouldNotValidateFileNamesThatContainsSlash() throws IOException { + ProfileAvatarRequest request = new ProfileAvatarRequest("not/valid/filename.png", + IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream())); BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); new ProfileAvatarValidator().validate(request, errors); assertEquals(1, errors.getErrorCount()); @@ -44,6 +34,7 @@ void shouldNotValidateFileNamesThatContainsSlash() { @Test void shouldNotValidateFilesOtherThanImages() { + String notValidBase64Image = "bm90IGFuIGltYWdl"; ProfileAvatarRequest request = new ProfileAvatarRequest("valid_filename.txt", notValidBase64Image.getBytes()); BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); new ProfileAvatarValidator().validate(request, errors); @@ -55,13 +46,24 @@ void shouldNotValidateFilesOtherThanImages() { @Test - void shouldThrowUncheckedIOExceptionIfImageReadingFails() { - ProfileAvatarRequest request = new ProfileAvatarRequest("valid_filename.png", validBase64Image.getBytes()); + void shouldThrowUncheckedIOExceptionIfImageReadingFails() throws IOException { + ProfileAvatarRequest request = new ProfileAvatarRequest("valid_filename.png", + IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream())); 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, () -> new ProfileAvatarValidator().validate(request, errors)); + ProfileAvatarValidator profileAvatarValidator = new ProfileAvatarValidator(); + assertThrows(UncheckedIOException.class, () -> profileAvatarValidator.validate(request, errors)); } + } + + @Test + void shouldValidateAcceptValidImageWithValidFileName() throws IOException { + ProfileAvatarRequest request = new ProfileAvatarRequest("image.png", + IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream())); + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); + new ProfileAvatarValidator().validate(request, 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 0000000000000000000000000000000000000000..81745f4bb10961edf28da90172d77d8113261487 GIT binary patch literal 546 zcmV+-0^R+IP)EX>4Tx04R}tkv&MmKpe$iTcx5c4t5afkfAzR5G~@URVYG*P%E_RU~=gfG-*gu zTpR`0f`cE6RRVO2K4+PavLUx?vG-5KnJf zI_G`j2rEkp@j3ChK^G)`e3JS*_Mt`=0!Tp@O!u%ypVkq_Bu3h!7y7jtZ)<5T{im#YBeolOFyN$1jpgCRYWF z91EyHh2;3b|KNAGW^roLO$sG}-WS{c7zKj6K&xTf-^aGyIspRDz?IhV*P6i0C+Urj z7CQn4w}Ff6jwbH`mpj1llP(#OBL!&si$&o5jJ_!k4BZ0#Yi@7teVjf3S?Vf%0~{Oz zV Date: Tue, 7 Nov 2023 20:28:10 +0100 Subject: [PATCH 08/82] ENG-5270: rewriting due to changes in the specifications (partial tested) --- .../services/userprofile/AvatarService.java | 131 ++++++++++++++++ .../services/userprofile/IAvatarService.java | 14 ++ .../services/userprofile/model/AvatarDto.java | 20 +++ .../web/userprofile/ProfileController.java | 99 +++--------- .../model/ProfileAvatarRequest.java | 3 +- .../model/ProfileAvatarResponse.java | 31 ++++ .../validator/ProfileAvatarValidator.java | 13 +- .../resources/spring/aps/servicesConfig.xml | 2 + .../UserProfileControllerTest.java | 147 ++++++++---------- .../validator/ProfileAvatarValidatorTest.java | 8 +- 10 files changed, 298 insertions(+), 170 deletions(-) create mode 100644 engine/src/main/java/org/entando/entando/aps/system/services/userprofile/AvatarService.java create mode 100644 engine/src/main/java/org/entando/entando/aps/system/services/userprofile/IAvatarService.java create mode 100644 engine/src/main/java/org/entando/entando/aps/system/services/userprofile/model/AvatarDto.java create mode 100644 engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarResponse.java 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..02b8a172af --- /dev/null +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/AvatarService.java @@ -0,0 +1,131 @@ +package org.entando.entando.aps.system.services.userprofile; + +import com.agiletec.aps.system.services.user.UserDetails; +import java.nio.file.Paths; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.apache.commons.io.FilenameUtils; +import org.entando.entando.aps.system.exception.ResourceNotFoundException; +import org.entando.entando.aps.system.services.entity.model.EntityAttributeDto; +import org.entando.entando.aps.system.services.entity.model.EntityDto; +import org.entando.entando.aps.system.services.storage.IFileBrowserService; +import org.entando.entando.aps.system.services.userprofile.model.AvatarDto; +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; + +@RequiredArgsConstructor +public class AvatarService implements IAvatarService { + + // Services + private final IFileBrowserService fileBrowserService; + private final IUserProfileService userProfileService; + + // CONSTANTS + public static final String PROFILE_PICTURE = "profilepicture"; + private static final String DEFAULT_AVATAR_PATH = "static/profile"; + + public AvatarDto getAvatarData(UserDetails userDetails) { + + // get profile picture attribute from user profile. The value of this attribute contains the file name of the + // profile image associated with the user profile + EntityDto userProfile = userProfileService.getUserProfile(userDetails.getUsername()); + EntityAttributeDto profilePictureAttribute = getProfilePictureAttribute(userProfile) + .orElseThrow(() -> new ResourceNotFoundException(EntityValidator.ERRCODE_ENTITY_DOES_NOT_EXIST, "image", + userDetails.getUsername())); + // set default params + boolean protectedFolder = false; + String fileName = (String) profilePictureAttribute.getValue(); + // all profiles pictures are saved in the same location at DEFAULT_AVATAR_PATH + // This is why each profile picture is named with the same name as the owner user + 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(); + } + + @Override + public String updateAvatar(ProfileAvatarRequest request, UserDetails userDetails, BindingResult bindingResult) { + EntityDto userProfile = userProfileService.getUserProfile(userDetails.getUsername()); + // remove previous image if present + deletePrevUserAvatarFromFileSystemIfPresent(userProfile); + // add profile picture file + FileBrowserFileRequest fileBrowserFileRequest = addProfileImageToFileSystem(request, userDetails, + bindingResult); + // update or add profile picture attribute + updateProfilePicture(bindingResult, userProfile, fileBrowserFileRequest); + return fileBrowserFileRequest.getFilename(); + } + + //------------------------ Utility methods ------------------------------------// + private void updateUserProfilePictureAttribute(EntityDto userProfile, + FileBrowserFileRequest fileBrowserFileRequest) { + getProfilePictureAttribute(userProfile).ifPresentOrElse( + attributeDto -> attributeDto.setValue(fileBrowserFileRequest.getFilename()), () -> { + EntityAttributeDto profileAttribute = new EntityAttributeDto(); + profileAttribute.setCode(PROFILE_PICTURE); + profileAttribute.setValue(fileBrowserFileRequest.getFilename()); + userProfile.getAttributes().add(profileAttribute); + }); + } + + private void updateProfilePicture(BindingResult bindingResult, EntityDto userProfile, + FileBrowserFileRequest fileBrowserFileRequest) { + // update profile picture attribute or add a new one if no image was already set by user + updateUserProfilePictureAttribute(userProfile, fileBrowserFileRequest); + // update user profile with the fresh data related to profile picture + userProfileService.updateUserProfile(userProfile, bindingResult); + } + + 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(EntityDto userProfile) { + getProfilePictureAttribute(userProfile) + .ifPresent(attribute -> { + String profilePicturePath = Paths.get(DEFAULT_AVATAR_PATH, (String) attribute.getValue()) + .toString(); + fileBrowserService.deleteFile(profilePicturePath, false); + } + ); + } + + 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; + } + + + private Optional getProfilePictureAttribute(EntityDto userProfile) { + return Optional.ofNullable(userProfile.getAttributes()) + .flatMap(attributes -> attributes.stream() + .filter(entityAttributeDto -> entityAttributeDto.getCode().equals(PROFILE_PICTURE)) + .findFirst()); + } + + +} 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..dcec8d7669 --- /dev/null +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/IAvatarService.java @@ -0,0 +1,14 @@ +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.web.userprofile.model.ProfileAvatarRequest; +import org.springframework.validation.BindingResult; + +public interface IAvatarService { + AvatarDto getAvatarData(UserDetails userDetails); + + + String updateAvatar(ProfileAvatarRequest request, UserDetails userDetails, + BindingResult bindingResult); +} 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..312359ed21 --- /dev/null +++ b/engine/src/main/java/org/entando/entando/aps/system/services/userprofile/model/AvatarDto.java @@ -0,0 +1,20 @@ +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; + +} 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 1b38471882..b03fe56885 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,21 +16,17 @@ 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 java.io.ByteArrayInputStream; -import java.io.IOException; -import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; -import java.util.function.BiConsumer; -import javax.imageio.ImageIO; 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.storage.IFileBrowserService; +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; @@ -40,8 +36,8 @@ 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.filebrowser.model.FileBrowserFileRequest; import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; +import org.entando.entando.web.userprofile.model.ProfileAvatarResponse; import org.entando.entando.web.userprofile.validator.ProfileAvatarValidator; import org.entando.entando.web.userprofile.validator.ProfileValidator; import org.springframework.http.HttpStatus; @@ -56,7 +52,6 @@ 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.RequestParam; import org.springframework.web.bind.annotation.RestController; /** @@ -78,13 +73,10 @@ public class ProfileController { private final IUserProfileManager userProfileManager; - private final IFileBrowserService fileBrowserService; - - public static final String FILE_NAME = "fileName"; + private final IAvatarService avatarService; public static final String PROTECTED_FOLDER = "protectedFolder"; - private static final String DEFAULT_AVATAR_PATH = "static/profile"; public static final String PREV_PATH = "prevPath"; @@ -194,86 +186,41 @@ public ResponseEntity> updateMyUserProfile(@Reques @GetMapping(path = "/userProfiles/avatar", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity, Map>> getAvatar( - @RequestParam(value = FILE_NAME, required = false, defaultValue = "") String fileName) throws IOException { - - // set fixed params - boolean protectedFolder = false; - String currentPath = Paths.get(DEFAULT_AVATAR_PATH, fileName).toString(); - // validate fileName to check if contains path to avoid directory listing - if (fileName.contains("/")) { - logger.error("Directory listing attempt in an avatar GET request"); - throw new IllegalArgumentException("The requested file name is not valid"); - } - // get file from volume or else throw exception - byte[] base64 = fileBrowserService.getFileStream(currentPath, protectedFolder); - // check if the desired file is an image, otherwise throw exception - if (ImageIO.read(new ByteArrayInputStream(base64)) == null) { - logger.error("Attempt to request a file that is not an image in an avatar GET request"); - throw new IllegalArgumentException("The requested file is not an image"); - } - - // prepare output + @RequestAttribute("user") UserDetails userDetails) { + // request user profile picture + AvatarDto avatarData = avatarService.getAvatarData(userDetails); + // fill output Map result = new HashMap<>(); - result.put(PROTECTED_FOLDER, protectedFolder); + result.put(PROTECTED_FOLDER, avatarData.isProtectedFolder()); result.put("isDirectory", false); - result.put("path", currentPath); - result.put("filename", fileName); - result.put("base64", base64); + result.put("path", avatarData.getCurrentPath()); + result.put("filename", avatarData.getFilename()); + result.put("base64", avatarData.getBase64()); Map metadata = new HashMap<>(); - metadata.put(PREV_PATH, DEFAULT_AVATAR_PATH); + 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, Map>> addAvatar( + public ResponseEntity> addAvatar( @Valid @RequestBody ProfileAvatarRequest request, + @RequestAttribute("user") UserDetails user, BindingResult bindingResult) { - return executeUpsert(request, bindingResult, fileBrowserService::addFile); - } - - @PutMapping(path = "/userProfiles/avatar", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity, Map>> updateAvatar( - @Valid @RequestBody ProfileAvatarRequest request, BindingResult bindingResult) { - return executeUpsert(request, bindingResult, fileBrowserService::updateFile); - } - private ResponseEntity, Map>> executeUpsert( - ProfileAvatarRequest request, BindingResult bindingResult, - BiConsumer upsertFunction) { - if (bindingResult.hasErrors()) { - throw new ValidationGenericException(bindingResult); - } // validate input dto to check for consistency of input profileAvatarValidator.validate(request, bindingResult); if (bindingResult.hasErrors()) { throw new ValidationGenericException(bindingResult); } - // prepare a FileBrowserFileRequest to use the api already available in the system - FileBrowserFileRequest fileBrowserFileRequest = convertToFileBrowserFileRequest(request); - // add the file to the volume - upsertFunction.accept(fileBrowserFileRequest, bindingResult); - // prepare and return a consistent response - return composeAvatarUpsertResponse(request); - } + // update the profile picture saving the received image in the file system, eventually deleting the previous + // existing image + String pictureFileName = avatarService.updateAvatar(request, user, bindingResult); - private static FileBrowserFileRequest convertToFileBrowserFileRequest(ProfileAvatarRequest request) { - FileBrowserFileRequest fileBrowserFileRequest = new FileBrowserFileRequest(); - fileBrowserFileRequest.setFilename(request.getFilename()); - fileBrowserFileRequest.setPath(Paths.get(DEFAULT_AVATAR_PATH, request.getFilename()).toString()); - fileBrowserFileRequest.setProtectedFolder(false); - fileBrowserFileRequest.setBase64(request.getBase64()); - return fileBrowserFileRequest; - } - - private ResponseEntity, Map>> composeAvatarUpsertResponse( - ProfileAvatarRequest request) { - Map result = new HashMap<>(); - result.put(PROTECTED_FOLDER, false); - result.put("path", Paths.get(DEFAULT_AVATAR_PATH, request.getFilename()).toString()); - result.put("filename", request.getFilename()); - Map metadata = new HashMap<>(); - metadata.put(PREV_PATH, DEFAULT_AVATAR_PATH); - return new ResponseEntity<>(new RestResponse<>(result, metadata), HttpStatus.OK); + if (bindingResult.hasErrors()) { + throw new ValidationGenericException(bindingResult); + } + return new ResponseEntity<>(new SimpleRestResponse<>(new ProfileAvatarResponse(pictureFileName)), + 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 index 56e73b0d04..f060159b48 100644 --- 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 @@ -30,8 +30,7 @@ public class ProfileAvatarRequest { @NotBlank(message = "fileBrowser.filename.notBlank") - private String filename; + private String fileName; @NotNull(message = "fileBrowser.base64.notBlank") private byte[] base64; - } diff --git a/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarResponse.java b/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarResponse.java new file mode 100644 index 0000000000..303213db22 --- /dev/null +++ b/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarResponse.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; + +/** + * @author E.Santoboni + */ +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class ProfileAvatarResponse { + + private String fileName; +} 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 index bade613260..9c19afcaf2 100644 --- 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 @@ -17,6 +17,8 @@ import java.io.IOException; import java.io.UncheckedIOException; import javax.imageio.ImageIO; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; import org.entando.entando.web.filebrowser.model.FileBrowserFileRequest; import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; import org.springframework.lang.NonNull; @@ -41,15 +43,16 @@ public boolean supports(@NonNull Class paramClass) { @Override public void validate(@NonNull Object target, @NonNull Errors errors) { ProfileAvatarRequest request = (ProfileAvatarRequest) target; - String filename = request.getFilename(); - if (filename.contains("/")) { - errors.rejectValue("filename", ERRCODE_INVALID_FILE_NAME, new String[]{filename}, + + String fileName = request.getFileName(); + if (StringUtils.isEmpty(FilenameUtils.getExtension(fileName))) { + errors.rejectValue("fileName", ERRCODE_INVALID_FILE_NAME, new String[]{fileName}, "fileBrowser.filename.invalidFilename"); return; } - try { - if (ImageIO.read(new ByteArrayInputStream(request.getBase64())) == null) { + 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) { diff --git a/engine/src/main/resources/spring/aps/servicesConfig.xml b/engine/src/main/resources/spring/aps/servicesConfig.xml index 65c9905104..330cd8553f 100644 --- a/engine/src/main/resources/spring/aps/servicesConfig.xml +++ b/engine/src/main/resources/spring/aps/servicesConfig.xml @@ -117,6 +117,8 @@ + + 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 5a341f84b6..4420367b43 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 @@ -14,33 +14,26 @@ package org.entando.entando.web.userprofile; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; 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; 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.awt.image.BufferedImage; -import java.io.InputStream; -import java.util.Arrays; -import javax.imageio.ImageIO; 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.storage.IFileBrowserService; +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.common.exceptions.ValidationConflictException; -import org.entando.entando.web.filebrowser.validator.FileBrowserValidator; import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; import org.entando.entando.web.userprofile.validator.ProfileAvatarValidator; import org.entando.entando.web.userprofile.validator.ProfileValidator; @@ -48,14 +41,10 @@ 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.MockedStatic; import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.stubbing.Answer; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -80,13 +69,13 @@ class UserProfileControllerTest extends AbstractControllerTest { private ProfileAvatarValidator profileAvatarValidator; @Mock - private IFileBrowserService fileBrowserService; + private IAvatarService avatarService; @BeforeEach public void setUp() throws Exception { ProfileController controller = new ProfileController(userProfileService, profileValidator, profileAvatarValidator, userManager, - userProfileManager, fileBrowserService); + userProfileManager, avatarService); mockMvc = MockMvcBuilders.standaloneSetup(controller) .addInterceptors(entandoOauth2Interceptor) .setMessageConverters(getMessageConverters()) @@ -150,85 +139,59 @@ void testUpdateProfile() throws Exception { result.andExpect(status().isOk()); } - @Test - void shouldGetAvatarReturn500OnDirectoryListing() throws Exception { - String accessToken = this.createAccessToken(); - ResultActions result = mockMvc.perform( - get("/userProfiles/avatar?fileName=some/dir/myFile.png") - .header("Authorization", "Bearer " + accessToken)); - result.andExpect(status().is5xxServerError()); - } - - @Test - void shouldGetAvatarReturn500OnWrongFileType() throws Exception { - String accessToken = this.createAccessToken(); - String notImageBase64 = "SGVsbG8="; - when(fileBrowserService.getFileStream(any(), any())).thenReturn(notImageBase64.getBytes()); - - ResultActions result = mockMvc.perform( - get("/userProfiles/avatar?fileName=myFile.txt") - .header("Authorization", "Bearer " + accessToken)); - result.andExpect(status().is5xxServerError()); - } - @Test void shouldGetAvatarReturn404IfImageNotExists() throws Exception { String accessToken = this.createAccessToken(); - when(fileBrowserService.getFileStream(any(), any())).thenThrow( - new ResourceNotFoundException("1", "File", "static/profile/myFile.png")); + // 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()); + 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(); - String pretendIsAnImageBase64 = "cHJldGVuZCBpcyBhbiBpbWFnZQ=="; - when(fileBrowserService.getFileStream(any(), any())).thenReturn(pretendIsAnImageBase64.getBytes()); - - try (MockedStatic utilities = Mockito.mockStatic(ImageIO.class)) { - utilities.when(() -> ImageIO.read(any(InputStream.class))) - .thenReturn(mock(BufferedImage.class)); - - ResultActions result = mockMvc.perform( - get("/userProfiles/avatar?fileName=myFile.png") - .header("Authorization", "Bearer " + accessToken)); - result.andExpect(status().isOk()); - } - } - - @Test - void shouldPostAvatarReturn409IfImageAlreadyExists() throws Exception { - String accessToken = this.createAccessToken(); - doThrow(new ValidationConflictException(mock(BindingResult.class))).when(fileBrowserService) - .addFile(any(), any()); + 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); - byte[] pretendIsAnImageBase64 = "cHJldGVuZCBpcyBhbiBpbWFnZQ==".getBytes(); - ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("myFile.png", pretendIsAnImageBase64); ResultActions result = mockMvc.perform( - post("/userProfiles/avatar") - .content(new ObjectMapper().writeValueAsString(profileAvatarRequest)) - .contentType(MediaType.APPLICATION_JSON_VALUE) + get("/userProfiles/avatar?fileName=myFile.png") .header("Authorization", "Bearer " + accessToken)); - result.andExpect(status().isConflict()); + 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(); - String notImageBase64 = "SGVsbG8="; Answer ans = invocation -> { Object[] args = invocation.getArguments(); - ((BindingResult) args[1]).rejectValue("filename", "1", new String[]{"any/dir/fileName.png"}, + ((BindingResult) args[1]).rejectValue("fileName", "1", new String[]{"fileName_without_extension"}, "fileBrowser.filename.invalidFilename"); return null; }; doAnswer(ans).when(profileAvatarValidator).validate(any(), any()); - ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("myFile.png", notImageBase64.getBytes()); + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("fileName_without_extension", + new byte[0]); ResultActions result = mockMvc.perform( post("/userProfiles/avatar") @@ -239,38 +202,56 @@ void shouldPostAvatarReturn400OnIllegalInput() throws Exception { } @Test - void shouldPostAvatarReturn200OnRightInputIfFileIsNotAlreadyPresent() throws Exception { + void shouldPostAvatarReturn200OnRightInput() throws Exception { String accessToken = this.createAccessToken(); - String pretendIsAnImageBase64 = "cHJldGVuZCBpcyBhbiBpbWFnZQ=="; - ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("myFile.png", - pretendIsAnImageBase64.getBytes()); + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("myFile.png", new byte[0]); + 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()); + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.payload.fileName").value("jack_bauer.png")); } - @Test - void shouldPutAvatarReturn404IfImageNotExists() throws Exception { + void shouldPostAvatarReturn400OnFileServiceAddFailureIfFileAlreadyPresent() throws Exception { String accessToken = this.createAccessToken(); - doThrow(new ResourceNotFoundException("1", "File", "static/profile/notAlreadyExistingImage.png")).when( - fileBrowserService) - .updateFile(any(), any()); - byte[] pretendIsAnImageBase64 = "cHJldGVuZCBpcyBhbiBpbWFnZQ==".getBytes(); - ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("notAlreadyExistingImage.png", - pretendIsAnImageBase64); + 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; + }; + doAnswer(ans).when(avatarService).updateAvatar(any(), any(), any()); + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("image.png", + new byte[0]); ResultActions result = mockMvc.perform( - put("/userProfiles/avatar") + post("/userProfiles/avatar") .content(new ObjectMapper().writeValueAsString(profileAvatarRequest)) .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + accessToken)); - result.andExpect(status().isNotFound()); + result.andExpect(status().isBadRequest()); + } + + @Test + void shouldPostAvatarReturn400OnWrongRequest() throws Exception { + String accessToken = this.createAccessToken(); + String request = "{\n" + + " \"fileNam\": \"image.png\",\n" + + " \"base64\": \"\"\n" + + "}"; + + ResultActions result = mockMvc.perform( + post("/userProfiles/avatar") + .content(request) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header("Authorization", "Bearer " + accessToken)); + result.andExpect(status().isBadRequest()); } private ResultActions performGetUserProfiles(String username) throws Exception { 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 index 7e1fc70a39..885e61c1a7 100644 --- 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 @@ -22,14 +22,14 @@ class ProfileAvatarValidatorTest { @Test - void shouldNotValidateFileNamesThatContainsSlash() throws IOException { - ProfileAvatarRequest request = new ProfileAvatarRequest("not/valid/filename.png", + void shouldNotValidateFileNamesMissingExtensions() throws IOException { + ProfileAvatarRequest request = new ProfileAvatarRequest("missing_extension_filename", IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream())); BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); new ProfileAvatarValidator().validate(request, errors); assertEquals(1, errors.getErrorCount()); assertEquals("fileBrowser.filename.invalidFilename", errors.getAllErrors().get(0).getDefaultMessage()); - assertEquals("not/valid/filename.png", ((FieldError) errors.getAllErrors().get(0)).getRejectedValue()); + assertEquals("missing_extension_filename", ((FieldError) errors.getAllErrors().get(0)).getRejectedValue()); } @Test @@ -47,7 +47,7 @@ void shouldNotValidateFilesOtherThanImages() { @Test void shouldThrowUncheckedIOExceptionIfImageReadingFails() throws IOException { - ProfileAvatarRequest request = new ProfileAvatarRequest("valid_filename.png", + ProfileAvatarRequest request = new ProfileAvatarRequest("image.png", IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream())); BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); try (MockedStatic mockStatic = Mockito.mockStatic(ImageIO.class)) { From 68b037357b73ddda870dedeb4bd454a525436787 Mon Sep 17 00:00:00 2001 From: "m.panagrosso" Date: Fri, 10 Nov 2023 15:42:46 +0100 Subject: [PATCH 09/82] ENG-5270: test coverage --- .../userprofile/AvatarServiceTest.java | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 engine/src/test/java/org/entando/entando/aps/system/services/userprofile/AvatarServiceTest.java 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..d06083e432 --- /dev/null +++ b/engine/src/test/java/org/entando/entando/aps/system/services/userprofile/AvatarServiceTest.java @@ -0,0 +1,131 @@ +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.User; +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.services.entity.model.EntityAttributeDto; +import org.entando.entando.aps.system.services.entity.model.EntityDto; +import org.entando.entando.aps.system.services.storage.IFileBrowserService; +import org.entando.entando.aps.system.services.userprofile.model.AvatarDto; +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.ArgumentCaptor; +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 IUserProfileService userProfileService; + @Mock + private IFileBrowserService fileBrowserService; + + IAvatarService avatarService; + + @BeforeEach + void init() { + avatarService = new AvatarService(fileBrowserService, userProfileService); + } + + @Test + void shouldGetAvatarDataReturnAvatarInfo() { + EntityDto entityDto = new EntityDto(); + EntityAttributeDto entityAttributeDto = new EntityAttributeDto(); + entityAttributeDto.setCode("profilepicture"); + entityAttributeDto.setValue("image.png"); + entityDto.setAttributes(List.of(entityAttributeDto)); + when(userProfileService.getUserProfile(any())).thenReturn(entityDto); + when(fileBrowserService.getFileStream(any(), any())).thenReturn(new byte[0]); + + AvatarDto avatarData = avatarService.getAvatarData(mock(UserDetails.class)); + + assertEquals("image.png", avatarData.getFilename()); + assertEquals("static/profile/image.png", avatarData.getCurrentPath()); + } + + @Test + void shouldGetAvatarDataThrowResourceNotFoundExceptionIfNoImageIsPresent() { + when(userProfileService.getUserProfile(any())).thenReturn(new EntityDto()); + assertThrows(ResourceNotFoundException.class, () -> avatarService.getAvatarData(mock(UserDetails.class))); + } + + @Test + void shouldUpdateAvatarDeletePreviousProfilePictureIfPresent() { + EntityDto entityDto = new EntityDto(); + EntityAttributeDto entityAttributeDto = new EntityAttributeDto(); + entityAttributeDto.setCode("profilepicture"); + entityAttributeDto.setValue("prevImage.png"); + entityDto.setAttributes(List.of(entityAttributeDto)); + when(userProfileService.getUserProfile(any())).thenReturn(entityDto); + + avatarService.updateAvatar(mock(ProfileAvatarRequest.class), mock(UserDetails.class), + mock(BindingResult.class)); + verify(fileBrowserService, Mockito.times(1)).deleteFile(any(), any()); + + } + + @Test + void shouldUpdateAvatarAddProfilePictureFromTheRequest() { + when(userProfileService.getUserProfile(any())).thenReturn(new EntityDto()); + avatarService.updateAvatar(mock(ProfileAvatarRequest.class), mock(UserDetails.class), + mock(BindingResult.class)); + verify(fileBrowserService, Mockito.times(1)).addFile(any(), any()); + } + + @Test + void shouldUpdateAvatarUserProfileAndRenameProfilePictureWithUserName() { + // set previous profile picture + EntityDto entityDto = new EntityDto(); + EntityAttributeDto entityAttributeDto = new EntityAttributeDto(); + entityAttributeDto.setCode("profilepicture"); + entityAttributeDto.setValue("prevImage.png"); + entityDto.setAttributes(List.of(entityAttributeDto)); + when(userProfileService.getUserProfile(any())).thenReturn(entityDto); + // set POST request DTO + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest(); + profileAvatarRequest.setFileName("image.png"); + profileAvatarRequest.setBase64(new byte[0]); + // set user details to return desired username + UserDetails userDetails = mock(UserDetails.class); + when(userDetails.getUsername()).thenReturn("user1"); + + avatarService.updateAvatar(profileAvatarRequest, userDetails, mock(BindingResult.class)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(EntityDto.class); + verify(userProfileService, Mockito.times(1)).updateUserProfile(captor.capture(), any()); + assertEquals("user1.png", captor.getValue().getAttributes().get(0).getValue()); + } + + @Test + void shouldUpdateAvatarUserProfileAndSetRenamedProfilePictureIfNoPreviousPictureWasPresent() { + // set previous profile picture + when(userProfileService.getUserProfile(any())).thenReturn(new EntityDto()); + // set POST request DTO + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest(); + profileAvatarRequest.setFileName("image.png"); + profileAvatarRequest.setBase64(new byte[0]); + // set user details to return desired username + UserDetails userDetails = mock(UserDetails.class); + when(userDetails.getUsername()).thenReturn("user1"); + + avatarService.updateAvatar(profileAvatarRequest, userDetails, mock(BindingResult.class)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(EntityDto.class); + verify(userProfileService, Mockito.times(1)).updateUserProfile(captor.capture(), any()); + assertEquals("user1.png", captor.getValue().getAttributes().get(0).getValue()); + } +} From f44dcb2d3586cff51bff8f9e43079565070383d7 Mon Sep 17 00:00:00 2001 From: "m.panagrosso" Date: Fri, 10 Nov 2023 16:38:21 +0100 Subject: [PATCH 10/82] ENG-5270: test fix --- .../web/userprofile/validator/ProfileAvatarValidator.java | 3 +-- .../userprofile/validator/ProfileAvatarValidatorTest.java | 7 +++++++ 2 files changed, 8 insertions(+), 2 deletions(-) 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 index 9c19afcaf2..491e7ff146 100644 --- 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 @@ -19,7 +19,6 @@ import javax.imageio.ImageIO; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; -import org.entando.entando.web.filebrowser.model.FileBrowserFileRequest; import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; @@ -37,7 +36,7 @@ public class ProfileAvatarValidator implements Validator { @Override public boolean supports(@NonNull Class paramClass) { - return (FileBrowserFileRequest.class.equals(paramClass)); + return (ProfileAvatarRequest.class.equals(paramClass)); } @Override 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 index 885e61c1a7..3453b652d4 100644 --- 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 @@ -1,6 +1,7 @@ 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; @@ -21,6 +22,12 @@ class ProfileAvatarValidatorTest { + @Test + void shouldSupportOnlyProfileAvatarRequest() { + assertTrue(new ProfileAvatarValidator().supports(ProfileAvatarRequest.class)); + assertFalse(new ProfileAvatarValidator().supports(Object.class)); + } + @Test void shouldNotValidateFileNamesMissingExtensions() throws IOException { ProfileAvatarRequest request = new ProfileAvatarRequest("missing_extension_filename", From 60a8bfe164decad1293eb4585a80d2980aa71333 Mon Sep 17 00:00:00 2001 From: "m.panagrosso" Date: Mon, 13 Nov 2023 11:22:10 +0100 Subject: [PATCH 11/82] ENG-5270: error message required property fixed --- .../entando/web/userprofile/model/ProfileAvatarRequest.java | 2 +- engine/src/main/resources/rest/messages.properties | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) 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 index f060159b48..a1468392c7 100644 --- 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 @@ -29,7 +29,7 @@ @Setter public class ProfileAvatarRequest { - @NotBlank(message = "fileBrowser.filename.notBlank") + @NotBlank(message = "avatar.filename.notBlank") private String fileName; @NotNull(message = "fileBrowser.base64.notBlank") private byte[] base64; diff --git a/engine/src/main/resources/rest/messages.properties b/engine/src/main/resources/rest/messages.properties index a5f4c8efcb..3c30722220 100644 --- a/engine/src/main/resources/rest/messages.properties +++ b/engine/src/main/resources/rest/messages.properties @@ -392,3 +392,6 @@ 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 From 1ab3d560ce4f33111efa03a21137c803e1861707 Mon Sep 17 00:00:00 2001 From: "m.panagrosso" Date: Tue, 14 Nov 2023 14:32:44 +0100 Subject: [PATCH 12/82] ENG-5199: default to ISO 639-1 --- .../aps/system/services/lang/LangManager.java | 3 +- .../lang/cache/ILangManagerCacheWrapper.java | 4 +- .../lang/cache/LangManagerCacheWrapper.java | 18 ++++++- .../lang/LangManagerIntegrationTest.java | 4 +- .../system/services/lang/LangManagerTest.java | 17 ++++--- .../cache/LangManagerCacheWrapperTest.java | 49 +++++++++++++++++++ 6 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 engine/src/test/java/com/agiletec/aps/system/services/lang/cache/LangManagerCacheWrapperTest.java 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/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())); + } +} From 852ac22f278fc255a599e144bf7fccf9f0af8834 Mon Sep 17 00:00:00 2001 From: "m.panagrosso" Date: Fri, 17 Nov 2023 14:50:55 +0100 Subject: [PATCH 13/82] ENG-5318: user in Activity Stream Interceptor taken from request (no session anymore) --- .../web/common/interceptor/ActivityStreamInterceptor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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(); } From c92540c63bf32cae6b3adcdda8806b6fe02a21ed Mon Sep 17 00:00:00 2001 From: "m.panagrosso" Date: Fri, 17 Nov 2023 17:44:15 +0100 Subject: [PATCH 14/82] ENG-5318: test coverage --- .../ActivityStreamInterceptorTest.java | 76 +++++++++++++++++++ .../interceptor/DoubleRequestMapping.java | 53 +++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 engine/src/test/java/org/entando/entando/web/common/interceptor/ActivityStreamInterceptorTest.java create mode 100644 engine/src/test/java/org/entando/entando/web/common/interceptor/DoubleRequestMapping.java diff --git a/engine/src/test/java/org/entando/entando/web/common/interceptor/ActivityStreamInterceptorTest.java b/engine/src/test/java/org/entando/entando/web/common/interceptor/ActivityStreamInterceptorTest.java new file mode 100644 index 0000000000..2c0d96b081 --- /dev/null +++ b/engine/src/test/java/org/entando/entando/web/common/interceptor/ActivityStreamInterceptorTest.java @@ -0,0 +1,76 @@ +package org.entando.entando.web.common.interceptor; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.lang.annotation.Annotation; +import java.lang.reflect.Method; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.entando.entando.aps.system.services.actionlog.IActionLogManager; +import org.entando.entando.aps.system.services.actionlog.model.ActionLogRecord; +import org.entando.entando.web.common.annotation.ActivityStreamAuditable; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.method.HandlerMethod; + +class ActivityStreamInterceptorTest { + + + @Test + void shouldReadUserFromRequest() throws Exception { + ActivityStreamInterceptor activityStreamInterceptor = new ActivityStreamInterceptor(); + IActionLogManager actionLogManager = mock(IActionLogManager.class); + ArgumentCaptor captor = ArgumentCaptor.forClass(ActionLogRecord.class); + ReflectionTestUtils.setField(activityStreamInterceptor, "actionLogManager", actionLogManager); + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + HandlerMethod handler = mock(HandlerMethod.class); + UserDetails userDetails = mock(UserDetails.class); + when(userDetails.getUsername()).thenReturn("user1"); + when(request.getAttribute("user")).thenReturn(userDetails); + when(request.getRequestURI()).thenReturn("/do/Page"); + Method method = mock(Method.class); + when(method.isAnnotationPresent(RequestMapping.class)).thenReturn(true); + when(method.isAnnotationPresent(ActivityStreamAuditable.class)).thenReturn(true); + when(handler.getMethod()).thenReturn(method); + when(method.getAnnotation(RequestMapping.class)).thenReturn(new DoubleRequestMapping()); + + activityStreamInterceptor.postHandle(request, response, handler, null); + verify(actionLogManager).addActionRecord(captor.capture()); + assertEquals("user1", captor.getValue().getUsername()); + + } + + @Test + void shouldUseGuestUserNameIfUserIsNotPresentInTheRequest() throws Exception { + ActivityStreamInterceptor activityStreamInterceptor = new ActivityStreamInterceptor(); + IActionLogManager actionLogManager = mock(IActionLogManager.class); + ArgumentCaptor captor = ArgumentCaptor.forClass(ActionLogRecord.class); + ReflectionTestUtils.setField(activityStreamInterceptor, "actionLogManager", actionLogManager); + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + HandlerMethod handler = mock(HandlerMethod.class); + when(request.getRequestURI()).thenReturn("/do/Page"); + Method method = mock(Method.class); + when(method.isAnnotationPresent(RequestMapping.class)).thenReturn(true); + when(method.isAnnotationPresent(ActivityStreamAuditable.class)).thenReturn(true); + when(handler.getMethod()).thenReturn(method); + when(method.getAnnotation(RequestMapping.class)).thenReturn(new DoubleRequestMapping()); + + activityStreamInterceptor.postHandle(request, response, handler, null); + verify(actionLogManager).addActionRecord(captor.capture()); + assertEquals("guest", captor.getValue().getUsername()); + + } +} + + + + diff --git a/engine/src/test/java/org/entando/entando/web/common/interceptor/DoubleRequestMapping.java b/engine/src/test/java/org/entando/entando/web/common/interceptor/DoubleRequestMapping.java new file mode 100644 index 0000000000..9130596c95 --- /dev/null +++ b/engine/src/test/java/org/entando/entando/web/common/interceptor/DoubleRequestMapping.java @@ -0,0 +1,53 @@ +package org.entando.entando.web.common.interceptor; + +import java.lang.annotation.Annotation; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +class DoubleRequestMapping implements RequestMapping { + + @Override + public String name() { + return null; + } + + @Override + public String[] value() { + return new String[]{"test"}; + } + + @Override + public String[] path() { + return new String[0]; + } + + @Override + public RequestMethod[] method() { + return new RequestMethod[0]; + } + + @Override + public String[] params() { + return new String[0]; + } + + @Override + public String[] headers() { + return new String[0]; + } + + @Override + public String[] consumes() { + return new String[0]; + } + + @Override + public String[] produces() { + return new String[0]; + } + + @Override + public Class annotationType() { + return null; + } +} \ No newline at end of file From 758cabd999b6113ec714cd67f1b2218da975b878 Mon Sep 17 00:00:00 2001 From: "m.panagrosso" Date: Mon, 20 Nov 2023 12:05:11 +0100 Subject: [PATCH 15/82] ENG-5318: removed test because it's not possible mock Method.class --- .../ActivityStreamInterceptorTest.java | 76 ------------------- .../interceptor/DoubleRequestMapping.java | 53 ------------- 2 files changed, 129 deletions(-) delete mode 100644 engine/src/test/java/org/entando/entando/web/common/interceptor/ActivityStreamInterceptorTest.java delete mode 100644 engine/src/test/java/org/entando/entando/web/common/interceptor/DoubleRequestMapping.java diff --git a/engine/src/test/java/org/entando/entando/web/common/interceptor/ActivityStreamInterceptorTest.java b/engine/src/test/java/org/entando/entando/web/common/interceptor/ActivityStreamInterceptorTest.java deleted file mode 100644 index 2c0d96b081..0000000000 --- a/engine/src/test/java/org/entando/entando/web/common/interceptor/ActivityStreamInterceptorTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.entando.entando.web.common.interceptor; - -import static org.junit.jupiter.api.Assertions.assertEquals; -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.lang.annotation.Annotation; -import java.lang.reflect.Method; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.entando.entando.aps.system.services.actionlog.IActionLogManager; -import org.entando.entando.aps.system.services.actionlog.model.ActionLogRecord; -import org.entando.entando.web.common.annotation.ActivityStreamAuditable; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.method.HandlerMethod; - -class ActivityStreamInterceptorTest { - - - @Test - void shouldReadUserFromRequest() throws Exception { - ActivityStreamInterceptor activityStreamInterceptor = new ActivityStreamInterceptor(); - IActionLogManager actionLogManager = mock(IActionLogManager.class); - ArgumentCaptor captor = ArgumentCaptor.forClass(ActionLogRecord.class); - ReflectionTestUtils.setField(activityStreamInterceptor, "actionLogManager", actionLogManager); - HttpServletRequest request = mock(HttpServletRequest.class); - HttpServletResponse response = mock(HttpServletResponse.class); - HandlerMethod handler = mock(HandlerMethod.class); - UserDetails userDetails = mock(UserDetails.class); - when(userDetails.getUsername()).thenReturn("user1"); - when(request.getAttribute("user")).thenReturn(userDetails); - when(request.getRequestURI()).thenReturn("/do/Page"); - Method method = mock(Method.class); - when(method.isAnnotationPresent(RequestMapping.class)).thenReturn(true); - when(method.isAnnotationPresent(ActivityStreamAuditable.class)).thenReturn(true); - when(handler.getMethod()).thenReturn(method); - when(method.getAnnotation(RequestMapping.class)).thenReturn(new DoubleRequestMapping()); - - activityStreamInterceptor.postHandle(request, response, handler, null); - verify(actionLogManager).addActionRecord(captor.capture()); - assertEquals("user1", captor.getValue().getUsername()); - - } - - @Test - void shouldUseGuestUserNameIfUserIsNotPresentInTheRequest() throws Exception { - ActivityStreamInterceptor activityStreamInterceptor = new ActivityStreamInterceptor(); - IActionLogManager actionLogManager = mock(IActionLogManager.class); - ArgumentCaptor captor = ArgumentCaptor.forClass(ActionLogRecord.class); - ReflectionTestUtils.setField(activityStreamInterceptor, "actionLogManager", actionLogManager); - HttpServletRequest request = mock(HttpServletRequest.class); - HttpServletResponse response = mock(HttpServletResponse.class); - HandlerMethod handler = mock(HandlerMethod.class); - when(request.getRequestURI()).thenReturn("/do/Page"); - Method method = mock(Method.class); - when(method.isAnnotationPresent(RequestMapping.class)).thenReturn(true); - when(method.isAnnotationPresent(ActivityStreamAuditable.class)).thenReturn(true); - when(handler.getMethod()).thenReturn(method); - when(method.getAnnotation(RequestMapping.class)).thenReturn(new DoubleRequestMapping()); - - activityStreamInterceptor.postHandle(request, response, handler, null); - verify(actionLogManager).addActionRecord(captor.capture()); - assertEquals("guest", captor.getValue().getUsername()); - - } -} - - - - diff --git a/engine/src/test/java/org/entando/entando/web/common/interceptor/DoubleRequestMapping.java b/engine/src/test/java/org/entando/entando/web/common/interceptor/DoubleRequestMapping.java deleted file mode 100644 index 9130596c95..0000000000 --- a/engine/src/test/java/org/entando/entando/web/common/interceptor/DoubleRequestMapping.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.entando.entando.web.common.interceptor; - -import java.lang.annotation.Annotation; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; - -class DoubleRequestMapping implements RequestMapping { - - @Override - public String name() { - return null; - } - - @Override - public String[] value() { - return new String[]{"test"}; - } - - @Override - public String[] path() { - return new String[0]; - } - - @Override - public RequestMethod[] method() { - return new RequestMethod[0]; - } - - @Override - public String[] params() { - return new String[0]; - } - - @Override - public String[] headers() { - return new String[0]; - } - - @Override - public String[] consumes() { - return new String[0]; - } - - @Override - public String[] produces() { - return new String[0]; - } - - @Override - public Class annotationType() { - return null; - } -} \ No newline at end of file From d06b67f202c5db7d361c3fc23a81f9f56101c31f Mon Sep 17 00:00:00 2001 From: "m.panagrosso" Date: Mon, 20 Nov 2023 18:13:13 +0100 Subject: [PATCH 16/82] ENG-5270: bug fix --- .../services/userprofile/AvatarService.java | 6 +- .../model/ProfileAvatarRequest.java | 4 +- .../userprofile/AvatarServiceTest.java | 15 ++++- .../UserProfileControllerTest.java | 58 ++++++++++++++++--- 4 files changed, 69 insertions(+), 14 deletions(-) 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 index 02b8a172af..0ff1f4fede 100644 --- 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 @@ -5,6 +5,7 @@ import java.util.Optional; import lombok.RequiredArgsConstructor; 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.services.entity.model.EntityAttributeDto; import org.entando.entando.aps.system.services.entity.model.EntityDto; @@ -36,7 +37,10 @@ public AvatarDto getAvatarData(UserDetails userDetails) { userDetails.getUsername())); // set default params boolean protectedFolder = false; - String fileName = (String) profilePictureAttribute.getValue(); + String fileName = Optional.ofNullable((String) profilePictureAttribute.getValue()) + .filter(StringUtils::isNotEmpty) + .orElseThrow(() -> new ResourceNotFoundException(EntityValidator.ERRCODE_ENTITY_DOES_NOT_EXIST, "image", + userDetails.getUsername())); // all profiles pictures are saved in the same location at DEFAULT_AVATAR_PATH // This is why each profile picture is named with the same name as the owner user String currentPath = Paths.get(DEFAULT_AVATAR_PATH, fileName).toString(); 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 index a1468392c7..b3a5afb28a 100644 --- 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 @@ -14,7 +14,7 @@ package org.entando.entando.web.userprofile.model; import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; +import javax.validation.constraints.NotEmpty; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -31,6 +31,6 @@ public class ProfileAvatarRequest { @NotBlank(message = "avatar.filename.notBlank") private String fileName; - @NotNull(message = "fileBrowser.base64.notBlank") + @NotEmpty(message = "fileBrowser.base64.notBlank") private byte[] base64; } 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 index d06083e432..cad98de61e 100644 --- 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 @@ -7,7 +7,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.agiletec.aps.system.services.user.User; import com.agiletec.aps.system.services.user.UserDetails; import java.util.List; import org.entando.entando.aps.system.exception.ResourceNotFoundException; @@ -16,7 +15,6 @@ import org.entando.entando.aps.system.services.storage.IFileBrowserService; import org.entando.entando.aps.system.services.userprofile.model.AvatarDto; 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; @@ -63,6 +61,19 @@ void shouldGetAvatarDataThrowResourceNotFoundExceptionIfNoImageIsPresent() { assertThrows(ResourceNotFoundException.class, () -> avatarService.getAvatarData(mock(UserDetails.class))); } + @Test + void shouldGetAvatarDataThrowResourceNotFoundExceptionIfImageInProfileAttributeIsEmpty() { + EntityDto entityDto = new EntityDto(); + EntityAttributeDto entityAttributeDto = new EntityAttributeDto(); + entityAttributeDto.setCode("profilepicture"); + entityAttributeDto.setValue(""); + entityDto.setAttributes(List.of(entityAttributeDto)); + when(userProfileService.getUserProfile(any())).thenReturn(entityDto); + + assertThrows(ResourceNotFoundException.class, () -> avatarService.getAvatarData(mock(UserDetails.class))); + + } + @Test void shouldUpdateAvatarDeletePreviousProfilePictureIfPresent() { EntityDto entityDto = new EntityDto(); 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 4420367b43..c115fee161 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 @@ -191,7 +191,7 @@ void shouldPostAvatarReturn400OnIllegalInput() throws Exception { }; doAnswer(ans).when(profileAvatarValidator).validate(any(), any()); ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("fileName_without_extension", - new byte[0]); + new byte[1]); ResultActions result = mockMvc.perform( post("/userProfiles/avatar") @@ -204,7 +204,7 @@ void shouldPostAvatarReturn400OnIllegalInput() throws Exception { @Test void shouldPostAvatarReturn200OnRightInput() throws Exception { String accessToken = this.createAccessToken(); - ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("myFile.png", new byte[0]); + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("myFile.png", new byte[1]); when(avatarService.updateAvatar(any(), any(), any())).thenReturn("jack_bauer.png"); ResultActions result = mockMvc.perform( @@ -228,7 +228,7 @@ void shouldPostAvatarReturn400OnFileServiceAddFailureIfFileAlreadyPresent() thro }; doAnswer(ans).when(avatarService).updateAvatar(any(), any(), any()); ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("image.png", - new byte[0]); + new byte[1]); ResultActions result = mockMvc.perform( post("/userProfiles/avatar") @@ -239,19 +239,59 @@ void shouldPostAvatarReturn400OnFileServiceAddFailureIfFileAlreadyPresent() thro } @Test - void shouldPostAvatarReturn400OnWrongRequest() throws Exception { + void shouldPostAvatarReturn400OnAndCodeNotBlankIfFileNameIsNotPresent() throws Exception { String accessToken = this.createAccessToken(); - String request = "{\n" - + " \"fileNam\": \"image.png\",\n" - + " \"base64\": \"\"\n" - + "}"; + String request = "{\"fileNam\":\"image.png\",\"base64\":\"AA==\"}"; ResultActions result = mockMvc.perform( post("/userProfiles/avatar") .content(request) .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + accessToken)); - result.andExpect(status().isBadRequest()); + result.andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.errors[0].code").value("NotBlank")); + } + + @Test + void shouldPostAvatarReturn400OnAndCodeNotBlankIfFileNameIsEmpty() throws Exception { + String accessToken = this.createAccessToken(); + String request = "{\"fileName\":\"\",\"base64\":\"AA==\"}"; + + 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("NotBlank")); + } + + @Test + void shouldPostAvatarReturn400OnAndCodeNotEmptyIfBase64IsNotPresent() throws Exception { + String accessToken = this.createAccessToken(); + String request = "{\"fileName\":\"image.png\",\"base6\":\"AA==\"}"; + + 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("NotEmpty")); + } + + @Test + void shouldPostAvatarReturn400OnAndCodeNotEmptyIfBase64IsEmpty() throws Exception { + String accessToken = this.createAccessToken(); + String request = "{\"fileName\":\"image.png\",\"base64\":\"\"}"; + + 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("NotEmpty")); } private ResultActions performGetUserProfiles(String username) throws Exception { From 7938bc988e50c9ee39f6a771bfc9ea0f87b6fc9a Mon Sep 17 00:00:00 2001 From: "m.panagrosso" Date: Tue, 21 Nov 2023 18:16:01 +0100 Subject: [PATCH 17/82] ENG-5270: delete avatar end point --- .../services/userprofile/AvatarService.java | 29 ++++- .../services/userprofile/IAvatarService.java | 2 + .../web/userprofile/ProfileController.java | 12 ++ .../userprofile/AvatarServiceTest.java | 105 +++++++++++++++++- .../UserProfileControllerTest.java | 57 ++++------ 5 files changed, 168 insertions(+), 37 deletions(-) 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 index 0ff1f4fede..8aca2da875 100644 --- 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 @@ -11,6 +11,8 @@ import org.entando.entando.aps.system.services.entity.model.EntityDto; import org.entando.entando.aps.system.services.storage.IFileBrowserService; import org.entando.entando.aps.system.services.userprofile.model.AvatarDto; +import org.entando.entando.ent.exception.EntException; +import org.entando.entando.ent.exception.EntRuntimeException; import org.entando.entando.web.entity.validator.EntityValidator; import org.entando.entando.web.filebrowser.model.FileBrowserFileRequest; import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; @@ -70,6 +72,17 @@ public String updateAvatar(ProfileAvatarRequest request, UserDetails userDetails return fileBrowserFileRequest.getFilename(); } + @Override + public void deleteAvatar(UserDetails userDetails, BindingResult bindingResult) { + EntityDto userProfile = userProfileService.getUserProfile(userDetails.getUsername()); + // remove previous image if present + deletePrevUserAvatarFromFileSystemIfPresent(userProfile); + // update profile picture attribute (if present) with an empty value + resetProfilePictureAttribute(userProfile); + // update user profile with the fresh data related to profile picture + userProfileService.updateUserProfile(userProfile, bindingResult); + } + //------------------------ Utility methods ------------------------------------// private void updateUserProfilePictureAttribute(EntityDto userProfile, FileBrowserFileRequest fileBrowserFileRequest) { @@ -103,14 +116,25 @@ private FileBrowserFileRequest addProfileImageToFileSystem( private void deletePrevUserAvatarFromFileSystemIfPresent(EntityDto userProfile) { getProfilePictureAttribute(userProfile) + .filter(attribute -> StringUtils.isNotEmpty((String) attribute.getValue())) .ifPresent(attribute -> { String profilePicturePath = Paths.get(DEFAULT_AVATAR_PATH, (String) attribute.getValue()) .toString(); - fileBrowserService.deleteFile(profilePicturePath, false); + removePictureFromFilesystem(profilePicturePath); } ); } + private void removePictureFromFilesystem(String profilePicturePath) throws EntRuntimeException { + try { + if (fileBrowserService.exists(profilePicturePath)) { + fileBrowserService.deleteFile(profilePicturePath, false); + } + } catch (EntException e) { + throw new EntRuntimeException("Error in checking file existence on the filesystem", e); + } + } + private static FileBrowserFileRequest convertToFileBrowserFileRequest(ProfileAvatarRequest request, UserDetails userDetails) { FileBrowserFileRequest fileBrowserFileRequest = new FileBrowserFileRequest(); @@ -131,5 +155,8 @@ private Optional getProfilePictureAttribute(EntityDto userPr .findFirst()); } + private void resetProfilePictureAttribute(EntityDto userProfile) { + getProfilePictureAttribute(userProfile).ifPresent(attributeDto -> attributeDto.setValue("")); + } } 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 index dcec8d7669..965b186917 100644 --- 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 @@ -11,4 +11,6 @@ public interface IAvatarService { String updateAvatar(ProfileAvatarRequest request, UserDetails userDetails, BindingResult bindingResult); + + void deleteAvatar(UserDetails userDetails, BindingResult bindingResult); } 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 b03fe56885..39761c6941 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 @@ -44,6 +44,8 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; +import org.springframework.validation.MapBindingResult; +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; @@ -223,4 +225,14 @@ public ResponseEntity> addAvatar( return new ResponseEntity<>(new SimpleRestResponse<>(new ProfileAvatarResponse(pictureFileName)), HttpStatus.OK); } + + @DeleteMapping(path = "/userProfiles/avatar") + public ResponseEntity deleteAvatar(@RequestAttribute("user") UserDetails user) { + + // delete the profile picture associated with the received user profile. If no Image is found, the method does + // nothing and returns ok anyway + avatarService.deleteAvatar(user, new MapBindingResult(new HashMap<>(), "user")); + + return ResponseEntity.ok().build(); + } } 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 index cad98de61e..67a9430c3c 100644 --- 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 @@ -14,6 +14,8 @@ import org.entando.entando.aps.system.services.entity.model.EntityDto; import org.entando.entando.aps.system.services.storage.IFileBrowserService; import org.entando.entando.aps.system.services.userprofile.model.AvatarDto; +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.BeforeEach; import org.junit.jupiter.api.Test; @@ -75,7 +77,7 @@ void shouldGetAvatarDataThrowResourceNotFoundExceptionIfImageInProfileAttributeI } @Test - void shouldUpdateAvatarDeletePreviousProfilePictureIfPresent() { + void shouldUpdateAvatarDeletePreviousProfilePictureIfPresent() throws EntException { EntityDto entityDto = new EntityDto(); EntityAttributeDto entityAttributeDto = new EntityAttributeDto(); entityAttributeDto.setCode("profilepicture"); @@ -83,6 +85,9 @@ void shouldUpdateAvatarDeletePreviousProfilePictureIfPresent() { entityDto.setAttributes(List.of(entityAttributeDto)); when(userProfileService.getUserProfile(any())).thenReturn(entityDto); + //pretend image exists on filesystem + when(fileBrowserService.exists(any())).thenReturn(true); + avatarService.updateAvatar(mock(ProfileAvatarRequest.class), mock(UserDetails.class), mock(BindingResult.class)); verify(fileBrowserService, Mockito.times(1)).deleteFile(any(), any()); @@ -139,4 +144,102 @@ void shouldUpdateAvatarUserProfileAndSetRenamedProfilePictureIfNoPreviousPicture verify(userProfileService, Mockito.times(1)).updateUserProfile(captor.capture(), any()); assertEquals("user1.png", captor.getValue().getAttributes().get(0).getValue()); } + + @Test + void shouldDeleteAvatarFromFilesystemAndResetUserProfilePictureAttribute() throws EntException { + // set previous profile picture + EntityDto entityDto = new EntityDto(); + EntityAttributeDto entityAttributeDto = new EntityAttributeDto(); + entityAttributeDto.setCode("profilepicture"); + entityAttributeDto.setValue("user1.png"); + entityDto.setAttributes(List.of(entityAttributeDto)); + when(userProfileService.getUserProfile(any())).thenReturn(entityDto); + + // set user details to return desired username + UserDetails userDetails = mock(UserDetails.class); + when(userDetails.getUsername()).thenReturn("user1"); + + //pretend image exists on filesystem + when(fileBrowserService.exists(any())).thenReturn(true); + + avatarService.deleteAvatar(userDetails, mock(BindingResult.class)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(EntityDto.class); + verify(userProfileService, Mockito.times(1)).updateUserProfile(captor.capture(), any()); + assertEquals("", captor.getValue().getAttributes().get(0).getValue()); + verify(fileBrowserService, Mockito.times(1)).deleteFile(any(), any()); + } + + + @Test + void shouldDeleteAvatarDoNothingAndRunSmoothlyIfUserImageIsNotSetInTheProfile() { + // set previous profile picture + EntityDto entityDto = new EntityDto(); + EntityAttributeDto entityAttributeDto = new EntityAttributeDto(); + entityAttributeDto.setCode("profilepicture"); + entityAttributeDto.setValue(""); + entityDto.setAttributes(List.of(entityAttributeDto)); + when(userProfileService.getUserProfile(any())).thenReturn(entityDto); + + // set user details to return desired username + UserDetails userDetails = mock(UserDetails.class); + when(userDetails.getUsername()).thenReturn("user1"); + + avatarService.deleteAvatar(userDetails, mock(BindingResult.class)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(EntityDto.class); + verify(userProfileService, Mockito.times(1)).updateUserProfile(captor.capture(), any()); + assertEquals("", captor.getValue().getAttributes().get(0).getValue()); + verify(fileBrowserService, Mockito.times(0)).deleteFile(any(), any()); + } + + + @Test + void shouldDeleteAvatarThrowExceptionIfProfilePictureCheckImageGoesInError() throws EntException { + // set previous profile picture + EntityDto entityDto = new EntityDto(); + EntityAttributeDto entityAttributeDto = new EntityAttributeDto(); + entityAttributeDto.setCode("profilepicture"); + entityAttributeDto.setValue("user1.png"); + entityDto.setAttributes(List.of(entityAttributeDto)); + when(userProfileService.getUserProfile(any())).thenReturn(entityDto); + + // set user details to return desired username + UserDetails userDetails = mock(UserDetails.class); + when(userDetails.getUsername()).thenReturn("user1"); + + //pretend fileBrowserService.exists goes in error + when(fileBrowserService.exists(any())).thenThrow(EntException.class); + + assertThrows(EntRuntimeException.class, + () -> avatarService.deleteAvatar(userDetails, mock(BindingResult.class))); + } + + @Test + void shouldUpdateAvatarThrowExceptionIfProfilePictureCheckImageGoesInError() throws EntException { + // set previous profile picture + EntityDto entityDto = new EntityDto(); + EntityAttributeDto entityAttributeDto = new EntityAttributeDto(); + entityAttributeDto.setCode("profilepicture"); + entityAttributeDto.setValue("user1.png"); + entityDto.setAttributes(List.of(entityAttributeDto)); + when(userProfileService.getUserProfile(any())).thenReturn(entityDto); + + // set user details to return desired username + UserDetails userDetails = mock(UserDetails.class); + when(userDetails.getUsername()).thenReturn("user1"); + + // set POST request DTO + ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest(); + profileAvatarRequest.setFileName("image.png"); + profileAvatarRequest.setBase64(new byte[0]); + + //pretend fileBrowserService.exists goes in error + when(fileBrowserService.exists(any())).thenThrow(EntException.class); + + assertThrows(EntRuntimeException.class, + () -> avatarService.updateAvatar(profileAvatarRequest, userDetails, mock(BindingResult.class))); + + + } } 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 c115fee161..f08d390f27 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 @@ -16,6 +16,7 @@ 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.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; @@ -26,6 +27,7 @@ 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.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; @@ -41,6 +43,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +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.junit.jupiter.MockitoExtension; @@ -238,24 +243,11 @@ void shouldPostAvatarReturn400OnFileServiceAddFailureIfFileAlreadyPresent() thro result.andExpect(status().isBadRequest()); } - @Test - void shouldPostAvatarReturn400OnAndCodeNotBlankIfFileNameIsNotPresent() throws Exception { - String accessToken = this.createAccessToken(); - String request = "{\"fileNam\":\"image.png\",\"base64\":\"AA==\"}"; - 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("NotBlank")); - } - - @Test - void shouldPostAvatarReturn400OnAndCodeNotBlankIfFileNameIsEmpty() throws Exception { + @ParameterizedTest + @MethodSource("provideValuesFor400") + void shouldPostAvatarReturn400(String request, String expectedErrorCode) throws Exception { String accessToken = this.createAccessToken(); - String request = "{\"fileName\":\"\",\"base64\":\"AA==\"}"; ResultActions result = mockMvc.perform( post("/userProfiles/avatar") @@ -263,35 +255,30 @@ void shouldPostAvatarReturn400OnAndCodeNotBlankIfFileNameIsEmpty() throws Except .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + accessToken)); result.andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.errors[0].code").value("NotBlank")); + .andExpect(jsonPath("$.errors[0].code").value(expectedErrorCode)); } + @Test - void shouldPostAvatarReturn400OnAndCodeNotEmptyIfBase64IsNotPresent() throws Exception { + void shouldDeleteAvatarReturn200() throws Exception { String accessToken = this.createAccessToken(); - String request = "{\"fileName\":\"image.png\",\"base6\":\"AA==\"}"; ResultActions result = mockMvc.perform( - post("/userProfiles/avatar") - .content(request) - .contentType(MediaType.APPLICATION_JSON_VALUE) + delete("/userProfiles/avatar") .header("Authorization", "Bearer " + accessToken)); - result.andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.errors[0].code").value("NotEmpty")); + result.andExpect(status().isOk()); } - @Test - void shouldPostAvatarReturn400OnAndCodeNotEmptyIfBase64IsEmpty() throws Exception { - String accessToken = this.createAccessToken(); - String request = "{\"fileName\":\"image.png\",\"base64\":\"\"}"; - 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("NotEmpty")); + private static Stream provideValuesFor400() { + return Stream.of( + Arguments.of("{\"fileNam\":\"image.png\",\"base64\":\"AA==\"}", "NotBlank"), + Arguments.of("{\"base64\":\"AA==\"}", "NotBlank"), + Arguments.of("{\"fileName\":\"\",\"base64\":\"AA==\"}", "NotBlank"), + Arguments.of("{\"fileName\":\"image.png\",\"base6\":\"AA==\"}", "NotEmpty"), + Arguments.of("{\"fileName\":\"image.png\"}", "NotEmpty"), + Arguments.of("{\"fileName\":\"image.png\",\"base64\":\"\"}", "NotEmpty") + ); } private ResultActions performGetUserProfiles(String username) throws Exception { From 21a034f7cc7757c5034f1a9280ba9ebce2761b25 Mon Sep 17 00:00:00 2001 From: "m.panagrosso" Date: Thu, 23 Nov 2023 18:08:50 +0100 Subject: [PATCH 18/82] ENG-5253: managed 422 in case of wrong typeCode --- .../jacms/web/content/ContentController.java | 31 ++++++++++++------- .../content/validator/ContentValidator.java | 11 +++++++ .../web/content/ContentControllerTest.java | 27 +++++++++++++++- .../main/resources/rest/messages.properties | 1 + 4 files changed, 57 insertions(+), 13 deletions(-) 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/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/engine/src/main/resources/rest/messages.properties b/engine/src/main/resources/rest/messages.properties index 7eda70d3d0..f4420f8253 100644 --- a/engine/src/main/resources/rest/messages.properties +++ b/engine/src/main/resources/rest/messages.properties @@ -357,6 +357,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 From f0ed3a2e7792e8af5e3040400f3bdf352a524945 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Tue, 5 Dec 2023 10:49:17 +0100 Subject: [PATCH 19/82] ENG-5270: Fix AvatarService --- .../agiletec/aps/system/SystemConstants.java | 3 - .../services/userprofile/AvatarService.java | 173 ++++++++--------- .../userprofile/AvatarServiceTest.java | 174 +++++++++--------- 3 files changed, 176 insertions(+), 174 deletions(-) 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..93676ee290 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. 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 index 8aca2da875..e1963544a5 100644 --- 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 @@ -1,16 +1,20 @@ package org.entando.entando.aps.system.services.userprofile; +import com.agiletec.aps.system.SystemConstants; +import com.agiletec.aps.system.common.entity.model.attribute.AttributeInterface; +import com.agiletec.aps.system.common.entity.model.attribute.MonoTextAttribute; import com.agiletec.aps.system.services.user.UserDetails; import java.nio.file.Paths; 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.services.entity.model.EntityAttributeDto; -import org.entando.entando.aps.system.services.entity.model.EntityDto; +import org.entando.entando.aps.system.exception.RestServerError; import org.entando.entando.aps.system.services.storage.IFileBrowserService; 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.exception.EntRuntimeException; import org.entando.entando.web.entity.validator.EntityValidator; @@ -18,91 +22,96 @@ import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; import org.springframework.validation.BindingResult; +@Slf4j @RequiredArgsConstructor public class AvatarService implements IAvatarService { // Services private final IFileBrowserService fileBrowserService; - private final IUserProfileService userProfileService; + private final IUserProfileManager userProfileManager; // CONSTANTS - public static final String PROFILE_PICTURE = "profilepicture"; private static final String DEFAULT_AVATAR_PATH = "static/profile"; public AvatarDto getAvatarData(UserDetails userDetails) { - - // get profile picture attribute from user profile. The value of this attribute contains the file name of the - // profile image associated with the user profile - EntityDto userProfile = userProfileService.getUserProfile(userDetails.getUsername()); - EntityAttributeDto profilePictureAttribute = getProfilePictureAttribute(userProfile) - .orElseThrow(() -> new ResourceNotFoundException(EntityValidator.ERRCODE_ENTITY_DOES_NOT_EXIST, "image", - userDetails.getUsername())); - // set default params - boolean protectedFolder = false; - String fileName = Optional.ofNullable((String) profilePictureAttribute.getValue()) - .filter(StringUtils::isNotEmpty) - .orElseThrow(() -> new ResourceNotFoundException(EntityValidator.ERRCODE_ENTITY_DOES_NOT_EXIST, "image", - userDetails.getUsername())); - // all profiles pictures are saved in the same location at DEFAULT_AVATAR_PATH - // This is why each profile picture is named with the same name as the owner user - 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(); + try { + // get profile picture attribute from user profile. The value of this attribute contains the file name of the + // profile image associated with the user profile + IUserProfile userProfile = userProfileManager.getProfile(userDetails.getUsername()); + AttributeInterface profilePictureAttribute = getProfilePictureAttribute(userProfile) + .orElseThrow(() -> new ResourceNotFoundException(EntityValidator.ERRCODE_ENTITY_DOES_NOT_EXIST, "image", + userDetails.getUsername())); + // set default params + boolean protectedFolder = false; + String fileName = Optional.ofNullable((String) profilePictureAttribute.getValue()) + .filter(StringUtils::isNotEmpty) + .orElseThrow(() -> new ResourceNotFoundException(EntityValidator.ERRCODE_ENTITY_DOES_NOT_EXIST, "image", + userDetails.getUsername())); + // all profiles pictures are saved in the same location at DEFAULT_AVATAR_PATH + // This is why each profile picture is named with the same name as the owner user + 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) { - EntityDto userProfile = userProfileService.getUserProfile(userDetails.getUsername()); - // remove previous image if present - deletePrevUserAvatarFromFileSystemIfPresent(userProfile); - // add profile picture file - FileBrowserFileRequest fileBrowserFileRequest = addProfileImageToFileSystem(request, userDetails, - bindingResult); - // update or add profile picture attribute - updateProfilePicture(bindingResult, userProfile, fileBrowserFileRequest); - return fileBrowserFileRequest.getFilename(); + try { + IUserProfile userProfile = userProfileManager.getProfile(userDetails.getUsername()); + if (null == userProfile) { + userProfile = this.userProfileManager.getDefaultProfileType(); + userProfile.setId(userDetails.getUsername()); + this.userProfileManager.addProfile(userDetails.getUsername(), userProfile); + } + // remove previous image if present + deletePrevUserAvatarFromFileSystemIfPresent(userProfile); + // add profile picture file + FileBrowserFileRequest fileBrowserFileRequest = addProfileImageToFileSystem(request, userDetails, + bindingResult); + // update profile picture attribute or add a new one if no image was already set by user + this.setProfilePictureAttribute(userProfile, fileBrowserFileRequest.getFilename()); + // update user profile with the fresh data related to profile picture + userProfileManager.updateProfile(userProfile.getId(), userProfile); + 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) { - EntityDto userProfile = userProfileService.getUserProfile(userDetails.getUsername()); - // remove previous image if present - deletePrevUserAvatarFromFileSystemIfPresent(userProfile); - // update profile picture attribute (if present) with an empty value - resetProfilePictureAttribute(userProfile); - // update user profile with the fresh data related to profile picture - userProfileService.updateUserProfile(userProfile, bindingResult); + try { + IUserProfile userProfile = userProfileManager.getProfile(userDetails.getUsername()); + // remove previous image if present + this.deletePrevUserAvatarFromFileSystemIfPresent(userProfile); + // update profile picture attribute (if present) with an empty value + this.setProfilePictureAttribute(userProfile, null); + // update user profile with the fresh data related to profile picture + userProfileManager.updateProfile(userProfile.getId(), userProfile); + } catch (Exception e) { + log.error("Error deleting avatar", e); + throw new RestServerError("Error deleting avatar", e); + } } //------------------------ Utility methods ------------------------------------// - private void updateUserProfilePictureAttribute(EntityDto userProfile, - FileBrowserFileRequest fileBrowserFileRequest) { - getProfilePictureAttribute(userProfile).ifPresentOrElse( - attributeDto -> attributeDto.setValue(fileBrowserFileRequest.getFilename()), () -> { - EntityAttributeDto profileAttribute = new EntityAttributeDto(); - profileAttribute.setCode(PROFILE_PICTURE); - profileAttribute.setValue(fileBrowserFileRequest.getFilename()); - userProfile.getAttributes().add(profileAttribute); - }); - } - - private void updateProfilePicture(BindingResult bindingResult, EntityDto userProfile, - FileBrowserFileRequest fileBrowserFileRequest) { - // update profile picture attribute or add a new one if no image was already set by user - updateUserProfilePictureAttribute(userProfile, fileBrowserFileRequest); - // update user profile with the fresh data related to profile picture - userProfileService.updateUserProfile(userProfile, bindingResult); - } - + private FileBrowserFileRequest addProfileImageToFileSystem( ProfileAvatarRequest request, UserDetails userDetails, BindingResult bindingResult) { @@ -114,14 +123,13 @@ private FileBrowserFileRequest addProfileImageToFileSystem( } - private void deletePrevUserAvatarFromFileSystemIfPresent(EntityDto userProfile) { - getProfilePictureAttribute(userProfile) - .filter(attribute -> StringUtils.isNotEmpty((String) attribute.getValue())) + private void deletePrevUserAvatarFromFileSystemIfPresent(IUserProfile userProfile) { + this.getProfilePictureAttribute(userProfile) .ifPresent(attribute -> { - String profilePicturePath = Paths.get(DEFAULT_AVATAR_PATH, (String) attribute.getValue()) - .toString(); - removePictureFromFilesystem(profilePicturePath); - } + String profilePicturePath = Paths.get(DEFAULT_AVATAR_PATH, (String) attribute.getValue()) + .toString(); + removePictureFromFilesystem(profilePicturePath); + } ); } @@ -146,17 +154,16 @@ private static FileBrowserFileRequest convertToFileBrowserFileRequest(ProfileAva fileBrowserFileRequest.setBase64(request.getBase64()); return fileBrowserFileRequest; } - - - private Optional getProfilePictureAttribute(EntityDto userProfile) { - return Optional.ofNullable(userProfile.getAttributes()) - .flatMap(attributes -> attributes.stream() - .filter(entityAttributeDto -> entityAttributeDto.getCode().equals(PROFILE_PICTURE)) - .findFirst()); + + private Optional getProfilePictureAttribute(IUserProfile userProfile) { + return Optional.ofNullable(userProfile).map(up -> up.getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE)); } - - private void resetProfilePictureAttribute(EntityDto userProfile) { - getProfilePictureAttribute(userProfile).ifPresent(attributeDto -> attributeDto.setValue("")); + + private void setProfilePictureAttribute(IUserProfile userProfile, String value) { + getProfilePictureAttribute(userProfile).ifPresent(attribute -> { + MonoTextAttribute textAtt = (MonoTextAttribute) attribute; + textAtt.setText(value); + }); } } 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 index 67a9430c3c..9fa2b5370f 100644 --- 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 @@ -7,15 +7,16 @@ import static org.mockito.Mockito.verify; 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.services.user.UserDetails; -import java.util.List; import org.entando.entando.aps.system.exception.ResourceNotFoundException; -import org.entando.entando.aps.system.services.entity.model.EntityAttributeDto; -import org.entando.entando.aps.system.services.entity.model.EntityDto; +import org.entando.entando.aps.system.exception.RestServerError; import org.entando.entando.aps.system.services.storage.IFileBrowserService; import org.entando.entando.aps.system.services.userprofile.model.AvatarDto; +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.entando.entando.ent.exception.EntRuntimeException; import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -30,7 +31,7 @@ class AvatarServiceTest { @Mock - private IUserProfileService userProfileService; + private IUserProfileManager userProfileManager; @Mock private IFileBrowserService fileBrowserService; @@ -38,17 +39,13 @@ class AvatarServiceTest { @BeforeEach void init() { - avatarService = new AvatarService(fileBrowserService, userProfileService); + avatarService = new AvatarService(fileBrowserService, userProfileManager); } @Test - void shouldGetAvatarDataReturnAvatarInfo() { - EntityDto entityDto = new EntityDto(); - EntityAttributeDto entityAttributeDto = new EntityAttributeDto(); - entityAttributeDto.setCode("profilepicture"); - entityAttributeDto.setValue("image.png"); - entityDto.setAttributes(List.of(entityAttributeDto)); - when(userProfileService.getUserProfile(any())).thenReturn(entityDto); + void shouldGetAvatarDataReturnAvatarInfo() throws EntException { + IUserProfile profile = this.buildValidUserProfile("username", "image.png"); + when(userProfileManager.getProfile(any())).thenReturn(profile); when(fileBrowserService.getFileStream(any(), any())).thenReturn(new byte[0]); AvatarDto avatarData = avatarService.getAvatarData(mock(UserDetails.class)); @@ -56,61 +53,65 @@ void shouldGetAvatarDataReturnAvatarInfo() { assertEquals("image.png", avatarData.getFilename()); assertEquals("static/profile/image.png", avatarData.getCurrentPath()); } - + + @Test + void shouldGetAvatarDataThrowResourceServerError() throws EntException { + when(userProfileManager.getProfile(any())).thenThrow(EntException.class); + assertThrows(RestServerError.class, () -> avatarService.getAvatarData(mock(UserDetails.class))); + } + @Test - void shouldGetAvatarDataThrowResourceNotFoundExceptionIfNoImageIsPresent() { - when(userProfileService.getUserProfile(any())).thenReturn(new EntityDto()); + void shouldGetAvatarDataThrowResourceNotFoundExceptionIfNoImageIsPresent() throws EntException { + when(userProfileManager.getProfile(any())).thenReturn(new UserProfile()); assertThrows(ResourceNotFoundException.class, () -> avatarService.getAvatarData(mock(UserDetails.class))); } @Test - void shouldGetAvatarDataThrowResourceNotFoundExceptionIfImageInProfileAttributeIsEmpty() { - EntityDto entityDto = new EntityDto(); - EntityAttributeDto entityAttributeDto = new EntityAttributeDto(); - entityAttributeDto.setCode("profilepicture"); - entityAttributeDto.setValue(""); - entityDto.setAttributes(List.of(entityAttributeDto)); - when(userProfileService.getUserProfile(any())).thenReturn(entityDto); - + void shouldGetAvatarDataThrowResourceNotFoundExceptionIfImageInProfileAttributeIsEmpty() throws EntException { + IUserProfile profile = this.buildValidUserProfile("username", ""); + when(userProfileManager.getProfile(any())).thenReturn(profile); assertThrows(ResourceNotFoundException.class, () -> avatarService.getAvatarData(mock(UserDetails.class))); + } + @Test + void shouldUpdateAvatarWithNullProfile() throws EntException { + when(userProfileManager.getProfile("username")).thenReturn(null); + IUserProfile prototype = this.buildValidUserProfile(null, null); + when(userProfileManager.getDefaultProfileType()).thenReturn(prototype); + when(fileBrowserService.exists(any())).thenReturn(true); + UserDetails userDetails = mock(UserDetails.class); + when(userDetails.getUsername()).thenReturn("username"); + avatarService.updateAvatar(mock(ProfileAvatarRequest.class), userDetails, + mock(BindingResult.class)); + verify(fileBrowserService, Mockito.times(1)).deleteFile(any(), any()); + verify(prototype, Mockito.times(1)).setId("username"); + verify(userProfileManager, Mockito.times(1)).addProfile("username", prototype); } @Test void shouldUpdateAvatarDeletePreviousProfilePictureIfPresent() throws EntException { - EntityDto entityDto = new EntityDto(); - EntityAttributeDto entityAttributeDto = new EntityAttributeDto(); - entityAttributeDto.setCode("profilepicture"); - entityAttributeDto.setValue("prevImage.png"); - entityDto.setAttributes(List.of(entityAttributeDto)); - when(userProfileService.getUserProfile(any())).thenReturn(entityDto); - + IUserProfile profile = this.buildValidUserProfile("username", "prevImage.png"); + when(userProfileManager.getProfile(any())).thenReturn(profile); //pretend image exists on filesystem when(fileBrowserService.exists(any())).thenReturn(true); - avatarService.updateAvatar(mock(ProfileAvatarRequest.class), mock(UserDetails.class), mock(BindingResult.class)); verify(fileBrowserService, Mockito.times(1)).deleteFile(any(), any()); - } @Test - void shouldUpdateAvatarAddProfilePictureFromTheRequest() { - when(userProfileService.getUserProfile(any())).thenReturn(new EntityDto()); + void shouldUpdateAvatarAddProfilePictureFromTheRequest() throws EntException { + when(userProfileManager.getProfile(any())).thenReturn(new UserProfile()); avatarService.updateAvatar(mock(ProfileAvatarRequest.class), mock(UserDetails.class), mock(BindingResult.class)); verify(fileBrowserService, Mockito.times(1)).addFile(any(), any()); } @Test - void shouldUpdateAvatarUserProfileAndRenameProfilePictureWithUserName() { + void shouldUpdateAvatarUserProfileAndRenameProfilePictureWithUserName() throws EntException { // set previous profile picture - EntityDto entityDto = new EntityDto(); - EntityAttributeDto entityAttributeDto = new EntityAttributeDto(); - entityAttributeDto.setCode("profilepicture"); - entityAttributeDto.setValue("prevImage.png"); - entityDto.setAttributes(List.of(entityAttributeDto)); - when(userProfileService.getUserProfile(any())).thenReturn(entityDto); + IUserProfile profile = this.buildValidUserProfile("user1", "prevImage.png"); + when(userProfileManager.getProfile(any())).thenReturn(profile); // set POST request DTO ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest(); profileAvatarRequest.setFileName("image.png"); @@ -120,16 +121,18 @@ void shouldUpdateAvatarUserProfileAndRenameProfilePictureWithUserName() { when(userDetails.getUsername()).thenReturn("user1"); avatarService.updateAvatar(profileAvatarRequest, userDetails, mock(BindingResult.class)); - - ArgumentCaptor captor = ArgumentCaptor.forClass(EntityDto.class); - verify(userProfileService, Mockito.times(1)).updateUserProfile(captor.capture(), any()); - assertEquals("user1.png", captor.getValue().getAttributes().get(0).getValue()); + + ArgumentCaptor captorProfile = ArgumentCaptor.forClass(IUserProfile.class); + verify(userProfileManager, Mockito.times(1)).updateProfile(Mockito.eq("user1"), captorProfile.capture()); + assertEquals("user1.png", captorProfile.getValue() + .getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE).getValue()); } @Test - void shouldUpdateAvatarUserProfileAndSetRenamedProfilePictureIfNoPreviousPictureWasPresent() { + void shouldUpdateAvatarUserProfileAndSetRenamedProfilePictureIfNoPreviousPictureWasPresent() throws EntException { // set previous profile picture - when(userProfileService.getUserProfile(any())).thenReturn(new EntityDto()); + IUserProfile profile = this.buildValidUserProfile("user1", null); + when(userProfileManager.getProfile(any())).thenReturn(profile); // set POST request DTO ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest(); profileAvatarRequest.setFileName("image.png"); @@ -140,20 +143,17 @@ void shouldUpdateAvatarUserProfileAndSetRenamedProfilePictureIfNoPreviousPicture avatarService.updateAvatar(profileAvatarRequest, userDetails, mock(BindingResult.class)); - ArgumentCaptor captor = ArgumentCaptor.forClass(EntityDto.class); - verify(userProfileService, Mockito.times(1)).updateUserProfile(captor.capture(), any()); - assertEquals("user1.png", captor.getValue().getAttributes().get(0).getValue()); + ArgumentCaptor captorProfile = ArgumentCaptor.forClass(IUserProfile.class); + verify(userProfileManager, Mockito.times(1)).updateProfile(Mockito.eq("user1"), captorProfile.capture()); + assertEquals("user1.png", captorProfile.getValue() + .getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE).getValue()); } @Test void shouldDeleteAvatarFromFilesystemAndResetUserProfilePictureAttribute() throws EntException { // set previous profile picture - EntityDto entityDto = new EntityDto(); - EntityAttributeDto entityAttributeDto = new EntityAttributeDto(); - entityAttributeDto.setCode("profilepicture"); - entityAttributeDto.setValue("user1.png"); - entityDto.setAttributes(List.of(entityAttributeDto)); - when(userProfileService.getUserProfile(any())).thenReturn(entityDto); + IUserProfile profile = this.buildValidUserProfile("user1", "user1.png"); + when(userProfileManager.getProfile(any())).thenReturn(profile); // set user details to return desired username UserDetails userDetails = mock(UserDetails.class); @@ -163,23 +163,20 @@ void shouldDeleteAvatarFromFilesystemAndResetUserProfilePictureAttribute() throw when(fileBrowserService.exists(any())).thenReturn(true); avatarService.deleteAvatar(userDetails, mock(BindingResult.class)); - - ArgumentCaptor captor = ArgumentCaptor.forClass(EntityDto.class); - verify(userProfileService, Mockito.times(1)).updateUserProfile(captor.capture(), any()); - assertEquals("", captor.getValue().getAttributes().get(0).getValue()); + + ArgumentCaptor captorProfile = ArgumentCaptor.forClass(IUserProfile.class); + verify(userProfileManager, Mockito.times(1)).updateProfile(Mockito.eq("user1"), captorProfile.capture()); + assertEquals("", captorProfile.getValue() + .getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE).getValue()); verify(fileBrowserService, Mockito.times(1)).deleteFile(any(), any()); } @Test - void shouldDeleteAvatarDoNothingAndRunSmoothlyIfUserImageIsNotSetInTheProfile() { + void shouldDeleteAvatarDoNothingAndRunSmoothlyIfUserImageIsNotSetInTheProfile() throws EntException { // set previous profile picture - EntityDto entityDto = new EntityDto(); - EntityAttributeDto entityAttributeDto = new EntityAttributeDto(); - entityAttributeDto.setCode("profilepicture"); - entityAttributeDto.setValue(""); - entityDto.setAttributes(List.of(entityAttributeDto)); - when(userProfileService.getUserProfile(any())).thenReturn(entityDto); + IUserProfile profile = this.buildValidUserProfile("user1", ""); + when(userProfileManager.getProfile(any())).thenReturn(profile); // set user details to return desired username UserDetails userDetails = mock(UserDetails.class); @@ -187,22 +184,18 @@ void shouldDeleteAvatarDoNothingAndRunSmoothlyIfUserImageIsNotSetInTheProfile() avatarService.deleteAvatar(userDetails, mock(BindingResult.class)); - ArgumentCaptor captor = ArgumentCaptor.forClass(EntityDto.class); - verify(userProfileService, Mockito.times(1)).updateUserProfile(captor.capture(), any()); - assertEquals("", captor.getValue().getAttributes().get(0).getValue()); + ArgumentCaptor captorProfile = ArgumentCaptor.forClass(IUserProfile.class); + verify(userProfileManager, Mockito.times(1)).updateProfile(Mockito.eq("user1"), captorProfile.capture()); + assertEquals("", captorProfile.getValue() + .getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE).getValue()); verify(fileBrowserService, Mockito.times(0)).deleteFile(any(), any()); } - @Test void shouldDeleteAvatarThrowExceptionIfProfilePictureCheckImageGoesInError() throws EntException { // set previous profile picture - EntityDto entityDto = new EntityDto(); - EntityAttributeDto entityAttributeDto = new EntityAttributeDto(); - entityAttributeDto.setCode("profilepicture"); - entityAttributeDto.setValue("user1.png"); - entityDto.setAttributes(List.of(entityAttributeDto)); - when(userProfileService.getUserProfile(any())).thenReturn(entityDto); + IUserProfile profile = this.buildValidUserProfile("user1", "user1.png"); + when(userProfileManager.getProfile(any())).thenReturn(profile); // set user details to return desired username UserDetails userDetails = mock(UserDetails.class); @@ -211,19 +204,15 @@ void shouldDeleteAvatarThrowExceptionIfProfilePictureCheckImageGoesInError() thr //pretend fileBrowserService.exists goes in error when(fileBrowserService.exists(any())).thenThrow(EntException.class); - assertThrows(EntRuntimeException.class, + assertThrows(RestServerError.class, () -> avatarService.deleteAvatar(userDetails, mock(BindingResult.class))); } @Test void shouldUpdateAvatarThrowExceptionIfProfilePictureCheckImageGoesInError() throws EntException { // set previous profile picture - EntityDto entityDto = new EntityDto(); - EntityAttributeDto entityAttributeDto = new EntityAttributeDto(); - entityAttributeDto.setCode("profilepicture"); - entityAttributeDto.setValue("user1.png"); - entityDto.setAttributes(List.of(entityAttributeDto)); - when(userProfileService.getUserProfile(any())).thenReturn(entityDto); + IUserProfile profile = this.buildValidUserProfile("user1", "user1.png"); + when(userProfileManager.getProfile(any())).thenReturn(profile); // set user details to return desired username UserDetails userDetails = mock(UserDetails.class); @@ -237,9 +226,18 @@ void shouldUpdateAvatarThrowExceptionIfProfilePictureCheckImageGoesInError() thr //pretend fileBrowserService.exists goes in error when(fileBrowserService.exists(any())).thenThrow(EntException.class); - assertThrows(EntRuntimeException.class, + assertThrows(RestServerError.class, () -> avatarService.updateAvatar(profileAvatarRequest, userDetails, mock(BindingResult.class))); - - } + + private IUserProfile buildValidUserProfile(String username, String attributeValue) { + IUserProfile profile = Mockito.mock(IUserProfile.class); + Mockito.lenient().when(profile.getId()).thenReturn(username); + MonoTextAttribute attribute = new MonoTextAttribute(); + attribute.setName("profilepicture"); + attribute.setText(attributeValue); + when(profile.getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE)).thenReturn(attribute); + return profile; + } + } From 953fa6578ebc18d6a20f32ef019eeddb91462e1c Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Wed, 6 Dec 2023 13:03:08 +0100 Subject: [PATCH 20/82] ENG-5068: Fix NotFound Page status code --- admin-console/src/main/webapp/404.jsp | 1 - admin-console/src/main/webapp/error.jsp | 4 +-- admin-console/src/main/webapp/index.jsp | 4 --- admin-console/src/main/webapp/logout.jsp | 5 ++-- .../controller/control/RequestValidator.java | 29 ++++++++++--------- portal-ui/src/main/webapp/404.jsp | 4 +++ .../control/TestRequestValidator.java | 23 +++++++++------ 7 files changed, 39 insertions(+), 31 deletions(-) delete mode 100644 admin-console/src/main/webapp/404.jsp delete mode 100644 admin-console/src/main/webapp/index.jsp create mode 100644 portal-ui/src/main/webapp/404.jsp 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/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/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..9848997f11 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 @@ -29,6 +29,7 @@ import com.agiletec.aps.system.services.page.IPage; import com.agiletec.aps.system.services.page.IPageManager; import com.agiletec.aps.system.services.page.PageUtils; +import java.util.Optional; /** * Implementazione del un sottoservizio di controllo che verifica la validità @@ -72,21 +73,23 @@ public int service(RequestContext reqCtx, int status) { 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/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); } From 4a00b3f166f76ebfb966ecd24a8efec98c71aa45 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Wed, 6 Dec 2023 14:45:03 +0100 Subject: [PATCH 21/82] ENG-5068: Code improvement --- .../control/AbstractControlService.java | 5 +++++ .../controller/control/Authenticator.java | 6 ------ .../controller/control/ErrorManager.java | 5 ----- .../controller/control/RequestAuthorizator.java | 5 ----- .../controller/control/RequestValidator.java | 16 ---------------- 5 files changed, 5 insertions(+), 32 deletions(-) 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 9848997f11..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 @@ -29,7 +29,6 @@ import com.agiletec.aps.system.services.page.IPage; import com.agiletec.aps.system.services.page.IPageManager; import com.agiletec.aps.system.services.page.PageUtils; -import java.util.Optional; /** * Implementazione del un sottoservizio di controllo che verifica la validità @@ -51,25 +50,10 @@ 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; } From ca4b39aadf13f8f5ab3dfee9473571bf30e088be Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Fri, 15 Dec 2023 09:27:47 +0100 Subject: [PATCH 22/82] ENG-5368: Fix bug in delete root page request --- .../entando/web/page/PageController.java | 8 +----- .../web/page/validator/PageValidator.java | 10 +++++++ .../main/resources/rest/messages.properties | 1 + .../entando/web/page/PageControllerTest.java | 28 +++++++++++++++---- 4 files changed, 34 insertions(+), 13 deletions(-) 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/resources/rest/messages.properties b/engine/src/main/resources/rest/messages.properties index ee38b813ec..6c2f39a945 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 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))); } From bad9f4d620effb75852cc069e5cad1108c474251 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Fri, 15 Dec 2023 17:00:33 +0100 Subject: [PATCH 23/82] ENG-5368: Fix bug on publish root page --- .../aps/system/services/page/PageService.java | 12 +++--- .../system/services/page/PageServiceTest.java | 43 ++++++------------- 2 files changed, 20 insertions(+), 35 deletions(-) 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/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); From a338389fdfa1d1974c30a2572fd8b8135651ec07 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Wed, 20 Dec 2023 17:19:16 +0100 Subject: [PATCH 24/82] ENG-5405: Improvement Avatar API --- .../services/userprofile/AvatarService.java | 103 ++++++++++-------- .../userprofile/AvatarServiceTest.java | 27 +++-- 2 files changed, 78 insertions(+), 52 deletions(-) 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 index e1963544a5..3dbca49837 100644 --- 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 @@ -5,7 +5,9 @@ import com.agiletec.aps.system.common.entity.model.attribute.MonoTextAttribute; import com.agiletec.aps.system.services.user.UserDetails; import java.nio.file.Paths; +import java.util.List; import java.util.Optional; +import java.util.function.Consumer; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FilenameUtils; @@ -13,6 +15,7 @@ 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.userprofile.model.AvatarDto; import org.entando.entando.aps.system.services.userprofile.model.IUserProfile; import org.entando.entando.ent.exception.EntException; @@ -33,24 +36,16 @@ public class AvatarService implements IAvatarService { // CONSTANTS private static final String DEFAULT_AVATAR_PATH = "static/profile"; + @Override public AvatarDto getAvatarData(UserDetails userDetails) { try { - // get profile picture attribute from user profile. The value of this attribute contains the file name of the - // profile image associated with the user profile - IUserProfile userProfile = userProfileManager.getProfile(userDetails.getUsername()); - AttributeInterface profilePictureAttribute = getProfilePictureAttribute(userProfile) - .orElseThrow(() -> new ResourceNotFoundException(EntityValidator.ERRCODE_ENTITY_DOES_NOT_EXIST, "image", - userDetails.getUsername())); - // set default params + String fileName = this.getAvatarFilename(userDetails); + if (StringUtils.isEmpty(fileName)) { + throw new ResourceNotFoundException(EntityValidator.ERRCODE_ENTITY_DOES_NOT_EXIST, "image", + userDetails.getUsername()); + } boolean protectedFolder = false; - String fileName = Optional.ofNullable((String) profilePictureAttribute.getValue()) - .filter(StringUtils::isNotEmpty) - .orElseThrow(() -> new ResourceNotFoundException(EntityValidator.ERRCODE_ENTITY_DOES_NOT_EXIST, "image", - userDetails.getUsername())); - // all profiles pictures are saved in the same location at DEFAULT_AVATAR_PATH - // This is why each profile picture is named with the same name as the owner user 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 @@ -72,21 +67,17 @@ public AvatarDto getAvatarData(UserDetails userDetails) { @Override public String updateAvatar(ProfileAvatarRequest request, UserDetails userDetails, BindingResult bindingResult) { try { - IUserProfile userProfile = userProfileManager.getProfile(userDetails.getUsername()); - if (null == userProfile) { - userProfile = this.userProfileManager.getDefaultProfileType(); - userProfile.setId(userDetails.getUsername()); - this.userProfileManager.addProfile(userDetails.getUsername(), userProfile); - } + String username = userDetails.getUsername(); + IUserProfile userProfile = userProfileManager.getProfile(username); // remove previous image if present - deletePrevUserAvatarFromFileSystemIfPresent(userProfile); + deletePrevUserAvatarFromFileSystemIfPresent(username, userProfile); // add profile picture file - FileBrowserFileRequest fileBrowserFileRequest = addProfileImageToFileSystem(request, userDetails, - bindingResult); + FileBrowserFileRequest fileBrowserFileRequest = addProfileImageToFileSystem(request, userDetails, bindingResult); // update profile picture attribute or add a new one if no image was already set by user - this.setProfilePictureAttribute(userProfile, fileBrowserFileRequest.getFilename()); - // update user profile with the fresh data related to profile picture - userProfileManager.updateProfile(userProfile.getId(), userProfile); + if (getProfilePictureAttribute(userProfile).isPresent()) { + this.setProfilePictureAttribute(userProfile, fileBrowserFileRequest.getFilename()); + userProfileManager.updateProfile(userProfile.getId(), userProfile); + } return fileBrowserFileRequest.getFilename(); } catch (Exception e) { log.error("Error updating avatar", e); @@ -97,13 +88,16 @@ public String updateAvatar(ProfileAvatarRequest request, UserDetails userDetails @Override public void deleteAvatar(UserDetails userDetails, BindingResult bindingResult) { try { - IUserProfile userProfile = userProfileManager.getProfile(userDetails.getUsername()); + String username = userDetails.getUsername(); + IUserProfile userProfile = userProfileManager.getProfile(username); // remove previous image if present - this.deletePrevUserAvatarFromFileSystemIfPresent(userProfile); + this.deletePrevUserAvatarFromFileSystemIfPresent(username, userProfile); // update profile picture attribute (if present) with an empty value - this.setProfilePictureAttribute(userProfile, null); - // update user profile with the fresh data related to profile picture - userProfileManager.updateProfile(userProfile.getId(), userProfile); + if (getProfilePictureAttribute(userProfile).isPresent()) { + this.setProfilePictureAttribute(userProfile, null); + // update user profile with the fresh data related to profile picture + userProfileManager.updateProfile(userProfile.getId(), userProfile); + } } catch (Exception e) { log.error("Error deleting avatar", e); throw new RestServerError("Error deleting avatar", e); @@ -111,10 +105,9 @@ public void deleteAvatar(UserDetails userDetails, BindingResult bindingResult) { } //------------------------ 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 @@ -122,15 +115,39 @@ private FileBrowserFileRequest addProfileImageToFileSystem( return fileBrowserFileRequest; } - - private void deletePrevUserAvatarFromFileSystemIfPresent(IUserProfile userProfile) { + private void deletePrevUserAvatarFromFileSystemIfPresent(String username, IUserProfile userProfile) { + Consumer deleteFile = filename -> { + if (null == filename) { + return; + } + String profilePicturePath = Paths.get(DEFAULT_AVATAR_PATH, filename).toString(); + this.removePictureFromFilesystem(profilePicturePath); + }; this.getProfilePictureAttribute(userProfile) - .ifPresent(attribute -> { - String profilePicturePath = Paths.get(DEFAULT_AVATAR_PATH, (String) attribute.getValue()) - .toString(); - removePictureFromFilesystem(profilePicturePath); - } - ); + .ifPresentOrElse(attribute -> deleteFile.accept((String) attribute.getValue()), + () -> deleteFile.accept(this.getAvatarFilenameByUsername(username))); + } + + private String getAvatarFilename(UserDetails userDetails) { + try { + IUserProfile userProfile = userProfileManager.getProfile(userDetails.getUsername()); + return getProfilePictureAttribute(userProfile).map(pr -> (String)pr.getValue()).orElseGet(() -> + this.getAvatarFilenameByUsername(userDetails.getUsername()) + ); + } catch (Exception e) { + throw new EntRuntimeException("Error extracting avatar " + userDetails.getUsername(), e); + } + } + + private String getAvatarFilenameByUsername(String username) { + 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 void removePictureFromFilesystem(String profilePicturePath) throws EntRuntimeException { @@ -154,11 +171,11 @@ private static FileBrowserFileRequest convertToFileBrowserFileRequest(ProfileAva fileBrowserFileRequest.setBase64(request.getBase64()); return fileBrowserFileRequest; } - + private Optional getProfilePictureAttribute(IUserProfile userProfile) { return Optional.ofNullable(userProfile).map(up -> up.getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE)); } - + private void setProfilePictureAttribute(IUserProfile userProfile, String value) { getProfilePictureAttribute(userProfile).ifPresent(attribute -> { MonoTextAttribute textAtt = (MonoTextAttribute) attribute; 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 index 9fa2b5370f..3ee456808d 100644 --- 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 @@ -10,9 +10,11 @@ import com.agiletec.aps.system.SystemConstants; import com.agiletec.aps.system.common.entity.model.attribute.MonoTextAttribute; 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.userprofile.model.AvatarDto; import org.entando.entando.aps.system.services.userprofile.model.IUserProfile; import org.entando.entando.aps.system.services.userprofile.model.UserProfile; @@ -59,7 +61,7 @@ void shouldGetAvatarDataThrowResourceServerError() throws EntException { when(userProfileManager.getProfile(any())).thenThrow(EntException.class); assertThrows(RestServerError.class, () -> avatarService.getAvatarData(mock(UserDetails.class))); } - + @Test void shouldGetAvatarDataThrowResourceNotFoundExceptionIfNoImageIsPresent() throws EntException { when(userProfileManager.getProfile(any())).thenReturn(new UserProfile()); @@ -76,16 +78,13 @@ void shouldGetAvatarDataThrowResourceNotFoundExceptionIfImageInProfileAttributeI @Test void shouldUpdateAvatarWithNullProfile() throws EntException { when(userProfileManager.getProfile("username")).thenReturn(null); - IUserProfile prototype = this.buildValidUserProfile(null, null); - when(userProfileManager.getDefaultProfileType()).thenReturn(prototype); - when(fileBrowserService.exists(any())).thenReturn(true); UserDetails userDetails = mock(UserDetails.class); when(userDetails.getUsername()).thenReturn("username"); + when(fileBrowserService.browseFolder(Mockito.anyString(), Mockito.eq(false))).thenReturn(List.of()); avatarService.updateAvatar(mock(ProfileAvatarRequest.class), userDetails, mock(BindingResult.class)); - verify(fileBrowserService, Mockito.times(1)).deleteFile(any(), any()); - verify(prototype, Mockito.times(1)).setId("username"); - verify(userProfileManager, Mockito.times(1)).addProfile("username", prototype); + verify(fileBrowserService, Mockito.times(0)).deleteFile(any(), any()); + verify(userProfileManager, Mockito.times(0)).addProfile(Mockito.eq("username"), Mockito.any(IUserProfile.class)); } @Test @@ -102,8 +101,18 @@ void shouldUpdateAvatarDeletePreviousProfilePictureIfPresent() throws EntExcepti @Test void shouldUpdateAvatarAddProfilePictureFromTheRequest() throws EntException { when(userProfileManager.getProfile(any())).thenReturn(new UserProfile()); - avatarService.updateAvatar(mock(ProfileAvatarRequest.class), mock(UserDetails.class), - mock(BindingResult.class)); + + BasicFileAttributeViewDto dtoDirectory = new BasicFileAttributeViewDto(); + dtoDirectory.setDirectory(true); + dtoDirectory.setName("folder"); + BasicFileAttributeViewDto dto = new BasicFileAttributeViewDto(); + dto.setDirectory(false); + dto.setName("test_username.jpg"); + 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(fileBrowserService, Mockito.times(1)).addFile(any(), any()); } From 711cfbcf0f20c28050b279bc0f63f1f5533300e4 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Wed, 20 Dec 2023 17:19:29 +0100 Subject: [PATCH 25/82] Added tests in content dispender --- .../services/content/ContentManager.java | 1 - .../dispenser/TestContentDispenser.java | 78 ++++++++++++------- .../java/com/agiletec/aps/BaseTestCase.java | 10 ++- 3 files changed, 59 insertions(+), 30 deletions(-) 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..1dc297b1e7 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; 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/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() { From fcf383011aca30b16186427fc4c47a7351aec1a9 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Tue, 12 Dec 2023 22:32:21 +0100 Subject: [PATCH 26/82] ENG-5334: Fix Solr schema issue --- pom.xml | 2 +- solr-plugin/pom.xml | 2 + .../aps/system/solr/ISolrIndexStatus.java | 13 +++++ .../aps/system/solr/ISolrIndexerDAO.java | 13 +++++ .../system/solr/ISolrProxyTenantAware.java | 2 +- .../system/solr/ISolrResourcesAccessor.java | 13 +++++ .../aps/system/solr/ISolrSchemaDAO.java | 13 +++++ .../solr/SearchEngineManagerFactory.java | 19 ++++++- .../aps/system/solr/SolrFieldsChecker.java | 27 ++++++--- .../SolrHttpClientBuilderConfiguration.java | 15 ++++- .../aps/system/solr/SolrIndexStatus.java | 13 +++++ .../system/solr/SolrResourcesAccessor.java | 19 +++++-- .../jpsolr/aps/system/solr/SolrSchemaDAO.java | 57 +++++++++++++++++-- .../system/solr/SolrSearchEngineManager.java | 53 ++++++++++------- .../cache/ISolrSearchEngineCacheWrapper.java | 34 +++++++++++ .../cache/SolrSearchEngineCacheWrapper.java | 57 +++++++++++++++++++ .../plugins/jpsolr/aps/baseManagersConfig.xml | 26 ++++++--- .../aps/system/solr/SolrSchemaDaoTest.java | 4 ++ .../solr/SolrSearchEngineManagerTest.java | 18 +++--- 19 files changed, 340 insertions(+), 60 deletions(-) create mode 100644 solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/cache/ISolrSearchEngineCacheWrapper.java create mode 100644 solr-plugin/src/main/java/org/entando/entando/plugins/jpsolr/aps/system/solr/cache/SolrSearchEngineCacheWrapper.java diff --git a/pom.xml b/pom.xml index 87e703be0f..fc66e71643 100644 --- a/pom.xml +++ b/pom.xml @@ -193,7 +193,7 @@ 7.4.1 4.1.77.Final 1.5 - 8.11.1 + 9.4.0 1.5.5 diff --git a/solr-plugin/pom.xml b/solr-plugin/pom.xml index 768f2fd40c..83f5f6bf93 100644 --- a/solr-plugin/pom.xml +++ b/solr-plugin/pom.xml @@ -80,6 +80,8 @@ org.entando.entando.plugins entando-plugin-jacms ${project.version} + classes + provided org.entando.entando 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..4e31f1fe1c 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; @@ -124,7 +138,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 +147,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/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/SolrSearchEngineManagerTest.java b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSearchEngineManagerTest.java index 6b4d38d9d7..cd31ebfbe2 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,7 +3,6 @@ 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; @@ -11,12 +10,12 @@ import com.agiletec.plugins.jacms.aps.system.services.content.IContentManager; 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 +40,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,7 +63,7 @@ void setUp() throws Exception { solrSearchEngineManager.setSolrProxy(solrProxy); solrSearchEngineManager.setContentManager(contentManager); solrSearchEngineManager.setLangManager(langManager); - solrSearchEngineManager.setCacheInfoManager(cacheInfoManager); + solrSearchEngineManager.setCacheWrapper(cacheWrapper); } @AfterEach From 1f6f6a86947ae6a22997b17adefcb19baa41faee Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Thu, 14 Dec 2023 17:32:28 +0100 Subject: [PATCH 27/82] ENG-5334: Improvement of test coverage --- .../aps/system/solr/SolrFieldsChecker.java | 4 +- .../system/solr/SolrFieldsCheckerTest.java | 180 ++++++++++++++++++ .../solr/SolrSearchEngineManagerTest.java | 14 ++ 3 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrFieldsCheckerTest.java 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 4e31f1fe1c..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 @@ -103,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) { 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/SolrSearchEngineManagerTest.java b/solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/SolrSearchEngineManagerTest.java index cd31ebfbe2..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 @@ -8,6 +8,7 @@ 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.Http2SolrClient; @@ -70,6 +71,19 @@ void setUp() throws Exception { 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 { From e56b45da2fb3e1b0a5be99702feb93c05670ec7e Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Fri, 22 Dec 2023 15:40:27 +0100 Subject: [PATCH 28/82] ENG-5405: Upgrade fields format --- .../system/services/userprofile/AvatarService.java | 2 +- .../userprofile/model/ProfileAvatarRequest.java | 2 +- .../userprofile/model/ProfileAvatarResponse.java | 3 ++- .../validator/ProfileAvatarValidator.java | 11 ++++------- .../services/userprofile/AvatarServiceTest.java | 6 +++--- .../web/userprofile/UserProfileControllerTest.java | 14 +++++++------- .../validator/ProfileAvatarValidatorTest.java | 13 +++++++++++++ 7 files changed, 31 insertions(+), 20 deletions(-) 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 index 3dbca49837..bda1d64c13 100644 --- 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 @@ -163,7 +163,7 @@ private void removePictureFromFilesystem(String profilePicturePath) throws EntRu private static FileBrowserFileRequest convertToFileBrowserFileRequest(ProfileAvatarRequest request, UserDetails userDetails) { FileBrowserFileRequest fileBrowserFileRequest = new FileBrowserFileRequest(); - String imageExtension = FilenameUtils.getExtension(request.getFileName()); + 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()); 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 index b3a5afb28a..c13caabfd9 100644 --- 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 @@ -30,7 +30,7 @@ public class ProfileAvatarRequest { @NotBlank(message = "avatar.filename.notBlank") - private String fileName; + private String filename; @NotEmpty(message = "fileBrowser.base64.notBlank") private byte[] base64; } diff --git a/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarResponse.java b/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarResponse.java index 303213db22..06b7368a48 100644 --- a/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarResponse.java +++ b/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarResponse.java @@ -27,5 +27,6 @@ @Setter public class ProfileAvatarResponse { - private String fileName; + private String filename; + } 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 index 491e7ff146..f3a6fa9f1f 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2018-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 @@ -25,9 +25,6 @@ import org.springframework.validation.Errors; import org.springframework.validation.Validator; -/** - * @author eu - */ @Component public class ProfileAvatarValidator implements Validator { @@ -43,9 +40,9 @@ public boolean supports(@NonNull Class paramClass) { public void validate(@NonNull Object target, @NonNull Errors errors) { ProfileAvatarRequest request = (ProfileAvatarRequest) target; - String fileName = request.getFileName(); - if (StringUtils.isEmpty(FilenameUtils.getExtension(fileName))) { - errors.rejectValue("fileName", ERRCODE_INVALID_FILE_NAME, new String[]{fileName}, + String filename = request.getFilename(); + if (StringUtils.isEmpty(FilenameUtils.getExtension(filename))) { + errors.rejectValue("filename", ERRCODE_INVALID_FILE_NAME, new String[]{filename}, "fileBrowser.filename.invalidFilename"); return; } 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 index 3ee456808d..281a97cc47 100644 --- 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 @@ -123,7 +123,7 @@ void shouldUpdateAvatarUserProfileAndRenameProfilePictureWithUserName() throws E when(userProfileManager.getProfile(any())).thenReturn(profile); // set POST request DTO ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest(); - profileAvatarRequest.setFileName("image.png"); + profileAvatarRequest.setFilename("image.png"); profileAvatarRequest.setBase64(new byte[0]); // set user details to return desired username UserDetails userDetails = mock(UserDetails.class); @@ -144,7 +144,7 @@ void shouldUpdateAvatarUserProfileAndSetRenamedProfilePictureIfNoPreviousPicture when(userProfileManager.getProfile(any())).thenReturn(profile); // set POST request DTO ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest(); - profileAvatarRequest.setFileName("image.png"); + profileAvatarRequest.setFilename("image.png"); profileAvatarRequest.setBase64(new byte[0]); // set user details to return desired username UserDetails userDetails = mock(UserDetails.class); @@ -229,7 +229,7 @@ void shouldUpdateAvatarThrowExceptionIfProfilePictureCheckImageGoesInError() thr // set POST request DTO ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest(); - profileAvatarRequest.setFileName("image.png"); + profileAvatarRequest.setFilename("image.png"); profileAvatarRequest.setBase64(new byte[0]); //pretend fileBrowserService.exists goes in error 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 f08d390f27..156c8abcc0 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 @@ -190,7 +190,7 @@ void shouldPostAvatarReturn400OnIllegalInput() throws Exception { Answer ans = invocation -> { Object[] args = invocation.getArguments(); - ((BindingResult) args[1]).rejectValue("fileName", "1", new String[]{"fileName_without_extension"}, + ((BindingResult) args[1]).rejectValue("filename", "1", new String[]{"fileName_without_extension"}, "fileBrowser.filename.invalidFilename"); return null; }; @@ -218,7 +218,7 @@ void shouldPostAvatarReturn200OnRightInput() throws Exception { .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer " + accessToken)); result.andExpect(status().isOk()) - .andExpect(jsonPath("$.payload.fileName").value("jack_bauer.png")); + .andExpect(jsonPath("$.payload.filename").value("jack_bauer.png")); } @Test @@ -272,12 +272,12 @@ void shouldDeleteAvatarReturn200() throws Exception { private static Stream provideValuesFor400() { return Stream.of( - Arguments.of("{\"fileNam\":\"image.png\",\"base64\":\"AA==\"}", "NotBlank"), + Arguments.of("{\"filenam\":\"image.png\",\"base64\":\"AA==\"}", "NotBlank"), Arguments.of("{\"base64\":\"AA==\"}", "NotBlank"), - Arguments.of("{\"fileName\":\"\",\"base64\":\"AA==\"}", "NotBlank"), - Arguments.of("{\"fileName\":\"image.png\",\"base6\":\"AA==\"}", "NotEmpty"), - Arguments.of("{\"fileName\":\"image.png\"}", "NotEmpty"), - Arguments.of("{\"fileName\":\"image.png\",\"base64\":\"\"}", "NotEmpty") + Arguments.of("{\"filename\":\"\",\"base64\":\"AA==\"}", "NotBlank"), + Arguments.of("{\"filename\":\"image.png\",\"base6\":\"AA==\"}", "NotEmpty"), + Arguments.of("{\"filename\":\"image.png\"}", "NotEmpty"), + Arguments.of("{\"filename\":\"image.png\",\"base64\":\"\"}", "NotEmpty") ); } 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 index 3453b652d4..1a4b382f0e 100644 --- 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 @@ -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.web.userprofile.validator; import static org.junit.jupiter.api.Assertions.assertEquals; From 4146b8b5dd2835f5d2a952973a4959f13f624f32 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Tue, 9 Jan 2024 09:54:18 +0100 Subject: [PATCH 29/82] ENG-5414: Added missing metadata into resource attributes in content API response --- .../services/content/model/ContentDto.java | 5 +- .../services/content/ContentService.java | 6 +- .../ContentControllerIntegrationTest.java | 67 +++++++++++++++++-- 3 files changed, 66 insertions(+), 12 deletions(-) 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..3ff7e66a12 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 @@ -314,9 +314,8 @@ private void setResourceAttribute(AbstractResourceAttribute resourceAttribute, M resourceAttribute.setResource(resourceInterface, langCode); 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())); } } 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..9f7572c358 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 @@ -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))); } 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..97563cbf21 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 @@ -1103,19 +1103,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 +1139,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()); + } } } @@ -5029,4 +5041,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); + } + } + } From 31aeae33049f879a81c0ec1e99d8e75748b14601 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Tue, 9 Jan 2024 11:49:01 +0100 Subject: [PATCH 30/82] ENG-5414: Fix wrong tests for link attribute --- .../services/content/model/ContentDto.java | 14 +++++++------- .../content/1_POST_valid_with_some_links.json | 2 +- .../jacms/web/content/1_PUT_valid_with_link.json | 2 +- .../ContentControllerIntegrationTest.java | 16 +++++++++------- 4 files changed, 18 insertions(+), 16 deletions(-) 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 3ff7e66a12..6aabf6a215 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 @@ -321,17 +321,17 @@ private void setResourceAttribute(AbstractResourceAttribute resourceAttribute, M 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; } @@ -340,7 +340,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/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 97563cbf21..4800881b59 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))) From 435aa2d54a85674f0990725ec7737a3d9ed08a24 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Thu, 11 Jan 2024 11:00:26 +0100 Subject: [PATCH 31/82] Readme upgrade --- cms-plugin/README.md | 13 ------------- seo-plugin/README.md | 13 ------------- versioning-plugin/README.md | 12 ------------ 3 files changed, 38 deletions(-) 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/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/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. From 0536eb2ba650006950a1258d6c2cfc3f37352bcb Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Mon, 15 Jan 2024 09:10:08 +0100 Subject: [PATCH 32/82] ENG-5114: Improvement of Avatar delete API response body --- .../entando/web/userprofile/ProfileController.java | 11 +++++------ .../web/userprofile/UserProfileControllerTest.java | 7 +++++-- 2 files changed, 10 insertions(+), 8 deletions(-) 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 39761c6941..c201cdad57 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 @@ -227,12 +227,11 @@ public ResponseEntity> addAvatar( } @DeleteMapping(path = "/userProfiles/avatar") - public ResponseEntity deleteAvatar(@RequestAttribute("user") UserDetails user) { - - // delete the profile picture associated with the received user profile. If no Image is found, the method does - // nothing and returns ok anyway + public ResponseEntity deleteAvatar(@RequestAttribute("user") UserDetails user) { avatarService.deleteAvatar(user, new MapBindingResult(new HashMap<>(), "user")); - - return ResponseEntity.ok().build(); + Map payload = new HashMap<>(); + payload.put("username", user.getUsername()); + return new ResponseEntity<>(new SimpleRestResponse<>(payload), HttpStatus.OK); } + } 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 156c8abcc0..3f38250d51 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 @@ -40,6 +40,7 @@ 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; @@ -262,11 +263,13 @@ void shouldPostAvatarReturn400(String request, String expectedErrorCode) throws @Test void shouldDeleteAvatarReturn200() throws Exception { String accessToken = this.createAccessToken(); - ResultActions result = mockMvc.perform( delete("/userProfiles/avatar") .header("Authorization", "Bearer " + accessToken)); - result.andExpect(status().isOk()); + 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))); } From 12a27e6ed74f44e30edc93a7ba822252ed702825 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Mon, 15 Jan 2024 09:58:08 +0100 Subject: [PATCH 33/82] ENG-5114: Workaround for sonar analysis on deprecated Java version --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index fc66e71643..ca7f4382ae 100644 --- a/pom.xml +++ b/pom.xml @@ -71,6 +71,7 @@ **/*.js + true org.apache.derby.jdbc.EmbeddedDriver localhost From 34ef7fcfbb86b5b9671326bc1d9b421426f7ec5d Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Mon, 15 Jan 2024 16:46:15 +0100 Subject: [PATCH 34/82] ENG-5423: First step of integration of user preferences for user avatar --- .../agiletec/aps/system/SystemConstants.java | 5 - .../userpreferences/IUserPreferencesDAO.java | 1 + .../IUserPreferencesManager.java | 18 +- .../userpreferences/UserPreferences.java | 15 +- .../userpreferences/UserPreferencesDAO.java | 14 +- .../UserPreferencesManager.java | 22 +- .../UserPreferencesService.java | 11 +- .../services/userprofile/AvatarService.java | 108 ++---- .../services/userprofile/attributeRoles.xml | 6 - .../services/userprofile/model/AvatarDto.java | 14 + .../model/UserPreferencesDto.java | 1 + .../model/UserPreferencesRequest.java | 6 +- .../web/userprofile/ProfileController.java | 20 +- .../model/ProfileAvatarRequest.java | 11 +- .../model/ProfileAvatarResponse.java | 32 -- .../validator/ProfileAvatarValidator.java | 24 +- .../resources/liquibase/changeSetPort.xml | 7 +- .../20240112000000_user_preference_avatar.xml | 16 + .../port/clob/production/sysconfig_3.xml | 39 +- .../liquibase/port/clob/test/sysconfig_3.xml | 351 ++++++++++-------- .../userprofile/AvatarServiceTest.java | 242 ++++-------- .../UserProfileManagerIntegrationTest.java | 2 +- ...rPreferencesControllerIntegrationTest.java | 3 +- .../web/userprofile/12_POST_valid.json | 23 -- .../entando/web/userprofile/12_PUT_valid.json | 17 - .../ProfileTypeControllerIntegrationTest.java | 4 +- .../UserProfileControllerIntegrationTest.java | 37 -- .../UserProfileControllerTest.java | 38 +- .../validator/ProfileAvatarValidatorTest.java | 8 +- pom.xml | 4 +- 30 files changed, 470 insertions(+), 629 deletions(-) delete mode 100644 engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarResponse.java create mode 100644 engine/src/main/resources/liquibase/port/20240112000000_user_preference_avatar.xml delete mode 100644 engine/src/test/java/org/entando/entando/web/userprofile/12_POST_valid.json delete mode 100644 engine/src/test/java/org/entando/entando/web/userprofile/12_PUT_valid.json 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 93676ee290..5d070030ba 100644 --- a/engine/src/main/java/com/agiletec/aps/system/SystemConstants.java +++ b/engine/src/main/java/com/agiletec/aps/system/SystemConstants.java @@ -296,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/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..fa439aae90 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,6 +13,7 @@ */ package org.entando.entando.aps.system.services.userpreferences; +import java.util.Optional; import org.entando.entando.ent.exception.EntException; public class UserPreferencesManager implements IUserPreferencesManager { @@ -34,6 +35,24 @@ public void updateUserPreferences(UserPreferences userPreferences) throws EntExc userPreferencesDAO.updateUserPreferences(userPreferences); } + @Override + public boolean isUserGravatarEnabled(String username) throws EntException { + return Optional.ofNullable(this.getUserPreferences(username)).map(p -> p.isGravatar()).orElse(Boolean.FALSE); + } + + @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); @@ -42,4 +61,5 @@ public void deleteUserPreferences(String username) throws EntException { public void setUserPreferencesDAO(UserPreferencesDAO userPreferencesDAO) { this.userPreferencesDAO = userPreferencesDAO; } + } 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 index bda1d64c13..3c36090dd6 100644 --- 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 @@ -1,13 +1,22 @@ +/* + * 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.SystemConstants; -import com.agiletec.aps.system.common.entity.model.attribute.AttributeInterface; -import com.agiletec.aps.system.common.entity.model.attribute.MonoTextAttribute; import com.agiletec.aps.system.services.user.UserDetails; import java.nio.file.Paths; import java.util.List; import java.util.Optional; -import java.util.function.Consumer; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FilenameUtils; @@ -16,10 +25,9 @@ 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.aps.system.services.userprofile.model.IUserProfile; -import org.entando.entando.ent.exception.EntException; -import org.entando.entando.ent.exception.EntRuntimeException; +import org.entando.entando.aps.system.services.userprofile.model.AvatarDto.AvatarDtoBuilder; import org.entando.entando.web.entity.validator.EntityValidator; import org.entando.entando.web.filebrowser.model.FileBrowserFileRequest; import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; @@ -28,10 +36,12 @@ @Slf4j @RequiredArgsConstructor public class AvatarService implements IAvatarService { - - // Services + + private static final String GRAVATAR_AVATAR_OPTION = "GRAVATAR"; + private static final String LOCAL_AVATAR_OPTION = "LOCAL_FILE"; + private final IFileBrowserService fileBrowserService; - private final IUserProfileManager userProfileManager; + private final IUserPreferencesManager userPreferencesManager; // CONSTANTS private static final String DEFAULT_AVATAR_PATH = "static/profile"; @@ -39,7 +49,11 @@ public class AvatarService implements IAvatarService { @Override public AvatarDto getAvatarData(UserDetails userDetails) { try { - String fileName = this.getAvatarFilename(userDetails); + 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()); @@ -68,16 +82,13 @@ public AvatarDto getAvatarData(UserDetails userDetails) { public String updateAvatar(ProfileAvatarRequest request, UserDetails userDetails, BindingResult bindingResult) { try { String username = userDetails.getUsername(); - IUserProfile userProfile = userProfileManager.getProfile(username); // remove previous image if present - deletePrevUserAvatarFromFileSystemIfPresent(username, userProfile); - // add profile picture file - FileBrowserFileRequest fileBrowserFileRequest = addProfileImageToFileSystem(request, userDetails, bindingResult); - // update profile picture attribute or add a new one if no image was already set by user - if (getProfilePictureAttribute(userProfile).isPresent()) { - this.setProfilePictureAttribute(userProfile, fileBrowserFileRequest.getFilename()); - userProfileManager.updateProfile(userProfile.getId(), userProfile); + 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); @@ -89,15 +100,8 @@ public String updateAvatar(ProfileAvatarRequest request, UserDetails userDetails public void deleteAvatar(UserDetails userDetails, BindingResult bindingResult) { try { String username = userDetails.getUsername(); - IUserProfile userProfile = userProfileManager.getProfile(username); - // remove previous image if present - this.deletePrevUserAvatarFromFileSystemIfPresent(username, userProfile); - // update profile picture attribute (if present) with an empty value - if (getProfilePictureAttribute(userProfile).isPresent()) { - this.setProfilePictureAttribute(userProfile, null); - // update user profile with the fresh data related to profile picture - userProfileManager.updateProfile(userProfile.getId(), userProfile); - } + 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); @@ -115,28 +119,13 @@ private FileBrowserFileRequest addProfileImageToFileSystem( return fileBrowserFileRequest; } - private void deletePrevUserAvatarFromFileSystemIfPresent(String username, IUserProfile userProfile) { - Consumer deleteFile = filename -> { - if (null == filename) { - return; - } - String profilePicturePath = Paths.get(DEFAULT_AVATAR_PATH, filename).toString(); - this.removePictureFromFilesystem(profilePicturePath); - }; - this.getProfilePictureAttribute(userProfile) - .ifPresentOrElse(attribute -> deleteFile.accept((String) attribute.getValue()), - () -> deleteFile.accept(this.getAvatarFilenameByUsername(username))); - } - - private String getAvatarFilename(UserDetails userDetails) { - try { - IUserProfile userProfile = userProfileManager.getProfile(userDetails.getUsername()); - return getProfilePictureAttribute(userProfile).map(pr -> (String)pr.getValue()).orElseGet(() -> - this.getAvatarFilenameByUsername(userDetails.getUsername()) - ); - } catch (Exception e) { - throw new EntRuntimeException("Error extracting avatar " + userDetails.getUsername(), e); + private void deletePrevUserAvatarFromFileSystemIfPresent(String username) { + 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) { @@ -150,16 +139,6 @@ private String getAvatarFilenameByUsername(String username) { return fileAvatar.orElse(null); } - private void removePictureFromFilesystem(String profilePicturePath) throws EntRuntimeException { - try { - if (fileBrowserService.exists(profilePicturePath)) { - fileBrowserService.deleteFile(profilePicturePath, false); - } - } catch (EntException e) { - throw new EntRuntimeException("Error in checking file existence on the filesystem", e); - } - } - private static FileBrowserFileRequest convertToFileBrowserFileRequest(ProfileAvatarRequest request, UserDetails userDetails) { FileBrowserFileRequest fileBrowserFileRequest = new FileBrowserFileRequest(); @@ -172,15 +151,4 @@ private static FileBrowserFileRequest convertToFileBrowserFileRequest(ProfileAva return fileBrowserFileRequest; } - private Optional getProfilePictureAttribute(IUserProfile userProfile) { - return Optional.ofNullable(userProfile).map(up -> up.getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE)); - } - - private void setProfilePictureAttribute(IUserProfile userProfile, String value) { - getProfilePictureAttribute(userProfile).ifPresent(attribute -> { - MonoTextAttribute textAtt = (MonoTextAttribute) attribute; - textAtt.setText(value); - }); - } - } 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 index 312359ed21..148c60e131 100644 --- 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 @@ -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.aps.system.services.userprofile.model; import lombok.AllArgsConstructor; @@ -16,5 +29,6 @@ public class AvatarDto { String filename; byte[] base64; String prevPath; + boolean gravatar; } 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 39761c6941..f5ef060523 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 @@ -37,7 +37,6 @@ 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.model.ProfileAvatarResponse; import org.entando.entando.web.userprofile.validator.ProfileAvatarValidator; import org.entando.entando.web.userprofile.validator.ProfileValidator; import org.springframework.http.HttpStatus; @@ -45,6 +44,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; 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; @@ -173,7 +173,7 @@ 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); profileValidator.validateBodyName(user.getUsername(), bodyRequest, bindingResult); if (bindingResult.hasErrors()) { @@ -185,7 +185,7 @@ public ResponseEntity> updateMyUserProfile(@Reques } 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) { @@ -197,33 +197,28 @@ public ResponseEntity, Map>> ge 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( + public ResponseEntity>> addAvatar( @Valid @RequestBody ProfileAvatarRequest request, @RequestAttribute("user") UserDetails user, BindingResult bindingResult) { - // validate input dto to check for consistency of input profileAvatarValidator.validate(request, bindingResult); if (bindingResult.hasErrors()) { throw new ValidationGenericException(bindingResult); } - // update the profile picture saving the received image in the file system, eventually deleting the previous - // existing image String pictureFileName = avatarService.updateAvatar(request, user, bindingResult); - if (bindingResult.hasErrors()) { throw new ValidationGenericException(bindingResult); } - return new ResponseEntity<>(new SimpleRestResponse<>(new ProfileAvatarResponse(pictureFileName)), - HttpStatus.OK); + return new ResponseEntity<>(new SimpleRestResponse<>(Map.of("filename", pictureFileName)), HttpStatus.OK); } @DeleteMapping(path = "/userProfiles/avatar") @@ -233,6 +228,9 @@ public ResponseEntity deleteAvatar(@RequestAttribute("user") UserDetails u // nothing and returns ok anyway avatarService.deleteAvatar(user, new MapBindingResult(new HashMap<>(), "user")); + // restituire lo usernane nel corpo + return ResponseEntity.ok().build(); } + } 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 index c13caabfd9..6487c041ea 100644 --- 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 @@ -13,24 +13,19 @@ */ package org.entando.entando.web.userprofile.model; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotEmpty; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -/** - * @author E.Santoboni - */ @NoArgsConstructor @AllArgsConstructor @Getter @Setter public class ProfileAvatarRequest { - - @NotBlank(message = "avatar.filename.notBlank") + private String filename; - @NotEmpty(message = "fileBrowser.base64.notBlank") private byte[] base64; + private boolean useGravatar; + } diff --git a/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarResponse.java b/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarResponse.java deleted file mode 100644 index 06b7368a48..0000000000 --- a/engine/src/main/java/org/entando/entando/web/userprofile/model/ProfileAvatarResponse.java +++ /dev/null @@ -1,32 +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.web.userprofile.model; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -/** - * @author E.Santoboni - */ -@NoArgsConstructor -@AllArgsConstructor -@Getter -@Setter -public class ProfileAvatarResponse { - - private String filename; - -} 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 index f3a6fa9f1f..92c1d2d964 100644 --- 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 @@ -19,6 +19,7 @@ import javax.imageio.ImageIO; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; +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; @@ -39,20 +40,27 @@ public boolean supports(@NonNull Class paramClass) { @Override public void validate(@NonNull Object target, @NonNull Errors errors) { ProfileAvatarRequest request = (ProfileAvatarRequest) target; - String filename = request.getFilename(); - if (StringUtils.isEmpty(FilenameUtils.getExtension(filename))) { + 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; } - - try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(request.getBase64())) { - if (ImageIO.read(byteArrayInputStream) == null) { - errors.rejectValue("base64", ERRCODE_INVALID_FILE_TYPE, "fileBrowser.file.invalidType"); + 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); } - } catch (IOException e) { - throw new UncheckedIOException(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/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 index 281a97cc47..36c1038e98 100644 --- 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 @@ -1,3 +1,16 @@ +/* + * 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; @@ -7,23 +20,20 @@ import static org.mockito.Mockito.verify; 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.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.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.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.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; @@ -31,222 +41,108 @@ @ExtendWith(MockitoExtension.class) class AvatarServiceTest { - - @Mock - private IUserProfileManager userProfileManager; + @Mock private IFileBrowserService fileBrowserService; + @Mock + private IUserPreferencesManager userPreferencesManager; IAvatarService avatarService; @BeforeEach void init() { - avatarService = new AvatarService(fileBrowserService, userProfileManager); + avatarService = new AvatarService(fileBrowserService, userPreferencesManager); } @Test void shouldGetAvatarDataReturnAvatarInfo() throws EntException { - IUserProfile profile = this.buildValidUserProfile("username", "image.png"); - when(userProfileManager.getProfile(any())).thenReturn(profile); + 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.browseFolder("static/profile", false)).thenReturn(List.of(dto)); when(fileBrowserService.getFileStream(any(), any())).thenReturn(new byte[0]); - - AvatarDto avatarData = avatarService.getAvatarData(mock(UserDetails.class)); - - assertEquals("image.png", avatarData.getFilename()); - assertEquals("static/profile/image.png", avatarData.getCurrentPath()); - } - - @Test - void shouldGetAvatarDataThrowResourceServerError() throws EntException { - when(userProfileManager.getProfile(any())).thenThrow(EntException.class); - assertThrows(RestServerError.class, () -> avatarService.getAvatarData(mock(UserDetails.class))); + 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 shouldGetAvatarDataThrowResourceNotFoundExceptionIfNoImageIsPresent() throws EntException { - when(userProfileManager.getProfile(any())).thenReturn(new UserProfile()); - assertThrows(ResourceNotFoundException.class, () -> avatarService.getAvatarData(mock(UserDetails.class))); + 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 shouldGetAvatarDataThrowResourceNotFoundExceptionIfImageInProfileAttributeIsEmpty() throws EntException { - IUserProfile profile = this.buildValidUserProfile("username", ""); - when(userProfileManager.getProfile(any())).thenReturn(profile); + void shouldGetAvatarDataThrowResourceNotFoundExceptionIfNoImageIsPresent() throws EntException { assertThrows(ResourceNotFoundException.class, () -> avatarService.getAvatarData(mock(UserDetails.class))); } - - @Test - void shouldUpdateAvatarWithNullProfile() throws EntException { - when(userProfileManager.getProfile("username")).thenReturn(null); - UserDetails userDetails = mock(UserDetails.class); - when(userDetails.getUsername()).thenReturn("username"); - when(fileBrowserService.browseFolder(Mockito.anyString(), Mockito.eq(false))).thenReturn(List.of()); - avatarService.updateAvatar(mock(ProfileAvatarRequest.class), userDetails, - mock(BindingResult.class)); - verify(fileBrowserService, Mockito.times(0)).deleteFile(any(), any()); - verify(userProfileManager, Mockito.times(0)).addProfile(Mockito.eq("username"), Mockito.any(IUserProfile.class)); - } - - @Test - void shouldUpdateAvatarDeletePreviousProfilePictureIfPresent() throws EntException { - IUserProfile profile = this.buildValidUserProfile("username", "prevImage.png"); - when(userProfileManager.getProfile(any())).thenReturn(profile); - //pretend image exists on filesystem - when(fileBrowserService.exists(any())).thenReturn(true); - avatarService.updateAvatar(mock(ProfileAvatarRequest.class), mock(UserDetails.class), - mock(BindingResult.class)); - verify(fileBrowserService, Mockito.times(1)).deleteFile(any(), any()); - } - + @Test void shouldUpdateAvatarAddProfilePictureFromTheRequest() throws EntException { - when(userProfileManager.getProfile(any())).thenReturn(new UserProfile()); - BasicFileAttributeViewDto dtoDirectory = new BasicFileAttributeViewDto(); dtoDirectory.setDirectory(true); dtoDirectory.setName("folder"); - BasicFileAttributeViewDto dto = new BasicFileAttributeViewDto(); - dto.setDirectory(false); - dto.setName("test_username.jpg"); + BasicFileAttributeViewDto dto = this.createMockFileAttributeDto("test_username", "jpg"); 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)); + 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 shouldUpdateAvatarUserProfileAndRenameProfilePictureWithUserName() throws EntException { - // set previous profile picture - IUserProfile profile = this.buildValidUserProfile("user1", "prevImage.png"); - when(userProfileManager.getProfile(any())).thenReturn(profile); - // set POST request DTO - ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest(); - profileAvatarRequest.setFilename("image.png"); - profileAvatarRequest.setBase64(new byte[0]); - // set user details to return desired username - UserDetails userDetails = mock(UserDetails.class); - when(userDetails.getUsername()).thenReturn("user1"); - - avatarService.updateAvatar(profileAvatarRequest, userDetails, mock(BindingResult.class)); - - ArgumentCaptor captorProfile = ArgumentCaptor.forClass(IUserProfile.class); - verify(userProfileManager, Mockito.times(1)).updateProfile(Mockito.eq("user1"), captorProfile.capture()); - assertEquals("user1.png", captorProfile.getValue() - .getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE).getValue()); - } - - @Test - void shouldUpdateAvatarUserProfileAndSetRenamedProfilePictureIfNoPreviousPictureWasPresent() throws EntException { - // set previous profile picture - IUserProfile profile = this.buildValidUserProfile("user1", null); - when(userProfileManager.getProfile(any())).thenReturn(profile); - // set POST request DTO - ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest(); - profileAvatarRequest.setFilename("image.png"); - profileAvatarRequest.setBase64(new byte[0]); - // set user details to return desired username - UserDetails userDetails = mock(UserDetails.class); - when(userDetails.getUsername()).thenReturn("user1"); - - avatarService.updateAvatar(profileAvatarRequest, userDetails, mock(BindingResult.class)); - - ArgumentCaptor captorProfile = ArgumentCaptor.forClass(IUserProfile.class); - verify(userProfileManager, Mockito.times(1)).updateProfile(Mockito.eq("user1"), captorProfile.capture()); - assertEquals("user1.png", captorProfile.getValue() - .getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE).getValue()); - } - + @Test - void shouldDeleteAvatarFromFilesystemAndResetUserProfilePictureAttribute() throws EntException { - // set previous profile picture - IUserProfile profile = this.buildValidUserProfile("user1", "user1.png"); - when(userProfileManager.getProfile(any())).thenReturn(profile); - - // set user details to return desired username + void shouldUpdateAvatarWithGravatar() throws EntException { + ProfileAvatarRequest request = new ProfileAvatarRequest(null, null, true); UserDetails userDetails = mock(UserDetails.class); - when(userDetails.getUsername()).thenReturn("user1"); - - //pretend image exists on filesystem - when(fileBrowserService.exists(any())).thenReturn(true); - - avatarService.deleteAvatar(userDetails, mock(BindingResult.class)); - - ArgumentCaptor captorProfile = ArgumentCaptor.forClass(IUserProfile.class); - verify(userProfileManager, Mockito.times(1)).updateProfile(Mockito.eq("user1"), captorProfile.capture()); - assertEquals("", captorProfile.getValue() - .getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE).getValue()); + when(userDetails.getUsername()).thenReturn("test_username_gravatar"); + BasicFileAttributeViewDto dto = this.createMockFileAttributeDto("test_username_gravatar", "jpg"); + when(fileBrowserService.browseFolder(Mockito.anyString(), Mockito.eq(false))).thenReturn(List.of(dto)); + avatarService.updateAvatar(request, userDetails, mock(BindingResult.class)); + verify(userPreferencesManager, Mockito.times(1)).updateUserGravatarPreference("test_username_gravatar", true); verify(fileBrowserService, Mockito.times(1)).deleteFile(any(), any()); + verify(fileBrowserService, Mockito.times(0)).addFile(any(), any()); } - - + @Test - void shouldDeleteAvatarDoNothingAndRunSmoothlyIfUserImageIsNotSetInTheProfile() throws EntException { - // set previous profile picture - IUserProfile profile = this.buildValidUserProfile("user1", ""); - when(userProfileManager.getProfile(any())).thenReturn(profile); - + void shouldDeleteAvatar() throws EntException { // set user details to return desired username UserDetails userDetails = mock(UserDetails.class); when(userDetails.getUsername()).thenReturn("user1"); - + BasicFileAttributeViewDto dto = this.createMockFileAttributeDto("user1", "png"); + when(fileBrowserService.browseFolder(Mockito.anyString(), Mockito.eq(false))).thenReturn(List.of(dto)); avatarService.deleteAvatar(userDetails, mock(BindingResult.class)); - - ArgumentCaptor captorProfile = ArgumentCaptor.forClass(IUserProfile.class); - verify(userProfileManager, Mockito.times(1)).updateProfile(Mockito.eq("user1"), captorProfile.capture()); - assertEquals("", captorProfile.getValue() - .getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE).getValue()); - verify(fileBrowserService, Mockito.times(0)).deleteFile(any(), any()); + verify(fileBrowserService, Mockito.times(1)).deleteFile(any(), any()); + verify(userPreferencesManager, Mockito.times(1)).updateUserGravatarPreference("user1", false); } - + @Test - void shouldDeleteAvatarThrowExceptionIfProfilePictureCheckImageGoesInError() throws EntException { - // set previous profile picture - IUserProfile profile = this.buildValidUserProfile("user1", "user1.png"); - when(userProfileManager.getProfile(any())).thenReturn(profile); - + void shouldDeleteAvatarThrowException() throws EntException { // set user details to return desired username UserDetails userDetails = mock(UserDetails.class); when(userDetails.getUsername()).thenReturn("user1"); - - //pretend fileBrowserService.exists goes in error - when(fileBrowserService.exists(any())).thenThrow(EntException.class); - + BasicFileAttributeViewDto dto = this.createMockFileAttributeDto("user1", "jpg"); + 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))); } - - @Test - void shouldUpdateAvatarThrowExceptionIfProfilePictureCheckImageGoesInError() throws EntException { - // set previous profile picture - IUserProfile profile = this.buildValidUserProfile("user1", "user1.png"); - when(userProfileManager.getProfile(any())).thenReturn(profile); - - // set user details to return desired username - UserDetails userDetails = mock(UserDetails.class); - when(userDetails.getUsername()).thenReturn("user1"); - - // set POST request DTO - ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest(); - profileAvatarRequest.setFilename("image.png"); - profileAvatarRequest.setBase64(new byte[0]); - - //pretend fileBrowserService.exists goes in error - when(fileBrowserService.exists(any())).thenThrow(EntException.class); - - assertThrows(RestServerError.class, - () -> avatarService.updateAvatar(profileAvatarRequest, userDetails, mock(BindingResult.class))); - } - private IUserProfile buildValidUserProfile(String username, String attributeValue) { - IUserProfile profile = Mockito.mock(IUserProfile.class); - Mockito.lenient().when(profile.getId()).thenReturn(username); - MonoTextAttribute attribute = new MonoTextAttribute(); - attribute.setName("profilepicture"); - attribute.setText(attributeValue); - when(profile.getAttributeByRole(SystemConstants.USER_PROFILE_ATTRIBUTE_ROLE_PROFILE_PICTURE)).thenReturn(attribute); - return profile; + 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/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/web/userpreferences/UserPreferencesControllerIntegrationTest.java b/engine/src/test/java/org/entando/entando/web/userpreferences/UserPreferencesControllerIntegrationTest.java index 965434a258..f7f396d151 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 @@ -33,7 +33,6 @@ 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 { 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..523736a1e5 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; @@ -495,42 +494,6 @@ void testPostMyProfileOk() throws Exception { } } - @Test - void testAddUserProfileWithProfilePicture() throws Exception { - 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"))); - } finally { - this.userProfileManager.deleteProfile("new_user_2"); - Assertions.assertNull(this.userProfileManager.getProfile("new_user_2")); - } - } - private String createAccessToken() throws Exception { UserDetails user = new OAuth2TestUtils.UserBuilder("jack_bauer", "0x24") .withAuthorization(Group.FREE_GROUP_NAME, "manageUserProfile", Permission.MANAGE_USER_PROFILES) 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 156c8abcc0..fa5da309c9 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 @@ -28,6 +28,7 @@ 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; @@ -48,8 +49,10 @@ import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.Spy; 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; @@ -70,7 +73,7 @@ class UserProfileControllerTest extends AbstractControllerTest { @Mock private IUserProfileManager userProfileManager; - @Mock + @Spy private ProfileAvatarValidator profileAvatarValidator; @Mock @@ -196,7 +199,7 @@ void shouldPostAvatarReturn400OnIllegalInput() throws Exception { }; doAnswer(ans).when(profileAvatarValidator).validate(any(), any()); ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("fileName_without_extension", - new byte[1]); + new byte[1], false); ResultActions result = mockMvc.perform( post("/userProfiles/avatar") @@ -209,9 +212,9 @@ void shouldPostAvatarReturn400OnIllegalInput() throws Exception { @Test void shouldPostAvatarReturn200OnRightInput() throws Exception { String accessToken = this.createAccessToken(); - ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("myFile.png", new byte[1]); + 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)) @@ -224,17 +227,15 @@ void shouldPostAvatarReturn200OnRightInput() throws Exception { @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; }; - doAnswer(ans).when(avatarService).updateAvatar(any(), any(), any()); + Mockito.lenient().doAnswer(ans).when(avatarService).updateAvatar(any(), any(), any()); ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("image.png", - new byte[1]); - + new byte[1], false); ResultActions result = mockMvc.perform( post("/userProfiles/avatar") .content(new ObjectMapper().writeValueAsString(profileAvatarRequest)) @@ -248,7 +249,6 @@ void shouldPostAvatarReturn400OnFileServiceAddFailureIfFileAlreadyPresent() thro @MethodSource("provideValuesFor400") void shouldPostAvatarReturn400(String request, String expectedErrorCode) throws Exception { String accessToken = this.createAccessToken(); - ResultActions result = mockMvc.perform( post("/userProfiles/avatar") .content(request) @@ -257,27 +257,24 @@ void shouldPostAvatarReturn400(String request, String expectedErrorCode) throws 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()); } - - + private static Stream provideValuesFor400() { return Stream.of( - Arguments.of("{\"filenam\":\"image.png\",\"base64\":\"AA==\"}", "NotBlank"), - Arguments.of("{\"base64\":\"AA==\"}", "NotBlank"), - Arguments.of("{\"filename\":\"\",\"base64\":\"AA==\"}", "NotBlank"), - Arguments.of("{\"filename\":\"image.png\",\"base6\":\"AA==\"}", "NotEmpty"), - Arguments.of("{\"filename\":\"image.png\"}", "NotEmpty"), - Arguments.of("{\"filename\":\"image.png\",\"base64\":\"\"}", "NotEmpty") + 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") ); } @@ -311,4 +308,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 index 1a4b382f0e..19ded83987 100644 --- 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 @@ -44,7 +44,7 @@ void shouldSupportOnlyProfileAvatarRequest() { @Test void shouldNotValidateFileNamesMissingExtensions() throws IOException { ProfileAvatarRequest request = new ProfileAvatarRequest("missing_extension_filename", - IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream())); + IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream()), false); BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); new ProfileAvatarValidator().validate(request, errors); assertEquals(1, errors.getErrorCount()); @@ -55,7 +55,7 @@ void shouldNotValidateFileNamesMissingExtensions() throws IOException { @Test void shouldNotValidateFilesOtherThanImages() { String notValidBase64Image = "bm90IGFuIGltYWdl"; - ProfileAvatarRequest request = new ProfileAvatarRequest("valid_filename.txt", notValidBase64Image.getBytes()); + ProfileAvatarRequest request = new ProfileAvatarRequest("valid_filename.txt", notValidBase64Image.getBytes(), false); BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); new ProfileAvatarValidator().validate(request, errors); assertEquals(1, errors.getErrorCount()); @@ -68,7 +68,7 @@ void shouldNotValidateFilesOtherThanImages() { @Test void shouldThrowUncheckedIOExceptionIfImageReadingFails() throws IOException { ProfileAvatarRequest request = new ProfileAvatarRequest("image.png", - IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream())); + 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); @@ -80,7 +80,7 @@ void shouldThrowUncheckedIOExceptionIfImageReadingFails() throws IOException { @Test void shouldValidateAcceptValidImageWithValidFileName() throws IOException { ProfileAvatarRequest request = new ProfileAvatarRequest("image.png", - IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream())); + IOUtils.toByteArray(new ClassPathResource("userprofile/image.png").getInputStream()), false); BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); new ProfileAvatarValidator().validate(request, errors); assertTrue(errors.getAllErrors().isEmpty()); diff --git a/pom.xml b/pom.xml index fc66e71643..3302116fad 100644 --- a/pom.xml +++ b/pom.xml @@ -79,8 +79,6 @@ agile jdbc:derby:memory:testPort;create=true jdbc:derby:memory:testServ;create=true - 11 - 11 5.3.27 5.5.7 @@ -154,7 +152,7 @@ 0.8 2.18.0 4.4 - 1.18.20 + 1.18.30 3.3.0 3.0.0-M4 2.5 From df1644901ce68c333beb38db0c3bf8be0fab23c1 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Tue, 16 Jan 2024 15:22:09 +0100 Subject: [PATCH 35/82] ENG-5423: Improvement of Gravatar integration --- .../services/userprofile/AvatarService.java | 8 +- .../web/userprofile/ProfileController.java | 20 ++-- .../validator/ProfileAvatarValidator.java | 29 +++++- .../main/resources/rest/messages.properties | 1 + .../userprofile/AvatarServiceTest.java | 10 +- .../UserProfileControllerIntegrationTest.java | 94 ++++++++++++++++++- .../UserProfileControllerTest.java | 11 ++- .../validator/ProfileAvatarValidatorTest.java | 73 +++++++++++--- pom.xml | 1 + 9 files changed, 206 insertions(+), 41 deletions(-) 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 index 3c36090dd6..ce9796769e 100644 --- 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 @@ -27,7 +27,6 @@ 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.aps.system.services.userprofile.model.AvatarDto.AvatarDtoBuilder; import org.entando.entando.web.entity.validator.EntityValidator; import org.entando.entando.web.filebrowser.model.FileBrowserFileRequest; import org.entando.entando.web.userprofile.model.ProfileAvatarRequest; @@ -119,7 +118,7 @@ private FileBrowserFileRequest addProfileImageToFileSystem( return fileBrowserFileRequest; } - private void deletePrevUserAvatarFromFileSystemIfPresent(String username) { + private void deletePrevUserAvatarFromFileSystemIfPresent(String username) throws Exception { String filename = this.getAvatarFilenameByUsername(username); if (null == filename) { return; @@ -128,7 +127,10 @@ private void deletePrevUserAvatarFromFileSystemIfPresent(String username) { fileBrowserService.deleteFile(profilePicturePath, false); } - private String getAvatarFilenameByUsername(String username) { + private String getAvatarFilenameByUsername(String username) throws Exception { + 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 -> { 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 f5ef060523..899b18adf9 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 @@ -79,7 +79,6 @@ public class ProfileController { public static final String PROTECTED_FOLDER = "protectedFolder"; - public static final String PREV_PATH = "prevPath"; @RestAccessControl(permission = {Permission.MANAGE_USER_PROFILES, Permission.MANAGE_USERS}) @@ -206,11 +205,11 @@ public ResponseEntity, Map>> ge @PostMapping(path = "/userProfiles/avatar", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity>> addAvatar( - @Valid @RequestBody ProfileAvatarRequest request, + @Validated @RequestBody ProfileAvatarRequest request, @RequestAttribute("user") UserDetails user, BindingResult bindingResult) { // validate input dto to check for consistency of input - profileAvatarValidator.validate(request, bindingResult); + profileAvatarValidator.validate(request, user, bindingResult); if (bindingResult.hasErrors()) { throw new ValidationGenericException(bindingResult); } @@ -218,19 +217,16 @@ public ResponseEntity>> addAvatar( if (bindingResult.hasErrors()) { throw new ValidationGenericException(bindingResult); } - return new ResponseEntity<>(new SimpleRestResponse<>(Map.of("filename", pictureFileName)), HttpStatus.OK); + Map response = null != pictureFileName ? Map.of("filename", pictureFileName) : Map.of("username", user.getUsername()); + return new ResponseEntity<>(new SimpleRestResponse<>(response), HttpStatus.OK); } @DeleteMapping(path = "/userProfiles/avatar") - public ResponseEntity deleteAvatar(@RequestAttribute("user") UserDetails user) { - - // delete the profile picture associated with the received user profile. If no Image is found, the method does - // nothing and returns ok anyway + public ResponseEntity>> deleteAvatar(@RequestAttribute("user") UserDetails user) { avatarService.deleteAvatar(user, new MapBindingResult(new HashMap<>(), "user")); - - // restituire lo usernane nel corpo - - return ResponseEntity.ok().build(); + 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/validator/ProfileAvatarValidator.java b/engine/src/main/java/org/entando/entando/web/userprofile/validator/ProfileAvatarValidator.java index 92c1d2d964..4164ca1f37 100644 --- 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 @@ -13,12 +13,19 @@ */ 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; @@ -27,16 +34,20 @@ 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; @@ -63,5 +74,21 @@ public void validate(@NonNull Object target, @NonNull Errors errors) { } } } + + 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/rest/messages.properties b/engine/src/main/resources/rest/messages.properties index 6c2f39a945..9db69e923f 100644 --- a/engine/src/main/resources/rest/messages.properties +++ b/engine/src/main/resources/rest/messages.properties @@ -397,3 +397,4 @@ components.usage.type.invalid=Requested occurrence for position ''{0}'' - Invali # Avatar avatar.filename.notBlank=''fileName'' is required +avatar.emailAttribute.missing=Missing email attribute in current user 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 index 36c1038e98..7fff096693 100644 --- 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 @@ -60,6 +60,7 @@ void shouldGetAvatarDataReturnAvatarInfo() throws EntException { 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); @@ -92,6 +93,7 @@ void shouldUpdateAvatarAddProfilePictureFromTheRequest() throws EntException { 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"); @@ -106,20 +108,19 @@ void shouldUpdateAvatarWithGravatar() throws EntException { ProfileAvatarRequest request = new ProfileAvatarRequest(null, null, true); UserDetails userDetails = mock(UserDetails.class); when(userDetails.getUsername()).thenReturn("test_username_gravatar"); - BasicFileAttributeViewDto dto = this.createMockFileAttributeDto("test_username_gravatar", "jpg"); - when(fileBrowserService.browseFolder(Mockito.anyString(), Mockito.eq(false))).thenReturn(List.of(dto)); + 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(1)).deleteFile(any(), any()); + verify(fileBrowserService, Mockito.times(0)).deleteFile(any(), any()); verify(fileBrowserService, Mockito.times(0)).addFile(any(), any()); } @Test void shouldDeleteAvatar() throws EntException { - // set user details to return desired username 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()); @@ -132,6 +133,7 @@ void shouldDeleteAvatarThrowException() throws EntException { 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, 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 523736a1e5..9ef440e2b6 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 @@ -25,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; @@ -47,13 +46,22 @@ 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.agiletec.aps.system.common.entity.model.attribute.MonoTextAttribute; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.io.IOUtils; +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.springframework.core.io.ClassPathResource; + class UserProfileControllerIntegrationTest extends AbstractControllerIntegrationTest { @Autowired - private IUserProfileService userProfileService; + private IUserProfileManager userProfileManager; @Autowired - private IUserProfileManager userProfileManager; + private IUserPreferencesManager userPreferencesManager; @Autowired private IUserManager userManager; @@ -493,7 +501,77 @@ void testPostMyProfileOk() throws Exception { } } } - + + @Test + void shouldPostFileAvatarReturn200OnRightInput() throws Exception { + UserPreferences userPreferences = this.userPreferencesManager.getUserPreferences("jack_bauer"); + Assertions.assertNull(userPreferences); + try { + String accessToken = this.createAccessToken(); + 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.userPreferencesManager.deleteUserPreferences("jack_bauer"); + } + } + + @Test + void shouldPostGravatarReturn200() 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 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.username").value("jack_bauer")); + userPreferences = this.userPreferencesManager.getUserPreferences("jack_bauer"); + Assertions.assertNotNull(userPreferences); + Assertions.assertTrue(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") .withAuthorization(Group.FREE_GROUP_NAME, "manageUserProfile", Permission.MANAGE_USER_PROFILES) @@ -567,5 +645,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 fa5da309c9..61e0477dca 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 @@ -41,6 +41,7 @@ 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; @@ -73,7 +74,6 @@ class UserProfileControllerTest extends AbstractControllerTest { @Mock private IUserProfileManager userProfileManager; - @Spy private ProfileAvatarValidator profileAvatarValidator; @Mock @@ -81,6 +81,7 @@ class UserProfileControllerTest extends AbstractControllerTest { @BeforeEach public void setUp() throws Exception { + profileAvatarValidator = new ProfileAvatarValidator(userProfileManager); ProfileController controller = new ProfileController(userProfileService, profileValidator, profileAvatarValidator, userManager, userProfileManager, avatarService); @@ -190,17 +191,14 @@ void shouldGetAvatarReturn200AndWellFormedResponseIfImageExists() throws Excepti @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; }; - doAnswer(ans).when(profileAvatarValidator).validate(any(), any()); ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest("fileName_without_extension", new byte[1], false); - ResultActions result = mockMvc.perform( post("/userProfiles/avatar") .content(new ObjectMapper().writeValueAsString(profileAvatarRequest)) @@ -264,7 +262,10 @@ void shouldDeleteAvatarReturn200() throws Exception { ResultActions result = mockMvc.perform( delete("/userProfiles/avatar") .header("Authorization", "Bearer " + accessToken)); - result.andExpect(status().isOk()); + 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() { 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 index 19ded83987..5b281291f9 100644 --- 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 @@ -19,26 +19,42 @@ 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(new ProfileAvatarValidator().supports(ProfileAvatarRequest.class)); - assertFalse(new ProfileAvatarValidator().supports(Object.class)); + assertTrue(profileAvatarValidator.supports(ProfileAvatarRequest.class)); + assertFalse(profileAvatarValidator.supports(Object.class)); } @Test @@ -46,25 +62,61 @@ 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"); - new ProfileAvatarValidator().validate(request, errors); + 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"); - new ProfileAvatarValidator().validate(request, errors); + 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", @@ -72,8 +124,7 @@ void shouldThrowUncheckedIOExceptionIfImageReadingFails() throws IOException { BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "profileAvatarRequest"); try (MockedStatic mockStatic = Mockito.mockStatic(ImageIO.class)) { mockStatic.when(() -> ImageIO.read(any(InputStream.class))).thenThrow(IOException.class); - ProfileAvatarValidator profileAvatarValidator = new ProfileAvatarValidator(); - assertThrows(UncheckedIOException.class, () -> profileAvatarValidator.validate(request, errors)); + assertThrows(UncheckedIOException.class, () -> profileAvatarValidator.validate(request, Mockito.mock(UserDetails.class), errors)); } } @@ -82,8 +133,8 @@ 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"); - new ProfileAvatarValidator().validate(request, errors); + profileAvatarValidator.validate(request, Mockito.mock(UserDetails.class), errors); assertTrue(errors.getAllErrors().isEmpty()); - } + } diff --git a/pom.xml b/pom.xml index 3302116fad..10854e5cb8 100644 --- a/pom.xml +++ b/pom.xml @@ -71,6 +71,7 @@ **/*.js + true org.apache.derby.jdbc.EmbeddedDriver localhost From 63cbae8c828994557145476986b8e937a7f7dbe7 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Mon, 27 Nov 2023 12:23:44 +0100 Subject: [PATCH 36/82] ENG-5347: Upgrade java to version 17 --- .github/workflows/ga-publication.yml | 2 +- .github/workflows/pr.yml | 8 +- .github/workflows/publication.yml | 2 +- Dockerfile.eap | 2 +- Dockerfile.wildfly | 2 +- admin-console/pom.xml | 2 +- cds-plugin/pom.xml | 2 +- cms-plugin/pom.xml | 2 +- .../services/content/ContentManager.java | 13 +- .../services/content/ContentUtilizer.java | 5 +- .../services/content/model/ContentDto.java | 2 + .../content/widget/UserFilterOptionBean.java | 42 ++-- .../services/resource/ResourceManager.java | 8 +- .../services/resource/ResourceUtilizer.java | 5 +- .../model/AbstractMonoInstanceResource.java | 3 +- .../resource/model/ImageResource.java | 3 +- .../resource/model/ResourceDataBean.java | 2 +- .../services/content/ContentService.java | 41 ++-- .../services/content/ContentTypeService.java | 1 + .../services/page/CmsPageServiceWrapper.java | 1 + .../services/resource/ResourcesService.java | 13 +- .../validators/WidgetValidatorCmsHelper.java | 1 + .../web/resource/ResourcesController.java | 2 +- .../validator/ResourcesValidator.java | 1 + .../widget/UserFilterOptionBeanTest.java | 197 ++++++++++++++++++ .../services/content/ContentServiceTest.java | 17 ++ contentscheduler-plugin/pom.xml | 2 +- engine/pom.xml | 6 +- .../services/category/CategoryUtilizer.java | 6 +- .../entando/aps/util/UrlUtilsTest.java | 73 +++---- .../entando/test_utils/UnitTestUtils.java | 47 ----- keycloak-plugin/core/pom.xml | 69 ------ keycloak-plugin/pom.xml | 2 +- mail-plugin/pom.xml | 2 +- pom.xml | 19 +- portal-ui/pom.xml | 2 +- redis-plugin/pom.xml | 6 +- seo-plugin/pom.xml | 2 +- solr-plugin/docker-compose.yaml | 11 - solr-plugin/pom.xml | 7 +- .../plugins/jpsolr/SolrBaseTestCase.java | 60 ++++++ .../plugins/jpsolr/SolrTestExtension.java | 76 ------- .../entando/plugins/jpsolr/SolrTestUtils.java | 48 +++++ .../aps/system/solr/AdvContentSearchTest.java | 36 +--- ...cetSearchEngineManagerIntegrationTest.java | 41 +--- ...olrSearchEngineManagerIntegrationTest.java | 32 +-- .../AbstractControllerIntegrationTest.java | 15 +- .../AdvContentSearchControllerTest.java | 3 - .../SearchByDoubleFiltersControllerTest.java | 3 - .../SearchByResourceControllerTest.java | 5 - versioning-plugin/pom.xml | 2 +- webapp/pom.xml | 2 +- 52 files changed, 499 insertions(+), 457 deletions(-) create mode 100644 cms-plugin/src/test/java/com/agiletec/plugins/jacms/aps/system/services/content/widget/UserFilterOptionBeanTest.java delete mode 100644 engine/src/test/java/org/entando/entando/test_utils/UnitTestUtils.java delete mode 100644 solr-plugin/docker-compose.yaml create mode 100644 solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/SolrBaseTestCase.java delete mode 100644 solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/SolrTestExtension.java 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..85a6cf4715 100755 --- a/.github/workflows/publication.yml +++ b/.github/workflows/publication.yml @@ -10,7 +10,7 @@ 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 diff --git a/Dockerfile.eap b/Dockerfile.eap index df815cde69..9750a75d4a 100644 --- a/Dockerfile.eap +++ b/Dockerfile.eap @@ -1,4 +1,4 @@ -FROM registry.hub.docker.com/entando/entando-eap73-clustered-base:7.2.0 +FROM registry.hub.docker.com/entando/entando-eap73-clustered-base:7.4.0-ENG-5316-PR-28 ARG VERSION ### Required OpenShift Labels diff --git a/Dockerfile.wildfly b/Dockerfile.wildfly index ee27854dce..0bc6b0e283 100644 --- a/Dockerfile.wildfly +++ b/Dockerfile.wildfly @@ -1,4 +1,4 @@ -FROM registry.hub.docker.com/entando/entando-wildfly17-base:7.2.0 +FROM registry.hub.docker.com/entando/entando-wildfly17-base:7.4.0-ENG-5316-PR-28 ARG VERSION 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/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/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 1dc297b1e7..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 @@ -52,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); @@ -614,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) { @@ -632,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) { @@ -641,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) { @@ -660,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/model/ContentDto.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/content/model/ContentDto.java index 6aabf6a215..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,6 +313,7 @@ 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) { values.entrySet().stream() 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..4e80457f7d 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()); @@ -603,7 +603,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 +643,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..b5b1ffca2d 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 @@ -21,6 +21,7 @@ import java.io.InputStream; 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; @@ -60,7 +61,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/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/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 9f7572c358..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; @@ -328,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); @@ -339,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) { @@ -350,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) { @@ -361,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) { @@ -372,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) { @@ -563,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); @@ -716,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); @@ -751,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); @@ -843,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); @@ -860,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() @@ -887,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..912066001a 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; @@ -523,8 +523,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 +531,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 +581,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 +592,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/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/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/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/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/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/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/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 ca7f4382ae..1744e3f4df 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,8 +64,8 @@ HEAD - 11 - 11 + 17 + 17 true @@ -80,8 +80,6 @@ agile jdbc:derby:memory:testPort;create=true jdbc:derby:memory:testServ;create=true - 11 - 11 5.3.27 5.5.7 @@ -132,6 +130,7 @@ 3.11.2 5.7.2 1.17.6 + 2.1.3 4.0.1 1.3.2 1.1.3 @@ -155,7 +154,7 @@ 0.8 2.18.0 4.4 - 1.18.20 + 1.18.22 3.3.0 3.0.0-M4 2.5 @@ -1015,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/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/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 83f5f6bf93..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 @@ -158,6 +158,11 @@ mockito-junit-jupiter test + + uk.org.webcompere + system-stubs-jupiter + test + org.springframework spring-test 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/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/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/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/webapp/pom.xml b/webapp/pom.xml index 268f1e3685..f758289cac 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 From d58a503b7573384cc6e66aa5f068ded065283d5a Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Mon, 4 Dec 2023 10:21:28 +0100 Subject: [PATCH 37/82] ENG-5347: Removed eap and wildfly images --- .github/workflows/publication.yml | 61 +------------------------------ Dockerfile.eap | 20 ---------- Dockerfile.tomcat | 4 +- Dockerfile.wildfly | 21 ----------- 4 files changed, 3 insertions(+), 103 deletions(-) delete mode 100644 Dockerfile.eap delete mode 100644 Dockerfile.wildfly diff --git a/.github/workflows/publication.yml b/.github/workflows/publication.yml index 85a6cf4715..2dd993b034 100755 --- a/.github/workflows/publication.yml +++ b/.github/workflows/publication.yml @@ -12,15 +12,10 @@ env: 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 9750a75d4a..0000000000 --- a/Dockerfile.eap +++ /dev/null @@ -1,20 +0,0 @@ -FROM registry.hub.docker.com/entando/entando-eap73-clustered-base:7.4.0-ENG-5316-PR-28 -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 0bc6b0e283..0000000000 --- a/Dockerfile.wildfly +++ /dev/null @@ -1,21 +0,0 @@ -FROM registry.hub.docker.com/entando/entando-wildfly17-base:7.4.0-ENG-5316-PR-28 - -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 From 8e35a8fdca38588ee848f960f755f4056cc8ef57 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Mon, 4 Dec 2023 15:34:04 +0100 Subject: [PATCH 38/82] ENG-5347: Removed jboss-deployment-structure file --- admin-console/src/main/webapp/META-INF/MANIFEST.MF | 3 --- admin-console/src/main/webapp/META-INF/context.xml | 2 -- portal-ui/src/main/webapp/META-INF/context.xml | 2 -- webapp/src/main/webapp/META-INF/context.xml | 2 ++ .../webapp/WEB-INF/jboss-deployment-structure.xml | 12 ------------ 5 files changed, 2 insertions(+), 19 deletions(-) delete mode 100644 admin-console/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 admin-console/src/main/webapp/META-INF/context.xml delete mode 100644 portal-ui/src/main/webapp/META-INF/context.xml create mode 100644 webapp/src/main/webapp/META-INF/context.xml delete mode 100644 webapp/src/main/webapp/WEB-INF/jboss-deployment-structure.xml 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/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/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 From dbb9367e1f1942c53f4adb338489b83a6e7b02f4 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Wed, 17 Jan 2024 09:41:19 +0100 Subject: [PATCH 39/82] ENG-5423: Fix sonar issues --- .../services/userprofile/AvatarService.java | 8 ++---- .../web/userprofile/ProfileController.java | 6 ++-- .../UserProfileControllerIntegrationTest.java | 28 +++++++++++++++---- .../UserProfileControllerTest.java | 2 -- 4 files changed, 29 insertions(+), 15 deletions(-) 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 index ce9796769e..5b601dfbab 100644 --- 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 @@ -27,6 +27,7 @@ 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; @@ -36,9 +37,6 @@ @RequiredArgsConstructor public class AvatarService implements IAvatarService { - private static final String GRAVATAR_AVATAR_OPTION = "GRAVATAR"; - private static final String LOCAL_AVATAR_OPTION = "LOCAL_FILE"; - private final IFileBrowserService fileBrowserService; private final IUserPreferencesManager userPreferencesManager; @@ -118,7 +116,7 @@ private FileBrowserFileRequest addProfileImageToFileSystem( return fileBrowserFileRequest; } - private void deletePrevUserAvatarFromFileSystemIfPresent(String username) throws Exception { + private void deletePrevUserAvatarFromFileSystemIfPresent(String username) throws EntException { String filename = this.getAvatarFilenameByUsername(username); if (null == filename) { return; @@ -127,7 +125,7 @@ private void deletePrevUserAvatarFromFileSystemIfPresent(String username) throws fileBrowserService.deleteFile(profilePicturePath, false); } - private String getAvatarFilenameByUsername(String username) throws Exception { + private String getAvatarFilenameByUsername(String username) throws EntException { if (!fileBrowserService.exists(DEFAULT_AVATAR_PATH)) { return null; } 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 7b8ae8d0af..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 @@ -204,7 +204,7 @@ public ResponseEntity, Map>> ge } @PostMapping(path = "/userProfiles/avatar", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity>> addAvatar( + public ResponseEntity>> addAvatar( @Validated @RequestBody ProfileAvatarRequest request, @RequestAttribute("user") UserDetails user, BindingResult bindingResult) { @@ -217,12 +217,12 @@ public ResponseEntity>> addAvatar( if (bindingResult.hasErrors()) { throw new ValidationGenericException(bindingResult); } - Map response = null != pictureFileName ? Map.of("filename", pictureFileName) : Map.of("username", user.getUsername()); + 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) { + public ResponseEntity>> deleteAvatar(@RequestAttribute("user") UserDetails user) { avatarService.deleteAvatar(user, new MapBindingResult(new HashMap<>(), "user")); Map payload = new HashMap<>(); payload.put("username", user.getUsername()); 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 9ef440e2b6..2fd400b765 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 @@ -47,12 +47,12 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.agiletec.aps.system.common.entity.model.attribute.ITextAttribute; -import com.agiletec.aps.system.common.entity.model.attribute.MonoTextAttribute; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.io.IOUtils; 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 { @@ -526,7 +526,7 @@ void shouldPostFileAvatarReturn200OnRightInput() throws Exception { } @Test - void shouldPostGravatarReturn200() throws Exception { + void shouldPostDeleteGravatarReturn200() throws Exception { UserPreferences userPreferences = this.userPreferencesManager.getUserPreferences("jack_bauer"); Assertions.assertNull(userPreferences); try { @@ -537,16 +537,34 @@ void shouldPostGravatarReturn200() throws Exception { this.userProfileManager.addProfile("jack_bauer", profile); String accessToken = this.createAccessToken(); ProfileAvatarRequest profileAvatarRequest = new ProfileAvatarRequest(null, null, true); - ResultActions result = mockMvc.perform( + ResultActions resultPost = 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.username").value("jack_bauer")); + 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"); 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 61e0477dca..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 @@ -14,7 +14,6 @@ package org.entando.entando.web.userprofile; 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.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -50,7 +49,6 @@ import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.stubbing.Answer; import org.springframework.core.io.ClassPathResource; From 30a8b4d51d87a1d1ff639fc9021d4a49a42be87a Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Wed, 17 Jan 2024 17:37:20 +0100 Subject: [PATCH 40/82] ENG-5439: Upgrade default configuration of Versioning plugin --- .../versioning/VersioningManager.java | 5 +- .../jsp/config/versioningSysConfig.jsp | 12 +++- ...sioningDAO.java => VersioningDAOTest.java} | 2 +- ... => VersioningManagerIntegrationTest.java} | 13 +++- .../versioning/VersioningManagerTest.java | 61 +++++++++++++++++++ 5 files changed, 85 insertions(+), 8 deletions(-) rename versioning-plugin/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/{TestVersioningDAO.java => VersioningDAOTest.java} (99%) rename versioning-plugin/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/{TestVersioningManager.java => VersioningManagerIntegrationTest.java} (97%) create mode 100644 versioning-plugin/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManagerTest.java 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/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()); + } + +} From c1fcbd90b8f671b134f6e188d88c7916fd28a26c Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Thu, 18 Jan 2024 14:40:50 +0100 Subject: [PATCH 41/82] ENG-5440: CDS-Connector: made `/public` path configurable --- .../aps/system/storage/CdsConfiguration.java | 7 ++-- .../aps/system/storage/CdsStorageManager.java | 8 ++--- .../jpcds/aps/system/storage/CdsUrlUtils.java | 12 ++++--- .../system/storage/CdsConfigurationTest.java | 1 - .../system/storage/CdsStorageManagerTest.java | 16 ++++----- .../aps/system/storage/CdsUtilsTest.java | 36 +++++++++++++++---- 6 files changed, 52 insertions(+), 28 deletions(-) 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..589bc34f87 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,8 @@ 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_PRIVATE_URL:https://cds.entando.org}") private String cdsPrivateUrl; @Value("${CDS_PATH:/api/v1}") @@ -51,6 +53,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/CdsStorageManager.java b/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsStorageManager.java index 9cb0c81c2b..bae2ea3072 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 @@ -116,7 +116,7 @@ public boolean deleteFile(String subPath, boolean isProtectedResource) { URI apiUrl = EntUrlBuilder.builder() .url(CdsUrlUtils.buildCdsInternalApiUrl(config, configuration)) .path("/delete/") - .path(CdsUrlUtils.getInternalSection(isProtectedResource)) + .path(CdsUrlUtils.getInternalSection(isProtectedResource, config, this.configuration)) .path(subPath) .build(); @@ -147,7 +147,7 @@ public InputStream getStream(String subPath, boolean isProtectedResource) throws url = EntUrlBuilder.builder() .url(url) - .path(CdsUrlUtils.getInternalSection(isProtectedResource)) + .path(CdsUrlUtils.getInternalSection(isProtectedResource, config, this.configuration)) .path(subPath).build(); Optional is = caller.getFile(url, config, isProtectedResource); @@ -245,7 +245,7 @@ private List listAttributes(String subPath, boolean isPr URI apiUrl = EntUrlBuilder.builder() .url(CdsUrlUtils.buildCdsInternalApiUrl(config, configuration).toString()) .path("/list/") - .path(CdsUrlUtils.getInternalSection(isProtectedResource)) + .path(CdsUrlUtils.getInternalSection(isProtectedResource, config, this.configuration)) .path(subPath) .build(); @@ -314,7 +314,7 @@ private String validateAndReturnResourcePath(Optional config, Stri 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.getInternalSection(privateUrl, config, this.configuration)) // << this is part of base url because we want check path traversal!! .build().toString(); String fullPath = EntUrlBuilder.builder() 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..0cfe677cdb 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,16 +29,20 @@ 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_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 getInternalSection(boolean isProtectedResource, Optional config, CdsConfiguration configuration) { + if (isProtectedResource) { + return DEFAULT_SECTION_PRIVATE; + } + 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){ 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..99f0504a1b 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 @@ -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/CdsStorageManagerTest.java b/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsStorageManagerTest.java index 3e4e0a483c..b09f075a12 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 @@ -226,6 +226,7 @@ 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", "cdsPath","/mytenant/api/v1/"); TenantConfig tc = new TenantConfig(configMap); Mockito.when(tenantManager.getConfig("my-tenant")).thenReturn(Optional.ofNullable(tc)); @@ -236,7 +237,7 @@ void shouldManageErrorWhenCallGetStream() throws Exception { ).isInstanceOf(EntRuntimeException.class).hasMessageStartingWith("Error validating path"); String testFilePath = "/testfolder/test.txt"; - URI testFile = URI.create( baseUrl + "/public" + testFilePath); + URI testFile = URI.create( baseUrl + "/custom-path" + testFilePath); Mockito.when(cdsRemoteCaller.getFile(eq(testFile), any(), eq(false))).thenReturn(null); @@ -246,7 +247,7 @@ void shouldManageErrorWhenCallGetStream() throws Exception { 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)); @@ -257,7 +258,7 @@ void shouldManageErrorWhenCallGetStream() throws Exception { String testFilePathNotFound = "/testfolder/test-notfound.txt"; - URI testFileNotFound = URI.create( baseUrl + "/public" + testFilePathNotFound); + URI testFileNotFound = URI.create( baseUrl + "/custom-path" + testFilePathNotFound); Mockito.when(cdsRemoteCaller.getFile(eq(testFileNotFound), any(), eq(false))).thenThrow(new HttpClientErrorException(HttpStatus.NOT_FOUND)); @@ -276,7 +277,7 @@ void shouldReturnDataWhenCallGetStream() throws Exception { 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)))); @@ -292,15 +293,14 @@ void shouldReturnDataWhenCallGetStream() throws Exception { 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 +314,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 +358,14 @@ 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", "cdsPath","/mytenant/api/v1/"); TenantConfig tc = new TenantConfig(configMap); Mockito.when(tenantManager.getConfig("my-tenant")).thenReturn(Optional.ofNullable(tc)); 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..cabee83919 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,12 +13,18 @@ */ package org.entando.entando.plugins.jpcds.aps.system.storage; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import org.apache.commons.collections4.map.HashedMap; +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) @@ -60,18 +66,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.getInternalSection(true, null, null)); + Assertions.assertEquals("/protected", CdsUrlUtils.getInternalSection(true, null, Mockito.mock(CdsConfiguration.class))); + + CdsConfiguration cdsConfiguration = new CdsConfiguration(); + cdsConfiguration.setCdsPublicPath(""); + Assertions.assertEquals("", CdsUrlUtils.getInternalSection(false, Optional.ofNullable(null), cdsConfiguration)); + + cdsConfiguration.setCdsPublicPath("/public"); + Assertions.assertEquals("/public", CdsUrlUtils.getInternalSection(false, Optional.ofNullable(null), cdsConfiguration)); + + Map tenantParams = new HashMap<>(); + TenantConfig tenantConfig = new TenantConfig(tenantParams); + Assertions.assertEquals("", CdsUrlUtils.getInternalSection(false, Optional.ofNullable(tenantConfig), cdsConfiguration)); + + tenantParams.put("cdsPublicPath", "/customPath"); + Assertions.assertEquals("/customPath", CdsUrlUtils.getInternalSection(false, Optional.ofNullable(tenantConfig), cdsConfiguration)); + Assertions.assertEquals("/protected", CdsUrlUtils.getInternalSection(true, Optional.ofNullable(tenantConfig), cdsConfiguration)); + } } From 848fb4f86220a3e3e9d83e5c8edf8a08ad3f27f5 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Mon, 22 Jan 2024 09:09:09 +0100 Subject: [PATCH 42/82] ENG-5444: Removed data associated with deleted user --- .../UserPreferencesManager.java | 8 +- .../services/userprofile/AvatarService.java | 5 + .../services/userprofile/IAvatarService.java | 6 +- ...rAspect.java => UserManagementAspect.java} | 39 ++- .../aps/managers/userprofileManagers.xml | 4 +- .../userprofile/UserManagementAspectTest.java | 247 ++++++++++++++++++ .../UserProfileManagerAspectTest.java | 160 ------------ ...rPreferencesControllerIntegrationTest.java | 54 +++- .../UserProfileControllerIntegrationTest.java | 54 +++- 9 files changed, 391 insertions(+), 186 deletions(-) rename engine/src/main/java/org/entando/entando/aps/system/services/userprofile/{UserProfileManagerAspect.java => UserManagementAspect.java} (75%) create mode 100644 engine/src/test/java/org/entando/entando/aps/system/services/userprofile/UserManagementAspectTest.java delete mode 100644 engine/src/test/java/org/entando/entando/aps/system/services/userprofile/UserProfileManagerAspectTest.java 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 fa439aae90..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 @@ -14,8 +14,12 @@ 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; @@ -57,9 +61,5 @@ public void updateUserGravatarPreference(String username, boolean enabled) throw public void deleteUserPreferences(String username) throws EntException { userPreferencesDAO.deleteUserPreferences(username); } - - public void setUserPreferencesDAO(UserPreferencesDAO userPreferencesDAO) { - this.userPreferencesDAO = userPreferencesDAO; - } } 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 index 5b601dfbab..c2c373fa98 100644 --- 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 @@ -105,6 +105,11 @@ public void deleteAvatar(UserDetails userDetails, BindingResult bindingResult) { } } + @Override + public void deleteAvatar(String username) throws EntException { + this.deletePrevUserAvatarFromFileSystemIfPresent(username); + } + //------------------------ Utility methods ------------------------------------// private FileBrowserFileRequest addProfileImageToFileSystem( 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 index 965b186917..f9467b8fc5 100644 --- 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 @@ -2,15 +2,19 @@ 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/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/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/web/userpreferences/UserPreferencesControllerIntegrationTest.java b/engine/src/test/java/org/entando/entando/web/userpreferences/UserPreferencesControllerIntegrationTest.java index f7f396d151..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 @@ -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,14 +21,17 @@ 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; @@ -58,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) @@ -86,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/UserProfileControllerIntegrationTest.java b/engine/src/test/java/org/entando/entando/web/userprofile/UserProfileControllerIntegrationTest.java index 2fd400b765..9d4d59e898 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 @@ -49,6 +49,7 @@ import com.agiletec.aps.system.common.entity.model.attribute.ITextAttribute; import com.fasterxml.jackson.databind.ObjectMapper; 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; @@ -60,6 +61,9 @@ class UserProfileControllerIntegrationTest extends AbstractControllerIntegration @Autowired private IUserProfileManager userProfileManager; + @Autowired + private IStorageManager storageManager; + @Autowired private IUserPreferencesManager userPreferencesManager; @@ -502,6 +506,50 @@ 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 shouldPostFileAvatarReturn200OnRightInput() throws Exception { UserPreferences userPreferences = this.userPreferencesManager.getUserPreferences("jack_bauer"); @@ -591,7 +639,11 @@ void shouldReturnErrorOnPostGravatarWithNullProfile() throws Exception { } 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); From 802cb98761f64a8da3a1bdc8e0c8beb55a9ea11d Mon Sep 17 00:00:00 2001 From: Riccardo Fadda Date: Tue, 23 Jan 2024 11:06:57 +0100 Subject: [PATCH 43/82] ENG-3474 Fixed colon in content description issue for content list widget --- .../page/serializer/WidgetConfigPropertiesSerializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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..6af64ae197 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 @@ -157,7 +157,7 @@ private Map extractProperty(String value) { continue; } - String[] keyValue = f.split("=|:"); + String[] keyValue = f.split("=|:", 2); if (keyValue.length != 2) { logger.warn("Invalid filter format: {}", f); continue; From f5d2140f9fb979bbd0e04626038e76110e78bd22 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Thu, 25 Jan 2024 09:26:42 +0100 Subject: [PATCH 44/82] ENG-3474: Added unit test and fix sonar issues --- .../WidgetConfigPropertiesSerializer.java | 19 +-- .../page/PageConfigurationControllerTest.java | 129 ++++++++++++++++++ 2 files changed, 136 insertions(+), 12 deletions(-) create mode 100644 engine/src/test/java/org/entando/entando/web/page/PageConfigurationControllerTest.java 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 6af64ae197..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("=|:", 2); + 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/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"))); + } + +} From 36bb562f29ecaa2e3bcf9fc3a5ccd3e4de513eb2 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Wed, 31 Jan 2024 11:50:30 +0100 Subject: [PATCH 45/82] ENG-5347: Pom upgrade --- webapp/pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webapp/pom.xml b/webapp/pom.xml index f758289cac..8d10971ce9 100644 --- a/webapp/pom.xml +++ b/webapp/pom.xml @@ -826,7 +826,7 @@ 3.0.0 - 11 + 17 @@ -837,8 +837,8 @@ org.apache.maven.plugins maven-compiler-plugin - 11 - 11 + 17 + 17 From 7b18ab9677d5ace795b32ed5dbc6c26d4a83f84a Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Tue, 30 Jan 2024 17:13:30 +0100 Subject: [PATCH 46/82] ENG-5461: Restored transient fields after serialization of content attributes --- .../aps/system/storage/CdsStorageManager.java | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) 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 bae2ea3072..93f08c9341 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; @@ -41,17 +42,18 @@ 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; - + private transient ITenantManager tenantManager; + private transient CdsConfiguration configuration; + private transient CdsRemoteCaller caller; @Autowired public CdsStorageManager(CdsRemoteCaller caller, ITenantManager tenantManager, CdsConfiguration configuration) { @@ -355,4 +357,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); + } + } From 682437dd3b659f6dc15a1a10d7bb41fe5e0f615d Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Wed, 31 Jan 2024 14:56:05 +0100 Subject: [PATCH 47/82] ENG-5461: Improvement test coverage --- .../aps/system/storage/CdsRemoteCaller.java | 3 +- .../aps/system/storage/CdsStorageManager.java | 6 ++ .../system/storage/CdsConfigurationTest.java | 2 +- .../storage/CdsCreateRowResponseDtoTest.java | 3 +- .../storage/CdsFileAttributeViewDtoTest.java | 4 +- .../system/storage/CdsRemoteCallerTest.java | 5 +- .../system/storage/CdsStorageManagerTest.java | 2 +- ...dsStorageSerializationIntegrationTest.java | 89 +++++++++++++++++++ .../aps/system/storage/CdsUtilsTest.java | 11 --- 9 files changed, 102 insertions(+), 23 deletions(-) create mode 100644 cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsStorageSerializationIntegrationTest.java 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 93f08c9341..17a6029c60 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 @@ -24,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; @@ -51,8 +53,12 @@ public class CdsStorageManager implements IStorageManager { private static final String ERROR_VALIDATING_PATH_MSG = "Error validating path"; + + @Getter(AccessLevel.PROTECTED) private transient ITenantManager tenantManager; + @Getter(AccessLevel.PROTECTED) private transient CdsConfiguration configuration; + @Getter(AccessLevel.PROTECTED) private transient CdsRemoteCaller caller; @Autowired 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 99f0504a1b..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 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 b09f075a12..43c72d273b 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 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 cabee83919..9468c36db4 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 @@ -16,12 +16,9 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; -import org.apache.commons.collections4.map.HashedMap; 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; @@ -30,14 +27,6 @@ @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"); From adeabfdb04269e3026421dc7516ef0d07b3657e4 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Mon, 5 Feb 2024 09:59:34 +0100 Subject: [PATCH 48/82] ENG-5464: Fix CDS internal section bug --- .../aps/system/storage/CdsConfiguration.java | 2 ++ .../aps/system/storage/CdsStorageManager.java | 29 +++++++------------ .../jpcds/aps/system/storage/CdsUrlUtils.java | 18 ++++++------ .../system/storage/CdsStorageManagerTest.java | 5 ++-- .../aps/system/storage/CdsUtilsTest.java | 17 +++++------ 5 files changed, 31 insertions(+), 40 deletions(-) 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 589bc34f87..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 @@ -41,6 +41,8 @@ public class CdsConfiguration { 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}") 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 17a6029c60..7c12b7b532 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 @@ -85,9 +85,7 @@ private void create(String subPath, boolean isProtectedResource, Optional is = caller.getFile(url, config, isProtectedResource); @@ -178,7 +172,7 @@ public InputStream getStream(String subPath, boolean isProtectedResource) throws public String getResourceUrl(String subPath, boolean isProtectedResource) { try { Optional config = getTenantConfig(); - return this.validateAndReturnResourcePath(config, subPath, isProtectedResource); + return this.validateAndReturnResourcePath(config, subPath, false, isProtectedResource); } catch (Exception e) { throw new EntRuntimeException("Error extracting resource url", e); } @@ -248,12 +242,12 @@ 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, config, this.configuration)) + .path(CdsUrlUtils.getSection(isProtectedResource, config, this.configuration, true)) .path(subPath) .build(); @@ -318,22 +312,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, config, this.configuration)) // << 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); 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 0cfe677cdb..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 @@ -30,6 +30,7 @@ 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 DEFAULT_SECTION_PUBLIC = ""; @@ -37,24 +38,25 @@ public final class CdsUrlUtils { private CdsUrlUtils(){ } - - public static String getInternalSection(boolean isProtectedResource, Optional config, CdsConfiguration configuration) { + + public static String getSection(boolean isProtectedResource, Optional config, CdsConfiguration configuration, boolean internalCall) { if (isProtectedResource) { return DEFAULT_SECTION_PRIVATE; } - return config.map(c -> c.getProperty(CDS_PUBLIC_PATH_TENANT_PARAM).orElse(DEFAULT_SECTION_PUBLIC)).orElse(configuration.getCdsPublicPath()); + 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){ @@ -63,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/CdsStorageManagerTest.java b/cds-plugin/src/test/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsStorageManagerTest.java index 43c72d273b..e76c391ec0 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 @@ -227,6 +227,7 @@ void shouldManageErrorWhenCallGetStream() throws Exception { 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)); @@ -244,8 +245,7 @@ void shouldManageErrorWhenCallGetStream() throws Exception { Assertions.assertThatThrownBy( ()-> cdsStorageManager.getStream(testFilePath,false) ).isInstanceOf(EntException.class).hasMessageStartingWith("Error extracting file"); - - + String testFilePathBadGateway = "/testfolder/test-badgw.txt"; URI testFileBadGateway = URI.create( baseUrl + "/custom-path" + testFilePathBadGateway); Mockito.when(cdsRemoteCaller.getFile(eq(testFileBadGateway), @@ -366,6 +366,7 @@ void shouldWorkFineWhenCallExistsWithRootAsEmpty() throws Exception { 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)); 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 9468c36db4..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 @@ -31,7 +31,6 @@ class CdsUtilsTest { 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"); @@ -40,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(""); @@ -66,23 +63,23 @@ void shouldWorkFineWithToString() { @Test void shouldExtractRigthSection() { - Assertions.assertEquals("/protected", CdsUrlUtils.getInternalSection(true, null, null)); - Assertions.assertEquals("/protected", CdsUrlUtils.getInternalSection(true, null, Mockito.mock(CdsConfiguration.class))); + 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.getInternalSection(false, Optional.ofNullable(null), cdsConfiguration)); + Assertions.assertEquals("", CdsUrlUtils.getSection(false, Optional.ofNullable(null), cdsConfiguration, false)); cdsConfiguration.setCdsPublicPath("/public"); - Assertions.assertEquals("/public", CdsUrlUtils.getInternalSection(false, Optional.ofNullable(null), cdsConfiguration)); + Assertions.assertEquals("/public", CdsUrlUtils.getSection(false, Optional.ofNullable(null), cdsConfiguration, false)); Map tenantParams = new HashMap<>(); TenantConfig tenantConfig = new TenantConfig(tenantParams); - Assertions.assertEquals("", CdsUrlUtils.getInternalSection(false, Optional.ofNullable(tenantConfig), cdsConfiguration)); + Assertions.assertEquals("", CdsUrlUtils.getSection(false, Optional.ofNullable(tenantConfig), cdsConfiguration, true)); tenantParams.put("cdsPublicPath", "/customPath"); - Assertions.assertEquals("/customPath", CdsUrlUtils.getInternalSection(false, Optional.ofNullable(tenantConfig), cdsConfiguration)); - Assertions.assertEquals("/protected", CdsUrlUtils.getInternalSection(true, Optional.ofNullable(tenantConfig), cdsConfiguration)); + Assertions.assertEquals("/customPath", CdsUrlUtils.getSection(false, Optional.ofNullable(tenantConfig), cdsConfiguration, false)); + Assertions.assertEquals("/protected", CdsUrlUtils.getSection(true, Optional.ofNullable(tenantConfig), cdsConfiguration, true)); } } From 36768d7c1375ee365dcefdc3257198889bd1fa21 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Mon, 12 Feb 2024 10:53:20 +0100 Subject: [PATCH 49/82] ENG-5468: Fix extracting file with empty stream from CDS --- .../aps/system/storage/CdsStorageManager.java | 36 +++++++------- .../system/storage/CdsStorageManagerTest.java | 47 ++++++++++++------- .../services/storage/LocalStorageManager.java | 4 +- .../EntResourceNotFoundException.java | 9 +++- 4 files changed, 54 insertions(+), 42 deletions(-) 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 7c12b7b532..55969f867e 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 @@ -81,19 +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, 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()); } @@ -112,8 +111,8 @@ 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, true, isProtectedResource); @@ -123,7 +122,7 @@ public boolean deleteFile(String subPath, boolean 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) { @@ -136,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); } - + 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.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); @@ -171,7 +169,7 @@ public InputStream getStream(String subPath, boolean isProtectedResource) throws @Override public String getResourceUrl(String subPath, boolean isProtectedResource) { try { - Optional config = getTenantConfig(); + Optional config = this.getTenantConfig(); return this.validateAndReturnResourcePath(config, subPath, false, isProtectedResource); } catch (Exception e) { throw new EntRuntimeException("Error extracting resource url", e); @@ -187,7 +185,7 @@ public boolean exists(String subPath, boolean isProtectedResource) { // 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); @@ -250,7 +248,7 @@ private List listAttributes(String subPath, boolean isPr .path(CdsUrlUtils.getSection(isProtectedResource, config, this.configuration, true)) .path(subPath) .build(); - + Optional cdsFileList = caller.getFileAttributeView(apiUrl, config); return remapAndSort(cdsFileList, filter); 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 e76c391ec0..3323a9ed93 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 @@ -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; @@ -238,13 +239,19 @@ void shouldManageErrorWhenCallGetStream() throws Exception { ).isInstanceOf(EntRuntimeException.class).hasMessageStartingWith("Error validating path"); String testFilePath = "/testfolder/test.txt"; + + 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 + "/custom-path" + testFilePathBadGateway); @@ -256,15 +263,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 + "/custom-path" + 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 @@ -276,20 +277,22 @@ 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/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"); @@ -466,7 +469,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)); @@ -474,8 +477,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"); @@ -483,7 +487,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/", @@ -500,7 +504,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"); @@ -585,5 +589,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/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..9c8fae05ce 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 **"); } 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); } + } From adc8bca5a2a142fd3248547b5e1274a00409ebe6 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Thu, 15 Feb 2024 10:10:30 +0100 Subject: [PATCH 50/82] ENG-5470: Added parameter to enable/disable Database Restore function --- .../services/database/DatabaseService.java | 34 +++++++++---------- .../main/resources/rest/messages.properties | 1 + .../resources/spring/aps/servicesConfig.xml | 1 + .../web/database/DatabaseControllerTest.java | 22 ++++++++++-- 4 files changed, 38 insertions(+), 20 deletions(-) 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..5f6eb8ada2 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 @@ -46,8 +51,12 @@ public class DatabaseService implements IDatabaseService { private final EntLogger logger = EntLogFactory.getSanitizedLogger(this.getClass()); + @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() { @@ -121,17 +130,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, "reportCode"); + 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); } this.getDatabaseManager().dropAndRestoreBackup(reportCode); - } catch (ResourceNotFoundException r) { + } catch (ValidationGenericException | ResourceNotFoundException r) { throw r; } catch (Throwable t) { logger.error("error starting restore", t); @@ -173,20 +187,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/resources/rest/messages.properties b/engine/src/main/resources/rest/messages.properties index 9db69e923f..4955556dca 100644 --- a/engine/src/main/resources/rest/messages.properties +++ b/engine/src/main/resources/rest/messages.properties @@ -283,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 diff --git a/engine/src/main/resources/spring/aps/servicesConfig.xml b/engine/src/main/resources/spring/aps/servicesConfig.xml index 330cd8553f..4a0fe88a80 100644 --- a/engine/src/main/resources/spring/aps/servicesConfig.xml +++ b/engine/src/main/resources/spring/aps/servicesConfig.xml @@ -143,6 +143,7 @@ + ${db.restore.enabled} 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 { From 2c6ad6502cd7f2b6dffcc1c77c50d96dea6858a9 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Thu, 15 Feb 2024 10:13:48 +0100 Subject: [PATCH 51/82] ENG-5469: Database Restore function tenant aware --- .../aps/system/init/DatabaseManager.java | 19 +++++- .../aps/system/init/DatabaseRestorer.java | 67 +++++++++++-------- .../database/validator/DatabaseValidator.java | 1 + .../aps/system/init/DatabaseManagerTest.java | 2 +- 4 files changed, 57 insertions(+), 32 deletions(-) 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..33dd681811 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 @@ -36,6 +36,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import javax.servlet.ServletContext; @@ -53,6 +54,7 @@ import liquibase.resource.ClassLoaderResourceAccessor; import org.apache.commons.beanutils.BeanComparator; import org.apache.commons.io.output.StringBuilderWriter; +import org.apache.commons.lang3.StringUtils; import org.entando.entando.aps.system.init.IInitializerManager.DatabaseMigrationStrategy; import org.entando.entando.aps.system.init.exception.DatabaseMigrationException; import org.entando.entando.aps.system.init.model.Component; @@ -65,11 +67,13 @@ import org.entando.entando.aps.system.services.storage.IStorageManager; import org.entando.entando.aps.system.services.storage.StorageManagerUtil; import org.entando.entando.aps.system.services.tenants.ITenantManager; +import org.entando.entando.aps.system.services.tenants.TenantDataAccessor; 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 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 +100,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 +575,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 +593,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 +601,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..2658ccc8f2 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,8 @@ */ package org.entando.entando.aps.system.init; +import com.agiletec.aps.util.ApsTenantApplicationUtils; +import com.agiletec.aps.util.ApsWebApplicationUtils; import org.entando.entando.ent.exception.EntException; import com.agiletec.aps.util.FileTextReader; @@ -20,12 +22,14 @@ import java.io.InputStream; import java.util.List; import java.util.Map; +import java.util.Optional; import javax.sql.DataSource; 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 +54,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 +83,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 +97,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/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/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); } } From 4154308216729c4d3b81ff89171b7d652aebc2d7 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Thu, 15 Feb 2024 10:57:10 +0100 Subject: [PATCH 52/82] ENG-5469: Fix smell code --- .../entando/entando/aps/system/init/DatabaseManager.java | 3 --- .../entando/entando/aps/system/init/DatabaseRestorer.java | 2 -- .../aps/system/services/database/DatabaseService.java | 8 +++++--- 3 files changed, 5 insertions(+), 8 deletions(-) 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 33dd681811..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 @@ -36,7 +36,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import javax.servlet.ServletContext; @@ -54,7 +53,6 @@ import liquibase.resource.ClassLoaderResourceAccessor; import org.apache.commons.beanutils.BeanComparator; import org.apache.commons.io.output.StringBuilderWriter; -import org.apache.commons.lang3.StringUtils; import org.entando.entando.aps.system.init.IInitializerManager.DatabaseMigrationStrategy; import org.entando.entando.aps.system.init.exception.DatabaseMigrationException; import org.entando.entando.aps.system.init.model.Component; @@ -67,7 +65,6 @@ import org.entando.entando.aps.system.services.storage.IStorageManager; import org.entando.entando.aps.system.services.storage.StorageManagerUtil; import org.entando.entando.aps.system.services.tenants.ITenantManager; -import org.entando.entando.aps.system.services.tenants.TenantDataAccessor; import org.entando.entando.ent.exception.EntException; import org.entando.entando.ent.exception.EntRuntimeException; import org.entando.entando.ent.util.EntLogging.EntLogFactory; 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 2658ccc8f2..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 @@ -14,7 +14,6 @@ package org.entando.entando.aps.system.init; import com.agiletec.aps.util.ApsTenantApplicationUtils; -import com.agiletec.aps.util.ApsWebApplicationUtils; import org.entando.entando.ent.exception.EntException; import com.agiletec.aps.util.FileTextReader; @@ -22,7 +21,6 @@ import java.io.InputStream; import java.util.List; import java.util.Map; -import java.util.Optional; import javax.sql.DataSource; 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 5f6eb8ada2..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 @@ -50,6 +50,8 @@ 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; @@ -91,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) { @@ -135,14 +137,14 @@ public void startDatabaseBackup() { public void startDatabaseRestore(String reportCode) { try { if (!this.isRestoreEnabled()) { - BindException bindException = new BindException(this, "reportCode"); + 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 (ValidationGenericException | ResourceNotFoundException r) { From 8008edbcd4623c2160573ac822a2297477d29aab Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Wed, 21 Feb 2024 15:11:14 +0100 Subject: [PATCH 53/82] ENG-5475: Added mapKey and mapValue into attributeDto for EnumeratorMap attribute --- .../entity/model/EntityAttributeDto.java | 7 ++--- .../UserProfileControllerIntegrationTest.java | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) 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/test/java/org/entando/entando/web/userprofile/UserProfileControllerIntegrationTest.java b/engine/src/test/java/org/entando/entando/web/userprofile/UserProfileControllerIntegrationTest.java index 9d4d59e898..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 @@ -48,6 +48,11 @@ 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; @@ -82,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 { From 532fab59f6cc3aa2cb689f3d9f9775281e0197a3 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Thu, 7 Mar 2024 18:00:07 +0100 Subject: [PATCH 54/82] ENG-5485: Fix bug on publication of unpublished contents --- .../helper/ContentAuthorizationHelper.java | 12 +- .../ContentViewerHelperIntegrationTest.java | 65 ++++- .../services/cache/CacheInfoManager.java | 74 +++-- .../services/cache/CacheInfoManagerTest.java | 263 +++++++++--------- 4 files changed, 225 insertions(+), 189 deletions(-) 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/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/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/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); + } + } From d6a515fdd5eb13058688b24d31cc2fcc4f9305a0 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Fri, 8 Mar 2024 16:57:32 +0100 Subject: [PATCH 55/82] ENG-5486: Added file length validation --- .../resource/MultipleResourceAction.java | 24 ++++--------------- .../apsadmin/resource/package_en.properties | 3 ++- .../apsadmin/resource/package_it.properties | 1 + .../resource/TestMultipleResourceAction.java | 1 + .../liquibase/jpversioning/changeSetPort.xml | 2 ++ ...0308000000_jpversioning_resource_descr.xml | 14 +++++++++++ 6 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 versioning-plugin/src/main/resources/liquibase/jpversioning/port/20240308000000_jpversioning_resource_descr.xml 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..91e56c1c1c 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 @@ -54,7 +54,7 @@ public void validate() { this.fetchFileFields(); addFieldErrors(validateFileDescriptions()); addFieldErrors(validateFileUploadIDs()); - addFieldErrors(validateFileUploaNames()); + addFieldErrors(validateFileUploadNames()); addFieldErrors(validateFileUploadContentType()); } } @@ -74,15 +74,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 +87,6 @@ private List validateFileDescriptions() { errors.add(new FieldError(FILE_DESCR_FIELD + i, getText("error.resource.file.descrTooLong"))); } } - return errors; } @@ -100,43 +96,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 +136,16 @@ 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; } 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..0c9008a73e 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 @@ -79,6 +79,7 @@ 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. @@ -89,7 +90,7 @@ 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..2ef7170cb1 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 @@ -77,6 +77,7 @@ 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. 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/TestMultipleResourceAction.java index 4b2e01875d..56f08c4b76 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/TestMultipleResourceAction.java @@ -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/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 @@ + + + + + + + + + From 6eec181e8923d4f24b50ac263b5c7442faae91c4 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Thu, 15 Feb 2024 15:21:05 +0100 Subject: [PATCH 56/82] ENG-5473: Integration of diskinfo api --- .../jpcds/aps/system/storage/CdsDiskInfo.java | 29 ++++++ .../aps/system/storage/CdsRemoteCaller.java | 19 ++-- .../aps/system/storage/CdsStorageManager.java | 19 +++- .../system/storage/ICdsStorageManager.java | 22 +++++ .../system/storage/CdsRemoteCallerTest.java | 91 +++++++------------ .../system/storage/CdsStorageManagerTest.java | 64 ++++++++----- 6 files changed, 157 insertions(+), 87 deletions(-) create mode 100644 cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsDiskInfo.java create mode 100644 cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/ICdsStorageManager.java diff --git a/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsDiskInfo.java b/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsDiskInfo.java new file mode 100644 index 0000000000..6e91c93f3c --- /dev/null +++ b/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsDiskInfo.java @@ -0,0 +1,29 @@ +/* + * 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 + * 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 com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Getter; +import lombok.Setter; + +@Getter@Setter +public class CdsDiskInfo implements Serializable { + + @JsonProperty(value = "total_size") + private long totalSize; + @JsonProperty(value = "used_space") + private long usedSpace; + +} 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 17eba2842a..5e4bca005c 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 @@ -177,7 +177,6 @@ public Optional getFileAttributeView(URI url, Optiona logger.debug("Trying to call GET (getFileAttributeView) on url:'{}' and is config tenant empty:'{}'", url, config.isEmpty()); - return this.executeGetCall(url, Arrays.asList(MediaType.APPLICATION_JSON), config, @@ -190,7 +189,6 @@ public Optional getFile(URI url, Optional co url, isProtectedResource, config.isEmpty()); - Optional bytes; if (isProtectedResource) { bytes = executeGetCall(url, null, config, false, new ParameterizedTypeReference(){}); @@ -204,11 +202,21 @@ public Optional getFile(URI url, Optional co } throw buildExceptionWithMessage("GET", e.getStatusCode(), url.toString()); } - } return bytes.map(ByteArrayInputStream::new); } - + + public Optional getDiskInfo(URI url) { + logger.debug("Trying to call GET (diskinfo)"); + Optional infos; + try { + infos = Optional.ofNullable(restTemplate.getForObject(url, CdsDiskInfo.class)); + } catch (HttpClientErrorException e) { + throw buildExceptionWithMessage("GET", e.getStatusCode(), url.toString()); + } + return infos; + } + private Optional executeGetCall(URI url, List acceptableMediaTypes, Optional config, boolean forceTokenRetrieve, ParameterizedTypeReference responseType) { @@ -217,7 +225,6 @@ private Optional executeGetCall(URI url, List acceptableMediaT HttpEntity entity = new HttpEntity<>(headers); ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.GET, entity, responseType); return Optional.ofNullable(responseEntity.getBody()); - } catch (HttpClientErrorException e) { if (e.getStatusCode().equals(HttpStatus.NOT_FOUND)) { logger.info("File Not found - uri {}", url); @@ -231,7 +238,7 @@ private Optional executeGetCall(URI url, List acceptableMediaT } } - private EntRuntimeException buildExceptionWithMessage(String method, HttpStatus statusCode, String url){ + private EntRuntimeException buildExceptionWithMessage(String method, HttpStatus statusCode, String url) { return new EntRuntimeException(String.format(REST_ERROR_MSG, method, statusCode, url)); } 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 55969f867e..ffbd8189a4 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 @@ -50,7 +50,7 @@ @Slf4j @Service(SystemConstants.STORAGE_MANAGER) @CdsActive(true) -public class CdsStorageManager implements IStorageManager { +public class CdsStorageManager implements IStorageManager, ICdsStorageManager { private static final String ERROR_VALIDATING_PATH_MSG = "Error validating path"; @@ -166,6 +166,23 @@ public InputStream getStream(String subPath, boolean isProtectedResource) throws } } + @Override + public CdsDiskInfo getDiskInfo() throws EntException { + final String ERROR_EXTRACTING_DISK_INFO = "Error extracting disk info: url %s"; + URI url = null; + try { + Optional config = getTenantConfig(); + url = CdsUrlUtils.buildCdsInternalApiUrl(config, configuration, "utils", "diskinfo"); + Optional is = caller.getDiskInfo(url); + return is.orElseThrow(IOException::new); + } catch (EntRuntimeException ert) { + throw ert; + } catch (Exception e) { + String errorMessage = String.format(ERROR_EXTRACTING_DISK_INFO, Optional.ofNullable(url).map(URI::toString).orElse("null")); + throw new EntResourceNotFoundException(errorMessage, e); + } + } + @Override public String getResourceUrl(String subPath, boolean isProtectedResource) { try { diff --git a/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/ICdsStorageManager.java b/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/ICdsStorageManager.java new file mode 100644 index 0000000000..28ca2be019 --- /dev/null +++ b/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/ICdsStorageManager.java @@ -0,0 +1,22 @@ +/* + * 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 + * 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 org.entando.entando.ent.exception.EntException; + +public interface ICdsStorageManager { + + public CdsDiskInfo getDiskInfo() throws EntException; + +} 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 28c137c812..b4d5dd74ae 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 @@ -20,6 +20,7 @@ import static org.mockito.ArgumentMatchers.eq; import com.agiletec.aps.util.ApsTenantApplicationUtils; +import com.fasterxml.jackson.databind.ObjectMapper; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.net.URI; @@ -48,14 +49,14 @@ @ExtendWith(MockitoExtension.class) class CdsRemoteCallerTest { + @InjectMocks private CdsRemoteCaller cdsRemoteCaller; @Mock private RestTemplate restTemplate; @Mock private CdsConfiguration cdsConfiguration; - - + @Test void shouldCreateDirectoryForTenant() throws Exception { Map configMap = Map.of("cdsPublicUrl","http://my-server/tenant1/cms-resources", @@ -168,8 +169,6 @@ void shouldManageErrorInExecutePostCall() throws Exception { Assertions.assertEquals( "Invalid operation 'POST', response status:'502 BAD_GATEWAY' for url:'http://cds-kube-service:8081/mytenant/api/v1/upload/'", ex.getMessage()); - - Mockito.when(restTemplate.exchange(eq(url),eq(HttpMethod.POST),any(), eq(new ParameterizedTypeReference>(){}))) .thenThrow(new HttpClientErrorException(HttpStatus.UNAUTHORIZED)); Mockito.when(restTemplate.exchange(eq(auth.toString()), @@ -177,7 +176,6 @@ void shouldManageErrorInExecutePostCall() throws Exception { any(), eq(new ParameterizedTypeReference>(){}))).thenReturn( ResponseEntity.status(HttpStatus.OK).body(Map.of("access_token","xxxxxx"))); - ex = assertThrows(EntRuntimeException.class, () -> cdsRemoteCaller.executePostCall(url, "/sub-path-testy", @@ -189,21 +187,10 @@ void shouldManageErrorInExecutePostCall() throws Exception { Assertions.assertEquals( "Invalid operation 'POST', response status:'401 UNAUTHORIZED' for url:'http://cds-kube-service:8081/mytenant/api/v1/upload/'", ex.getMessage()); - } @Test void shouldCreateFileForPrimary() throws Exception { - Map configMap = Map.of("cdsPublicUrl","http://my-server/tenant1/cms-resources", - "cdsPrivateUrl","http://cds-kube-service:8081/", - "cdsPath","/mytenant/api/v1", - "kcAuthUrl", "http://tenant1.server.com/auth", - "kcRealm", "tenant1", - "kcClientId", "id", - "kcClientSecret", "secret", - "tenantCode", "my-tenant1"); - - TenantConfig tc = new TenantConfig(configMap); Mockito.when(cdsConfiguration.getKcAuthUrl()).thenReturn("http://auth.server.com/auth"); Mockito.when(cdsConfiguration.getKcRealm()).thenReturn("primary"); Mockito.when(cdsConfiguration.getKcClientId()).thenReturn("sec1"); @@ -234,9 +221,6 @@ void shouldCreateFileForPrimary() throws Exception { Assertions.assertTrue(ret.isStatusOk()); - - Assertions.assertTrue(ret.isStatusOk()); - Mockito.verify(restTemplate, Mockito.times(1)) .exchange(eq(authPrimary.toString()), eq(HttpMethod.POST), @@ -246,16 +230,6 @@ void shouldCreateFileForPrimary() throws Exception { @Test void shouldDeleteFile() { - Map configMap = Map.of("cdsPublicUrl","http://my-server/tenant1/cms-resources", - "cdsPrivateUrl","http://cds-kube-service:8081/", - "cdsPath","/mytenant/api/v1", - "kcAuthUrl", "http://tenant1.server.com/auth", - "kcRealm", "tenant1", - "kcClientId", "id", - "kcClientSecret", "secret", - "tenantCode", "my-tenant1"); - - TenantConfig tc = new TenantConfig(configMap); Mockito.when(cdsConfiguration.getKcAuthUrl()).thenReturn("http://auth.server.com/auth"); Mockito.when(cdsConfiguration.getKcRealm()).thenReturn("primary"); Mockito.when(cdsConfiguration.getKcClientId()).thenReturn("sec1"); @@ -310,8 +284,7 @@ void shouldManageErrorWhenDeleteFile() { any(), eq(new ParameterizedTypeReference>(){}))).thenReturn( ResponseEntity.status(HttpStatus.OK).body(Map.of(OAuth2AccessToken.ACCESS_TOKEN,"xxxxxx"))); - - + Exception ex = assertThrows(EntRuntimeException.class, () -> cdsRemoteCaller.executeDeleteCall(urlBadGateway, Optional.ofNullable(tc), @@ -322,9 +295,7 @@ void shouldManageErrorWhenDeleteFile() { ex.getMessage()); Mockito.verify(restTemplate, Mockito.times(1)) .exchange(eq(urlBadGateway),eq(HttpMethod.DELETE),any(), eq(new ParameterizedTypeReference>(){})); - - - + URI urlUnAuth = URI.create("http://cds-kube-service:8081/mytenant/api/v1/delete/public/filename-unauth.txt"); Mockito.when(restTemplate.exchange(eq(urlUnAuth),eq(HttpMethod.DELETE),any(), eq(new ParameterizedTypeReference>(){}))) .thenThrow(new HttpClientErrorException(HttpStatus.UNAUTHORIZED)); @@ -346,26 +317,40 @@ void shouldManageErrorWhenDeleteFile() { .exchange(eq(urlUnAuth),eq(HttpMethod.DELETE),any(), eq(new ParameterizedTypeReference>(){})); } + + @Test + void shouldRetrieveDiskInfo() throws Exception { + String url = "http://cds-kube-service:8081/mytenant/api/v1/utils/diskinfo"; + URI urlUnAuth = URI.create(url); + Mockito.when(restTemplate.getForObject(eq(urlUnAuth), eq(CdsDiskInfo.class))).thenThrow(new HttpClientErrorException(HttpStatus.UNAUTHORIZED)); + Exception ex = assertThrows(EntRuntimeException.class,() -> cdsRemoteCaller.getDiskInfo(urlUnAuth)); + Assertions.assertEquals( + String.format("Invalid operation 'GET', response status:'401 UNAUTHORIZED' for url:'%s'", url), + ex.getMessage()); + CdsDiskInfo diskInfo = new CdsDiskInfo(); + diskInfo.setTotalSize(10l); + diskInfo.setUsedSpace(5l); + Mockito.when(restTemplate.getForObject(eq(urlUnAuth), eq(CdsDiskInfo.class))).thenReturn(diskInfo); + Optional extractedOpt = this.cdsRemoteCaller.getDiskInfo(urlUnAuth); + Assertions.assertTrue(extractedOpt.isPresent()); + Assertions.assertEquals(10l, extractedOpt.get().getTotalSize()); + } + + @Test + void shouldDeserializeDiskInfo() throws Exception { + String response = "{\"total_size\": 222,\"used_space\": 111}"; + CdsDiskInfo info = new ObjectMapper().readValue(response, CdsDiskInfo.class); + Assertions.assertEquals(222l, info.getTotalSize()); + Assertions.assertEquals(111l, info.getUsedSpace()); + } @Test void shouldRetrieveFileContent() throws Exception { - Map configMap = Map.of("cdsPublicUrl","http://my-server/tenant1/cms-resources", - "cdsPrivateUrl","http://cds-kube-service:8081/", - "cdsPath","/mytenant/api/v1", - "kcAuthUrl", "http://tenant1.server.com/auth", - "kcRealm", "tenant1", - "kcClientId", "id", - "kcClientSecret", "secret", - "tenantCode", "my-tenant1"); - - TenantConfig tc = new TenantConfig(configMap); Mockito.when(cdsConfiguration.getKcAuthUrl()).thenReturn("http://auth.server.com/auth"); Mockito.when(cdsConfiguration.getKcRealm()).thenReturn("primary"); Mockito.when(cdsConfiguration.getKcClientId()).thenReturn("sec1"); Mockito.when(cdsConfiguration.getKcClientSecret()).thenReturn("sec"); - Map map = Map.of("status","OK"); - //public URI url = URI.create("http://cds-kube-service:8081/mytenant/public/myfolder/filename.txt"); byte[] data = "random text".getBytes(StandardCharsets.UTF_8); @@ -408,22 +393,11 @@ void shouldRetrieveFileContent() throws Exception { @Test void shouldManageErrorWhenRetrieveFileContent() throws Exception { - Map configMap = Map.of("cdsPublicUrl","http://my-server/tenant1/cms-resources", - "cdsPrivateUrl","http://cds-kube-service:8081/", - "cdsPath","/mytenant/api/v1", - "kcAuthUrl", "http://tenant1.server.com/auth", - "kcRealm", "tenant1", - "kcClientId", "id", - "kcClientSecret", "secret", - "tenantCode", "my-tenant1"); - - TenantConfig tc = new TenantConfig(configMap); Mockito.when(cdsConfiguration.getKcAuthUrl()).thenReturn("http://auth.server.com/auth"); Mockito.when(cdsConfiguration.getKcRealm()).thenReturn("primary"); Mockito.when(cdsConfiguration.getKcClientId()).thenReturn("sec1"); Mockito.when(cdsConfiguration.getKcClientSecret()).thenReturn("sec"); - Map map = Map.of("status","OK"); URI publicUrlBadGateway = URI.create("http://cds-kube-service:8081/mytenant/public/myfolder/filename-badgw.txt"); //public BAD_GATEWAY @@ -515,8 +489,7 @@ void shouldRetrieveFileAttribute() { Mockito.when(cdsConfiguration.getKcRealm()).thenReturn("primary"); Mockito.when(cdsConfiguration.getKcClientId()).thenReturn("sec1"); Mockito.when(cdsConfiguration.getKcClientSecret()).thenReturn("sec"); - - Map map = Map.of("status","OK"); + URI url = URI.create("http://cds-kube-service:8081/mytenant/api/v1/list/protected/myfolder/filename.txt"); // private api for resource in public or protected 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 3323a9ed93..5b2fd7f6b2 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 @@ -13,6 +13,7 @@ */ package org.entando.entando.plugins.jpcds.aps.system.storage; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.*; @@ -75,7 +76,6 @@ void shouldCreateDirectory() throws Exception { CdsCreateResponseDto ret = new CdsCreateResponseDto(); ret.setStatusOk(true); - ArgumentCaptor captor = ArgumentCaptor.forClass(URI.class); Mockito.when(cdsRemoteCaller.executePostCall(eq(URI.create("http://cds-kube-service:8081/mytenant/api/v1/upload/")), eq("/sub-path-testy"), eq(false), @@ -86,11 +86,6 @@ void shouldCreateDirectory() throws Exception { ApsTenantApplicationUtils.setTenant("my-tenant"); cdsStorageManager.createDirectory("/sub-path-testy",false); - - MultiValueMap body = new LinkedMultiValueMap<>(); - body.add("path", "/sub-path-testy"); - body.add("protected", false); - Mockito.verify(cdsRemoteCaller, Mockito.times(1)) .executePostCall(eq(URI.create("http://cds-kube-service:8081/mytenant/api/v1/upload/")), eq("/sub-path-testy"), @@ -109,37 +104,28 @@ void shouldNotCreateDirectory() throws Exception { TenantConfig tc = new TenantConfig(configMap); Mockito.when(tenantManager.getConfig("my-tenant")).thenReturn(Optional.ofNullable(tc)); ApsTenantApplicationUtils.setTenant("my-tenant"); - Assertions.assertThatThrownBy( ()-> cdsStorageManager.createDirectory("../../sub-path-testy",false) ).isInstanceOf(EntRuntimeException.class).hasMessageStartingWith("Path validation failed:"); - Assertions.assertThatThrownBy( ()-> cdsStorageManager.createDirectory(null,false) ).isInstanceOf(EntRuntimeException.class).hasMessageStartingWith("Error validating path"); - - CdsCreateResponseDto ret = new CdsCreateResponseDto(); ret.setStatusOk(false); - ArgumentCaptor captor = ArgumentCaptor.forClass(URI.class); Mockito.when(cdsRemoteCaller.executePostCall(any(), eq("/sub-path-testy"), eq(false), any(), any(), eq(false))).thenReturn(ret); - ApsTenantApplicationUtils.setTenant("my-tenant"); Assertions.assertThatThrownBy( ()-> cdsStorageManager.createDirectory("/sub-path-testy",false) ).isInstanceOf(EntRuntimeException.class).hasMessageStartingWith("Invalid status - Response"); - - ApsTenantApplicationUtils.setTenant("my-notexist-tenant"); Assertions.assertThatThrownBy( ()-> cdsStorageManager.createDirectory("/sub-path-testy",false) ).isInstanceOf(EntRuntimeException.class).hasMessage("Error saving file/directory"); - } @Test @@ -149,21 +135,17 @@ void shouldCreateFile() throws Exception { "cdsPath","/mytenant/api/v1/"); TenantConfig tc = new TenantConfig(configMap); Mockito.when(tenantManager.getConfig("my-tenant")).thenReturn(Optional.ofNullable(tc)); - 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), any(), any(), eq(false))).thenReturn(ret); - ApsTenantApplicationUtils.setTenant("my-tenant"); InputStream is = new ByteArrayInputStream("testo a casos".getBytes(StandardCharsets.UTF_8)); cdsStorageManager.saveFile("/sub-path-testy/myfilename",false, is); - Mockito.verify(cdsRemoteCaller, Mockito.times(1)) .executePostCall(eq(URI.create("http://cds-kube-service:8081/mytenant/api/v1/upload/")), eq("/sub-path-testy/myfilename"), @@ -171,9 +153,50 @@ void shouldCreateFile() throws Exception { any(), any(), eq(false)); - } + @Test + void shouldRetrieveDiskInfo() throws Exception { + final String baseUrl = "http://my-server/tenant1/cms-resources"; + Map configMap = Map.of("cdsPublicUrl", baseUrl, + "cdsPrivateUrl","http://tenant-cds-kube-service:8081", + "cdsPublicPath","/custom-path", + "cdsInternalPublicSection", "/custom-path", + "cdsPath","/api/v1"); + TenantConfig tc = new TenantConfig(configMap); + Mockito.when(tenantManager.getConfig("my-tenant")).thenReturn(Optional.ofNullable(tc)); + ApsTenantApplicationUtils.setTenant("my-tenant"); + Mockito.when(this.cdsRemoteCaller.getDiskInfo(Mockito.any(URI.class))).thenReturn(Optional.of(Mockito.mock(CdsDiskInfo.class))); + CdsDiskInfo diskInfo = this.cdsStorageManager.getDiskInfo(); + Assertions.assertThat(diskInfo).isNotNull(); + ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(URI.class); + Mockito.verify(cdsRemoteCaller).getDiskInfo(uriCaptor.capture()); + String expectedUrl = "http://tenant-cds-kube-service:8081/api/v1/utils/diskinfo"; + Assertions.assertThat(uriCaptor.getValue().toString()).isEqualTo(expectedUrl); + } + + @Test + void shouldReturnErrorCallingDiskInfo() throws Exception { + final String baseUrl = "http://my-server/tenant1/cms-resources"; + Map configMap = Map.of("cdsPublicUrl", baseUrl, + "cdsPrivateUrl","http://tenantx-cds-kube-service:8081", + "cdsPublicPath","/custom-path", + "cdsInternalPublicSection", "/custom-path", + "cdsPath","/api/v1"); + TenantConfig tc = new TenantConfig(configMap); + Mockito.when(tenantManager.getConfig("my-tenant")).thenReturn(Optional.ofNullable(tc)); + ApsTenantApplicationUtils.setTenant("my-tenant"); + Mockito.when(this.cdsRemoteCaller.getDiskInfo(Mockito.any(URI.class))).thenThrow(new EntRuntimeException("Error")); + Exception ex = assertThrows(Exception.class,() -> this.cdsStorageManager.getDiskInfo()); + Assertions.assertThat(ex).isInstanceOf(EntRuntimeException.class); + + Mockito.when(this.cdsRemoteCaller.getDiskInfo(Mockito.any(URI.class))).thenReturn(Optional.empty()); + Exception otherEx = assertThrows(Exception.class,() -> this.cdsStorageManager.getDiskInfo()); + Assertions.assertThat(otherEx).isInstanceOf(EntResourceNotFoundException.class); + String expectedUrl = "http://tenantx-cds-kube-service:8081/api/v1/utils/diskinfo"; + Assertions.assertThat(otherEx.getMessage()).contains(expectedUrl); + } + @Test void shouldNotDeleteDirectory() throws Exception { Map configMap = Map.of("cdsPublicUrl","http://my-server/tenant1/cms-resources", @@ -530,7 +553,6 @@ void shouldEditFile() throws Exception { CdsCreateResponseDto ret = new CdsCreateResponseDto(); ret.setStatusOk(true); ret.setList(Arrays.asList(new CdsCreateRowResponseDto[]{})); - ArgumentCaptor captor = ArgumentCaptor.forClass(URI.class); Mockito.when(cdsRemoteCaller.executePostCall(any(), eq("/sub-path-testy/myfilename"), eq(false), From e0f340a831a70ce40a88961561f4cc72224ce186 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Fri, 16 Feb 2024 16:15:49 +0100 Subject: [PATCH 57/82] ENG-5473: Integration of diskinfo inside admin-console --- admin-console/pom.xml | 23 +++++-- .../filebrowser/FileBrowserAction.java | 15 +++++ .../filebrowser/package_en.properties | 5 ++ .../filebrowser/package_it.properties | 5 ++ .../jsp/filebrowser/filebrowser-list.jsp | 15 ++++- ... => FileBrowserActionIntegrationTest.java} | 4 +- .../filebrowser/FileBrowserActionTest.java | 67 +++++++++++++++++++ .../aps/system/storage/CdsRemoteCaller.java | 10 +-- .../aps/system/storage/CdsStorageManager.java | 7 +- .../system/storage/ICdsStorageManager.java | 22 ------ .../system/storage/CdsRemoteCallerTest.java | 29 +++++--- .../system/storage/CdsStorageManagerTest.java | 12 ++-- .../services/storage/IStorageManager.java | 4 +- .../services/storage/LocalStorageManager.java | 8 +++ .../services/storage/model/DiskInfoDto.java | 4 +- .../web/system/CurrentSystemController.java | 6 -- .../LocalStorageManagerIntegrationTest.java | 20 +++++- 17 files changed, 192 insertions(+), 64 deletions(-) rename admin-console/src/test/java/org/entando/entando/apsadmin/filebrowser/{TestFileBrowserAction.java => FileBrowserActionIntegrationTest.java} (99%) create mode 100644 admin-console/src/test/java/org/entando/entando/apsadmin/filebrowser/FileBrowserActionTest.java delete mode 100644 cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/ICdsStorageManager.java rename cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsDiskInfo.java => engine/src/main/java/org/entando/entando/aps/system/services/storage/model/DiskInfoDto.java (88%) diff --git a/admin-console/pom.xml b/admin-console/pom.xml index 4054735eb6..c420ca7467 100644 --- a/admin-console/pom.xml +++ b/admin-console/pom.xml @@ -274,21 +274,34 @@ org.apache.httpcomponents httpclient
+ + javax.servlet + javax.servlet-api + + + javax.servlet.jsp + jsp-api + + org.springframework spring-test + test org.junit.jupiter junit-jupiter + test org.junit.jupiter junit-jupiter-api + test org.mockito mockito-core + test org.mockito @@ -298,14 +311,12 @@ org.mockito mockito-junit-jupiter + test - javax.servlet - javax.servlet-api - - - javax.servlet.jsp - jsp-api + uk.org.webcompere + system-stubs-jupiter + test org.apache.derby diff --git a/admin-console/src/main/java/org/entando/entando/apsadmin/filebrowser/FileBrowserAction.java b/admin-console/src/main/java/org/entando/entando/apsadmin/filebrowser/FileBrowserAction.java index 3db3a885f1..f8de312ea9 100644 --- a/admin-console/src/main/java/org/entando/entando/apsadmin/filebrowser/FileBrowserAction.java +++ b/admin-console/src/main/java/org/entando/entando/apsadmin/filebrowser/FileBrowserAction.java @@ -28,9 +28,12 @@ import org.apache.commons.lang3.StringUtils; import org.entando.entando.aps.system.services.storage.BasicFileAttributeView; +import org.entando.entando.aps.system.services.storage.CdsEnvironmentVariables; import org.entando.entando.aps.system.services.storage.IStorageManager; import org.entando.entando.aps.system.services.storage.RootFolderAttributeView; import org.entando.entando.aps.system.services.storage.StorageManagerUtil; +import org.entando.entando.aps.system.services.storage.model.DiskInfoDto; +import org.entando.entando.ent.exception.EntException; import org.entando.entando.ent.util.EntLogging.EntLogger; import org.entando.entando.ent.util.EntLogging.EntLogFactory; @@ -256,6 +259,18 @@ public String createDir() { } return SUCCESS; } + + public DiskInfoDto getDiskInfo() { + try { + if (!CdsEnvironmentVariables.active()) { + return null; + } + return this.getStorageManager().getDiskInfo(); + } catch (EntException t) { + logger.error("error extracting disk info", t); + return null; + } + } public String download() { try { diff --git a/admin-console/src/main/java/org/entando/entando/apsadmin/filebrowser/package_en.properties b/admin-console/src/main/java/org/entando/entando/apsadmin/filebrowser/package_en.properties index 8efeab9b2f..d28404f448 100644 --- a/admin-console/src/main/java/org/entando/entando/apsadmin/filebrowser/package_en.properties +++ b/admin-console/src/main/java/org/entando/entando/apsadmin/filebrowser/package_en.properties @@ -22,3 +22,8 @@ filebrowser.label.add-fileinput=Add Another File filebrowser.label.remove-fileinput=Remove from form filebrowser.label.button-choose-file=Choose file filebrowser.label.no-file-selected=No file chosen + +label.disk.status=Disk status +label.disk.used=(Used) +label.disk.available=(Available) +label.disk.used.percentage=(Use%) diff --git a/admin-console/src/main/java/org/entando/entando/apsadmin/filebrowser/package_it.properties b/admin-console/src/main/java/org/entando/entando/apsadmin/filebrowser/package_it.properties index da67ae6082..0ad1b3f44c 100644 --- a/admin-console/src/main/java/org/entando/entando/apsadmin/filebrowser/package_it.properties +++ b/admin-console/src/main/java/org/entando/entando/apsadmin/filebrowser/package_it.properties @@ -22,3 +22,8 @@ filebrowser.label.add-fileinput=Aggiungi un altro file filebrowser.label.remove-fileinput=Rimuovi dal form filebrowser.label.button-choose-file=Seleziona un file filebrowser.label.no-file-selected=Nessun file selezionato + +label.disk.status=Stato disco +label.disk.used=(Usato) +label.disk.available=(Totale Disponibile) +label.disk.used.percentage=(Percentuale utilizzo) diff --git a/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/filebrowser/filebrowser-list.jsp b/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/filebrowser/filebrowser-list.jsp index a3cd2e9b2f..b2f33e3d6e 100644 --- a/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/filebrowser/filebrowser-list.jsp +++ b/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/filebrowser/filebrowser-list.jsp @@ -23,6 +23,18 @@
+ + + +
+  :   / +   – +  %  +
+ + + +
@@ -42,8 +54,7 @@ "> - + diff --git a/admin-console/src/test/java/org/entando/entando/apsadmin/filebrowser/TestFileBrowserAction.java b/admin-console/src/test/java/org/entando/entando/apsadmin/filebrowser/FileBrowserActionIntegrationTest.java similarity index 99% rename from admin-console/src/test/java/org/entando/entando/apsadmin/filebrowser/TestFileBrowserAction.java rename to admin-console/src/test/java/org/entando/entando/apsadmin/filebrowser/FileBrowserActionIntegrationTest.java index c018bf7c33..816ae0758e 100644 --- a/admin-console/src/test/java/org/entando/entando/apsadmin/filebrowser/TestFileBrowserAction.java +++ b/admin-console/src/test/java/org/entando/entando/apsadmin/filebrowser/FileBrowserActionIntegrationTest.java @@ -33,9 +33,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class TestFileBrowserAction extends ApsAdminBaseTestCase { +class FileBrowserActionIntegrationTest extends ApsAdminBaseTestCase { - private static final EntLogger logger = EntLogFactory.getSanitizedLogger(TestFileBrowserAction.class); + private static final EntLogger logger = EntLogFactory.getSanitizedLogger(FileBrowserActionIntegrationTest.class); private IStorageManager localStorageManager; diff --git a/admin-console/src/test/java/org/entando/entando/apsadmin/filebrowser/FileBrowserActionTest.java b/admin-console/src/test/java/org/entando/entando/apsadmin/filebrowser/FileBrowserActionTest.java new file mode 100644 index 0000000000..5caba4330f --- /dev/null +++ b/admin-console/src/test/java/org/entando/entando/apsadmin/filebrowser/FileBrowserActionTest.java @@ -0,0 +1,67 @@ +/* + * 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 + * 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.apsadmin.filebrowser; + +import org.entando.entando.aps.system.services.storage.IStorageManager; +import org.entando.entando.aps.system.services.storage.model.DiskInfoDto; +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; + +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +import uk.org.webcompere.systemstubs.jupiter.SystemStub; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; + +@ExtendWith({MockitoExtension.class,SystemStubsExtension.class}) +class FileBrowserActionTest { + + @SystemStub + private static EnvironmentVariables environmentVariables; + + @Mock + private IStorageManager storageManager; + + @InjectMocks + private FileBrowserAction fileBrowserAction; + + @Test + void shouldReturnNullDiskInfo() throws Exception { + environmentVariables.set("CDS_ENABLED", "false"); + DiskInfoDto dto = fileBrowserAction.getDiskInfo(); + Assertions.assertNull(dto); + Mockito.verifyNoInteractions(this.storageManager); + } + + @Test + void shouldReturnDiskInfo() throws Exception { + environmentVariables.set("CDS_ENABLED", "true"); + Mockito.when(this.storageManager.getDiskInfo()).thenReturn(Mockito.mock(DiskInfoDto.class)); + DiskInfoDto dto = fileBrowserAction.getDiskInfo(); + Assertions.assertNotNull(dto); + } + + @Test + void shouldReturnNulInvokingGetDiskInfoWithServiceError() throws Exception { + environmentVariables.set("CDS_ENABLED", "true"); + Mockito.when(this.storageManager.getDiskInfo()).thenThrow(new EntException("Error")); + DiskInfoDto dto = fileBrowserAction.getDiskInfo(); + Assertions.assertNull(dto); + } + +} 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 5e4bca005c..c637a59c96 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 @@ -13,6 +13,8 @@ */ package org.entando.entando.plugins.jpcds.aps.system.storage; +import org.entando.entando.aps.system.services.storage.model.DiskInfoDto; + import static org.entando.entando.aps.system.services.tenants.ITenantManager.PRIMARY_CODE; import java.io.ByteArrayInputStream; @@ -206,11 +208,11 @@ public Optional getFile(URI url, Optional co return bytes.map(ByteArrayInputStream::new); } - public Optional getDiskInfo(URI url) { - logger.debug("Trying to call GET (diskinfo)"); - Optional infos; + public Optional getDiskInfo(URI url, Optional config) { + logger.error("Trying to call GET (diskinfo) on url:'{}' and is config tenant empty:'{}'", url, config.isEmpty()); + Optional infos; try { - infos = Optional.ofNullable(restTemplate.getForObject(url, CdsDiskInfo.class)); + infos = this.executeGetCall(url, null, config, false, new ParameterizedTypeReference(){}); } catch (HttpClientErrorException e) { throw buildExceptionWithMessage("GET", e.getStatusCode(), url.toString()); } 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 ffbd8189a4..e22c363ffb 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 org.entando.entando.aps.system.services.storage.model.DiskInfoDto; import com.agiletec.aps.system.SystemConstants; import com.agiletec.aps.util.ApsTenantApplicationUtils; import java.io.ByteArrayInputStream; @@ -50,7 +51,7 @@ @Slf4j @Service(SystemConstants.STORAGE_MANAGER) @CdsActive(true) -public class CdsStorageManager implements IStorageManager, ICdsStorageManager { +public class CdsStorageManager implements IStorageManager { private static final String ERROR_VALIDATING_PATH_MSG = "Error validating path"; @@ -167,13 +168,13 @@ public InputStream getStream(String subPath, boolean isProtectedResource) throws } @Override - public CdsDiskInfo getDiskInfo() throws EntException { + public DiskInfoDto getDiskInfo() throws EntException { final String ERROR_EXTRACTING_DISK_INFO = "Error extracting disk info: url %s"; URI url = null; try { Optional config = getTenantConfig(); url = CdsUrlUtils.buildCdsInternalApiUrl(config, configuration, "utils", "diskinfo"); - Optional is = caller.getDiskInfo(url); + Optional is = caller.getDiskInfo(url, config); return is.orElseThrow(IOException::new); } catch (EntRuntimeException ert) { throw ert; diff --git a/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/ICdsStorageManager.java b/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/ICdsStorageManager.java deleted file mode 100644 index 28ca2be019..0000000000 --- a/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/ICdsStorageManager.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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 - * 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 org.entando.entando.ent.exception.EntException; - -public interface ICdsStorageManager { - - public CdsDiskInfo getDiskInfo() throws EntException; - -} 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 b4d5dd74ae..d8b3662b7b 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 @@ -13,6 +13,8 @@ */ package org.entando.entando.plugins.jpcds.aps.system.storage; +import org.entando.entando.aps.system.services.storage.model.DiskInfoDto; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -315,23 +317,34 @@ void shouldManageErrorWhenDeleteFile() { ex.getMessage()); Mockito.verify(restTemplate, Mockito.times(2)) .exchange(eq(urlUnAuth),eq(HttpMethod.DELETE),any(), eq(new ParameterizedTypeReference>(){})); - } @Test void shouldRetrieveDiskInfo() throws Exception { + Mockito.when(cdsConfiguration.getKcAuthUrl()).thenReturn("http://auth.server.com/auth"); + Mockito.when(cdsConfiguration.getKcRealm()).thenReturn("primary"); + Mockito.when(cdsConfiguration.getKcClientId()).thenReturn("sec1"); + Mockito.when(cdsConfiguration.getKcClientSecret()).thenReturn("sec"); String url = "http://cds-kube-service:8081/mytenant/api/v1/utils/diskinfo"; - URI urlUnAuth = URI.create(url); - Mockito.when(restTemplate.getForObject(eq(urlUnAuth), eq(CdsDiskInfo.class))).thenThrow(new HttpClientErrorException(HttpStatus.UNAUTHORIZED)); - Exception ex = assertThrows(EntRuntimeException.class,() -> cdsRemoteCaller.getDiskInfo(urlUnAuth)); + URI urlDiskInfo = URI.create(url); + URI authPrimary = URI.create("http://auth.server.com/auth/realms/primary/protocol/openid-connect/token"); + Mockito.when(restTemplate.exchange(eq(authPrimary.toString()), + eq(HttpMethod.POST), + any(), + eq(new ParameterizedTypeReference>(){}))).thenReturn( + ResponseEntity.status(HttpStatus.OK).body(Map.of(OAuth2AccessToken.ACCESS_TOKEN,"entando"))); + Mockito.when(restTemplate.exchange(eq(urlDiskInfo),eq(HttpMethod.GET),any(), eq(new ParameterizedTypeReference(){}))) + .thenThrow(new HttpClientErrorException(HttpStatus.UNAUTHORIZED)); + Exception ex = assertThrows(EntRuntimeException.class,() -> cdsRemoteCaller.getDiskInfo(urlDiskInfo, Optional.empty())); Assertions.assertEquals( String.format("Invalid operation 'GET', response status:'401 UNAUTHORIZED' for url:'%s'", url), ex.getMessage()); - CdsDiskInfo diskInfo = new CdsDiskInfo(); + DiskInfoDto diskInfo = new DiskInfoDto(); diskInfo.setTotalSize(10l); diskInfo.setUsedSpace(5l); - Mockito.when(restTemplate.getForObject(eq(urlUnAuth), eq(CdsDiskInfo.class))).thenReturn(diskInfo); - Optional extractedOpt = this.cdsRemoteCaller.getDiskInfo(urlUnAuth); + Mockito.when(restTemplate.exchange(eq(urlDiskInfo),eq(HttpMethod.GET),any(), eq(new ParameterizedTypeReference(){}))) + .thenReturn(ResponseEntity.ok().body(diskInfo)); + Optional extractedOpt = this.cdsRemoteCaller.getDiskInfo(urlDiskInfo, Optional.empty()); Assertions.assertTrue(extractedOpt.isPresent()); Assertions.assertEquals(10l, extractedOpt.get().getTotalSize()); } @@ -339,7 +352,7 @@ void shouldRetrieveDiskInfo() throws Exception { @Test void shouldDeserializeDiskInfo() throws Exception { String response = "{\"total_size\": 222,\"used_space\": 111}"; - CdsDiskInfo info = new ObjectMapper().readValue(response, CdsDiskInfo.class); + DiskInfoDto info = new ObjectMapper().readValue(response, DiskInfoDto.class); Assertions.assertEquals(222l, info.getTotalSize()); Assertions.assertEquals(111l, info.getUsedSpace()); } 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 5b2fd7f6b2..539b9daf17 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 @@ -13,6 +13,8 @@ */ package org.entando.entando.plugins.jpcds.aps.system.storage; +import org.entando.entando.aps.system.services.storage.model.DiskInfoDto; + import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -166,11 +168,11 @@ void shouldRetrieveDiskInfo() throws Exception { TenantConfig tc = new TenantConfig(configMap); Mockito.when(tenantManager.getConfig("my-tenant")).thenReturn(Optional.ofNullable(tc)); ApsTenantApplicationUtils.setTenant("my-tenant"); - Mockito.when(this.cdsRemoteCaller.getDiskInfo(Mockito.any(URI.class))).thenReturn(Optional.of(Mockito.mock(CdsDiskInfo.class))); - CdsDiskInfo diskInfo = this.cdsStorageManager.getDiskInfo(); + Mockito.when(this.cdsRemoteCaller.getDiskInfo(Mockito.any(URI.class), Mockito.any())).thenReturn(Optional.of(Mockito.mock(DiskInfoDto.class))); + DiskInfoDto diskInfo = this.cdsStorageManager.getDiskInfo(); Assertions.assertThat(diskInfo).isNotNull(); ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(URI.class); - Mockito.verify(cdsRemoteCaller).getDiskInfo(uriCaptor.capture()); + Mockito.verify(cdsRemoteCaller).getDiskInfo(uriCaptor.capture(), Mockito.any()); String expectedUrl = "http://tenant-cds-kube-service:8081/api/v1/utils/diskinfo"; Assertions.assertThat(uriCaptor.getValue().toString()).isEqualTo(expectedUrl); } @@ -186,11 +188,11 @@ void shouldReturnErrorCallingDiskInfo() throws Exception { TenantConfig tc = new TenantConfig(configMap); Mockito.when(tenantManager.getConfig("my-tenant")).thenReturn(Optional.ofNullable(tc)); ApsTenantApplicationUtils.setTenant("my-tenant"); - Mockito.when(this.cdsRemoteCaller.getDiskInfo(Mockito.any(URI.class))).thenThrow(new EntRuntimeException("Error")); + Mockito.when(this.cdsRemoteCaller.getDiskInfo(Mockito.any(URI.class), Mockito.any())).thenThrow(new EntRuntimeException("Error")); Exception ex = assertThrows(Exception.class,() -> this.cdsStorageManager.getDiskInfo()); Assertions.assertThat(ex).isInstanceOf(EntRuntimeException.class); - Mockito.when(this.cdsRemoteCaller.getDiskInfo(Mockito.any(URI.class))).thenReturn(Optional.empty()); + Mockito.when(this.cdsRemoteCaller.getDiskInfo(Mockito.any(URI.class), Mockito.any())).thenReturn(Optional.empty()); Exception otherEx = assertThrows(Exception.class,() -> this.cdsStorageManager.getDiskInfo()); Assertions.assertThat(otherEx).isInstanceOf(EntResourceNotFoundException.class); String expectedUrl = "http://tenantx-cds-kube-service:8081/api/v1/utils/diskinfo"; 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..c89fd664f5 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,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.Serializable; -import java.util.function.BiFunction; +import org.entando.entando.aps.system.services.storage.model.DiskInfoDto; /** * @author E.Santoboni @@ -60,5 +60,7 @@ public interface IStorageManager extends Serializable { public void editFile(String subPath, boolean isProtectedResource, InputStream is) throws EntException; public String createFullPath(String subPath, boolean isProtectedResource) throws EntException; + + public DiskInfoDto getDiskInfo() 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 9c8fae05ce..bc0573edc5 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 @@ -30,6 +30,8 @@ import static org.entando.entando.aps.system.services.storage.StorageManagerUtil.isSamePath; +import org.entando.entando.aps.system.services.storage.model.DiskInfoDto; + @Service("StorageManager") @CdsActive(false) public class LocalStorageManager implements IStorageManager, InitializingBean { @@ -415,6 +417,11 @@ public BasicFileAttributeView getAttributes(String subPath, boolean isProtectedR return new BasicFileAttributeView(file); } } + + @Override + public DiskInfoDto getDiskInfo() throws EntException { + throw new UnsupportedOperationException("Not supported for Local Storage"); + } protected String getBaseURL() { return baseURL; @@ -470,4 +477,5 @@ public void handleStorageFunctionProblem(Exception e, String msg) { throw (EntRuntimeException) e; } } + } diff --git a/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsDiskInfo.java b/engine/src/main/java/org/entando/entando/aps/system/services/storage/model/DiskInfoDto.java similarity index 88% rename from cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsDiskInfo.java rename to engine/src/main/java/org/entando/entando/aps/system/services/storage/model/DiskInfoDto.java index 6e91c93f3c..ee29ad436c 100644 --- a/cds-plugin/src/main/java/org/entando/entando/plugins/jpcds/aps/system/storage/CdsDiskInfo.java +++ b/engine/src/main/java/org/entando/entando/aps/system/services/storage/model/DiskInfoDto.java @@ -11,7 +11,7 @@ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ -package org.entando.entando.plugins.jpcds.aps.system.storage; +package org.entando.entando.aps.system.services.storage.model; import com.fasterxml.jackson.annotation.JsonProperty; import java.io.Serializable; @@ -19,7 +19,7 @@ import lombok.Setter; @Getter@Setter -public class CdsDiskInfo implements Serializable { +public class DiskInfoDto implements Serializable { @JsonProperty(value = "total_size") private long totalSize; diff --git a/engine/src/main/java/org/entando/entando/web/system/CurrentSystemController.java b/engine/src/main/java/org/entando/entando/web/system/CurrentSystemController.java index be8c095681..9dfc9d7765 100644 --- a/engine/src/main/java/org/entando/entando/web/system/CurrentSystemController.java +++ b/engine/src/main/java/org/entando/entando/web/system/CurrentSystemController.java @@ -14,22 +14,16 @@ package org.entando.entando.web.system; import com.agiletec.aps.system.services.role.Permission; -import java.util.HashMap; -import java.util.Map; import lombok.extern.slf4j.Slf4j; -import org.entando.entando.aps.system.init.IComponentManager; import org.entando.entando.aps.system.services.systemconfiguration.ISystemConfigurationService; -import org.entando.entando.aps.system.services.systemconfiguration.SystemConfigurationService; import org.entando.entando.web.common.annotation.RestAccessControl; import org.entando.entando.web.common.model.SimpleRestResponse; import org.entando.entando.web.system.model.SystemConfigurationDto; -import org.entando.entando.web.tenant.model.TenantDto; 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.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @Slf4j 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..a0ca25b20d 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 @@ -30,6 +30,7 @@ import java.util.List; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.Functions; +import org.entando.entando.aps.system.services.storage.model.DiskInfoDto; import org.entando.entando.ent.exception.EntException; import org.entando.entando.ent.exception.EntRuntimeException; import org.entando.entando.ent.util.EntLogging.EntLogFactory; @@ -132,7 +133,6 @@ void testListAttributes_3() throws Throwable { @Test void testGetStream_ShouldBlockPathTraversal() throws Throwable { String testFilePath = "../testfolder/test.txt"; - try { localStorageManager.getStream(testFilePath, false); } catch (EntRuntimeException e) { @@ -255,9 +255,23 @@ void testCreateDeleteDirectory_ShouldBlockPathTraversals() throws Throwable { this.localStorageManager.deleteDirectory("target/mydir", false); this.localStorageManager.deleteDirectory("target", false); } - + + @Test + void testGetDiskInfo() throws Throwable { + DiskInfoDto dto = null; + try { + dto = this.localStorageManager.getDiskInfo(); + Assertions.fail(); + } catch (Exception e) { + Assertions.assertTrue(UnsupportedOperationException.class.isAssignableFrom(e.getClass())); + Assertions.assertEquals("Not supported for Local Storage", e.getMessage()); + } finally { + assertNull(dto); + } + } + @BeforeEach - private void init() throws Exception { + void init() throws Exception { try { localStorageManager = (IStorageManager) this.getApplicationContext().getBean(SystemConstants.STORAGE_MANAGER); } catch (Throwable t) { From a10ce4f9f1473694fc1d0b2d77060210e5a1510a Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Tue, 20 Feb 2024 14:55:43 +0100 Subject: [PATCH 58/82] ENG-5473: Added new method into ResourceDao to filter resources for content relactions --- .../services/resource/IResourceDAO.java | 6 ++ .../system/services/resource/ResourceDAO.java | 57 ++++++++++++++----- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/IResourceDAO.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/IResourceDAO.java index 8b94fdbea7..bad1bcd233 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/IResourceDAO.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/IResourceDAO.java @@ -92,11 +92,17 @@ public interface IResourceDAO { public List searchResourcesId(FieldSearchFilter[] filters, String categoryCode, Collection groupCodes); public List searchResourcesId(FieldSearchFilter[] filters, List categories); + + public List searchResourcesId(FieldSearchFilter[] filters, List categories, Boolean referenced); public List searchResourcesId(FieldSearchFilter[] filters, List categories, Collection groupCodes); + public List searchResourcesId(FieldSearchFilter[] filters, List categories, Collection groupCodes, Boolean referenced); + public Integer countResources(FieldSearchFilter[] filters, List categories, Collection groupCodes); + public Integer countResources(FieldSearchFilter[] filters, List categories, Collection groupCodes, Boolean referenced); + /** * Carica un record di risorse in funzione dell'idRisorsa. Questo record è * necessario per l'estrazione della risorse in oggetto tipo diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceDAO.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceDAO.java index 00e6d41760..1c6d7305c3 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceDAO.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceDAO.java @@ -51,31 +51,34 @@ public class ResourceDAO extends AbstractSearcherDAO implements IResourceDAO { private ICategoryManager categoryManager; - private final String LOAD_RESOURCE_VO + private static final String LOAD_RESOURCE_VO = "SELECT restype, descr, maingroup, resourcexml, masterfilename, creationdate, lastmodified, owner, folderpath, correlationcode FROM resources WHERE resid = ? "; private static final String LOAD_RESOURCE_VO_BY_CODE = "SELECT resid, restype, descr, maingroup, resourcexml, masterfilename, creationdate, lastmodified, owner, folderpath, correlationcode FROM resources WHERE correlationcode = ? "; - private final String ADD_RESOURCE + private static final String ADD_RESOURCE = "INSERT INTO resources (resid, restype, descr, maingroup, resourcexml, masterfilename, creationdate, lastmodified, owner, folderpath, correlationcode) " + "VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ?, ?, ?)"; - private final String UPDATE_RESOURCE + private static final String UPDATE_RESOURCE = "UPDATE resources SET restype = ? , descr = ? , maingroup = ? , resourcexml = ? , masterfilename = ? , lastmodified = ?, folderpath = ? WHERE resid = ? "; - private final String DELETE_CONTENTS_REFERENCE + private static final String DELETE_CONTENTS_REFERENCE = "DELETE FROM contentrelations WHERE refresource = ? "; - private final String DELETE_RESOURCE + private static final String DELETE_RESOURCE = "DELETE FROM resources WHERE resid = ? "; - private final String ADD_RESOURCE_REL_RECORD + private static final String ADD_RESOURCE_REL_RECORD = "INSERT INTO resourcerelations (resid, refcategory) VALUES ( ? , ? )"; - private final String DELETE_RESOURCE_REL_RECORD + private static final String DELETE_RESOURCE_REL_RECORD = "DELETE FROM resourcerelations WHERE resid = ? "; + private static final String SELECT_ALL_REL_RECORD + = "SELECT resid FROM resourcerelations WHERE resid IS NOT NULL"; + protected ICategoryManager getCategoryManager() { return categoryManager; } @@ -305,12 +308,22 @@ public List searchResourcesId(FieldSearchFilter[] filters, String catego @Override public List searchResourcesId(FieldSearchFilter[] filters, List categories, Collection groupCodes) { + return this.searchResourcesId(filters, categories, groupCodes, null); + } + + @Override + public List searchResourcesId(FieldSearchFilter[] filters, List categories, Collection groupCodes, Boolean referenced) { filters = this.addGroupFilter(filters, groupCodes); - return this.searchResourcesId(filters, categories); + return this.searchResourcesId(filters, categories, referenced); } - + @Override public Integer countResources(FieldSearchFilter[] filters, List categories, Collection groupCodes) { + return this.countResources(filters, categories, groupCodes, null); + } + + @Override + public Integer countResources(FieldSearchFilter[] filters, List categories, Collection groupCodes, Boolean referenced) { Connection conn = null; int count = 0; PreparedStatement stat = null; @@ -318,7 +331,7 @@ public Integer countResources(FieldSearchFilter[] filters, List categori try { conn = this.getConnection(); filters = this.addGroupFilter(filters, groupCodes); - stat = this.buildStatement(filters, categories, true, conn); + stat = this.buildStatement(filters, categories, true, referenced, conn); result = stat.executeQuery(); if (result.next()) { count = result.getInt(1); @@ -334,13 +347,19 @@ public Integer countResources(FieldSearchFilter[] filters, List categori @Override public List searchResourcesId(FieldSearchFilter[] filters, List categories) { + Boolean referenced = null; + return this.searchResourcesId(filters, categories, referenced); + } + + @Override + public List searchResourcesId(FieldSearchFilter[] filters, List categories, Boolean referenced) { Connection conn = null; List resources = new ArrayList<>(); PreparedStatement stat = null; ResultSet res = null; try { conn = this.getConnection(); - stat = this.buildStatement(filters, categories, false, conn); + stat = this.buildStatement(filters, categories, false, referenced, conn); res = stat.executeQuery(); while (res.next()) { String id = res.getString(this.getMasterTableIdFieldName()); @@ -357,8 +376,9 @@ public List searchResourcesId(FieldSearchFilter[] filters, List return resources; } - private PreparedStatement buildStatement(FieldSearchFilter[] filters, List categories, boolean isCount, Connection conn) { - String query = this.createQueryString(filters, categories, isCount); + private PreparedStatement buildStatement(FieldSearchFilter[] filters, + List categories, boolean isCount, Boolean referenced, Connection conn) { + String query = this.createQueryString(filters, categories, isCount, referenced); PreparedStatement stat = null; try { stat = conn.prepareStatement(query); @@ -376,9 +396,16 @@ private PreparedStatement buildStatement(FieldSearchFilter[] filters, List categories, boolean isCount) { + private String createQueryString(FieldSearchFilter[] filters, + List categories, boolean isCount, Boolean referenced) { StringBuffer query = this.createBaseQueryBlock(filters, false, isCount, categories); - this.appendMetadataFieldFilterQueryBlocks(filters, query, false); + boolean hasAppendWhereClause = this.appendMetadataFieldFilterQueryBlocks(filters, query, false); + if (null != referenced) { + super.verifyWhereClauseAppend(query, hasAppendWhereClause); + query.append(" ").append(this.getMasterTableIdFieldName()) + .append(((referenced) ? " IN " : " NOT IN ")) + .append("(").append(SELECT_ALL_REL_RECORD).append(") "); + } if (!isCount) { super.appendOrderQueryBlocks(filters, query, false); this.appendLimitQueryBlock(filters, query); From e15582e213605118c32c1ad7851f5728769e6117 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Tue, 27 Feb 2024 17:52:26 +0100 Subject: [PATCH 59/82] ENG-5473: Fix Resource manager and action --- .../services/resource/IResourceManager.java | 5 ++- .../system/services/resource/ResourceDAO.java | 4 +-- .../services/resource/ResourceManager.java | 15 ++++++--- .../resource/ResourceFinderAction.java | 27 +++++++-------- .../apsadmin/resource/package_en.properties | 3 ++ .../apsadmin/resource/package_it.properties | 5 +++ .../jsp/resource/inc/resource_searchForm.jsp | 24 +++++++++++++- .../ResourceManagerIntegrationTest.java | 22 ++++++++----- .../resource/TestResourceFinderAction.java | 33 +++++++++++++++++++ 9 files changed, 105 insertions(+), 33 deletions(-) diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/IResourceManager.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/IResourceManager.java index 5ae6e7d46f..e441eddef3 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/IResourceManager.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/IResourceManager.java @@ -187,6 +187,8 @@ public List searchResourcesId(FieldSearchFilter[] filters, public List searchResourcesId(FieldSearchFilter[] filters, List categories) throws EntException; public SearcherDaoPaginatedResult getPaginatedResourcesId(FieldSearchFilter[] filters, List categories, Collection userGroupCodes) throws EntException; + + public SearcherDaoPaginatedResult getPaginatedResourcesId(FieldSearchFilter[] filters, List categories, Collection userGroupCodes, Boolean referenced) throws EntException; /** * Restituisce la risorsa con l'id specificato. @@ -200,9 +202,6 @@ public List searchResourcesId(FieldSearchFilter[] filters, public ResourceInterface loadResource(String id, String correlationalCode) throws EntException; - /** - * Check that a resource exists by or or correlotionCode - */ boolean exists(String id, String correlationCode); /** diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceDAO.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceDAO.java index 1c6d7305c3..a60455db18 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceDAO.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceDAO.java @@ -77,7 +77,7 @@ public class ResourceDAO extends AbstractSearcherDAO implements IResourceDAO { = "DELETE FROM resourcerelations WHERE resid = ? "; private static final String SELECT_ALL_REL_RECORD - = "SELECT resid FROM resourcerelations WHERE resid IS NOT NULL"; + = "SELECT refresource FROM contentrelations WHERE refresource IS NOT NULL"; protected ICategoryManager getCategoryManager() { return categoryManager; @@ -402,7 +402,7 @@ private String createQueryString(FieldSearchFilter[] filters, boolean hasAppendWhereClause = this.appendMetadataFieldFilterQueryBlocks(filters, query, false); if (null != referenced) { super.verifyWhereClauseAppend(query, hasAppendWhereClause); - query.append(" ").append(this.getMasterTableIdFieldName()) + query.append(" ").append(this.getMasterTableName()).append(".").append(this.getMasterTableIdFieldName()) .append(((referenced) ? " IN " : " NOT IN ")) .append("(").append(SELECT_ALL_REL_RECORD).append(") "); } 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 4e80457f7d..03f11cc496 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 @@ -402,14 +402,20 @@ public List searchResourcesId(FieldSearchFilter[] filters, List } return resourcesId; } - + @Override public SearcherDaoPaginatedResult getPaginatedResourcesId(FieldSearchFilter[] filters, List categories, Collection userGroupCodes) throws EntException { + return this.getPaginatedResourcesId(filters, categories, userGroupCodes, null); + } + + @Override + public SearcherDaoPaginatedResult getPaginatedResourcesId(FieldSearchFilter[] filters, + List categories, Collection userGroupCodes, Boolean referenced) throws EntException { SearcherDaoPaginatedResult pagedResult = null; try { - int count = this.getResourceDAO().countResources(filters, categories, userGroupCodes); - List resourcesId = this.getResourceDAO().searchResourcesId(filters, categories, userGroupCodes); + int count = this.getResourceDAO().countResources(filters, categories, userGroupCodes, referenced); + List resourcesId = this.getResourceDAO().searchResourcesId(filters, categories, userGroupCodes, referenced); pagedResult = new SearcherDaoPaginatedResult<>(count, resourcesId); } catch (Throwable t) { logger.error("Error searching paginated resources id", t); @@ -449,8 +455,7 @@ public ResourceInterface loadResource(String id) throws EntException { public ResourceInterface loadResource(String id, String correlationCode) throws EntException { ResourceInterface resource = null; try { - ResourceRecordVO resourceVo = loadResourceVo(id, - correlationCode); + ResourceRecordVO resourceVo = loadResourceVo(id, correlationCode); if (null != resourceVo) { resource = this.createResource(resourceVo); resource.setMasterFileName(resourceVo.getMasterFileName()); 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..4459e64d85 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 @@ -30,6 +30,9 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; +import lombok.Getter; +import lombok.Setter; import org.apache.commons.beanutils.BeanComparator; import org.apache.commons.lang3.ArrayUtils; @@ -45,11 +48,12 @@ public class ResourceFinderAction extends AbstractResourceAction { private static final EntLogger logger = EntLogFactory.getSanitizedLogger(ResourceFinderAction.class); - + private String text; private String fileName; private String ownerGroupName; private String categoryCode; + private String referenced; private ResourceIconUtil resourceIconUtil; private IImageDimensionReader imageDimensionManager; private boolean openCollapsed = false; @@ -82,7 +86,8 @@ public SearcherDaoPaginatedResult getPaginatedResourcesId(Integer limit) filters = ArrayUtils.add(filters, this.getPagerFilter(limit)); } List categories = (StringUtils.isBlank(this.getCategoryCode())) ? null : Arrays.asList(this.getCategoryCode()); - result = this.getResourceManager().getPaginatedResourcesId(filters, categories, groupCodesForSearch); + Boolean getReferenced = Optional.ofNullable(this.getReferenced()).map(r -> r.equalsIgnoreCase("yes")).orElse(null); + result = this.getResourceManager().getPaginatedResourcesId(filters, categories, groupCodesForSearch, getReferenced); } catch (Throwable t) { logger.error("error in getPaginateResourcesId", t); throw new RuntimeException("error in getPaginateResourcesId", t); @@ -204,7 +209,6 @@ public List getImageDimensions() { public String getText() { return text; } - public void setText(String text) { this.text = text; } @@ -212,7 +216,6 @@ public void setText(String text) { public String getFileName() { return fileName; } - public void setFileName(String fileName) { this.fileName = fileName; } @@ -220,18 +223,23 @@ public void setFileName(String fileName) { public String getOwnerGroupName() { return ownerGroupName; } - public void setOwnerGroupName(String ownerGroupName) { this.ownerGroupName = ownerGroupName; } + public String getReferenced() { + return referenced; + } + public void setReferenced(String referenced) { + this.referenced = referenced; + } + public String getCategoryCode() { if (this.categoryCode != null && this.getCategoryManager().getRoot().getCode().equals(this.categoryCode)) { this.categoryCode = null; } return categoryCode; } - public void setCategoryCode(String categoryCode) { this.categoryCode = categoryCode; } @@ -239,7 +247,6 @@ public void setCategoryCode(String categoryCode) { protected ResourceIconUtil getResourceIconUtil() { return resourceIconUtil; } - public void setResourceIconUtil(ResourceIconUtil resourceIconUtil) { this.resourceIconUtil = resourceIconUtil; } @@ -247,7 +254,6 @@ public void setResourceIconUtil(ResourceIconUtil resourceIconUtil) { protected IImageDimensionReader getImageDimensionManager() { return imageDimensionManager; } - public void setImageDimensionManager(IImageDimensionReader imageDimensionManager) { this.imageDimensionManager = imageDimensionManager; } @@ -262,7 +268,6 @@ public boolean isOpenCollapsed() { || !StringUtils.isBlank(this.getFileName()) || !StringUtils.isBlank(this.getOwnerGroupName())); } - public void setOpenCollapsed(boolean openCollapsed) { this.openCollapsed = openCollapsed; } @@ -274,7 +279,6 @@ protected List getGroupCodesForSearch() { public String getLastOrder() { return lastOrder; } - public void setLastOrder(String order) { this.lastOrder = order; } @@ -282,7 +286,6 @@ public void setLastOrder(String order) { public String getOrder() { return order; } - public void setOrder(String order) { this.order = order; } @@ -290,7 +293,6 @@ public void setOrder(String order) { public String getLastGroupBy() { return lastGroupBy; } - public void setLastGroupBy(String lastGroupBy) { this.lastGroupBy = lastGroupBy; } @@ -298,7 +300,6 @@ public void setLastGroupBy(String lastGroupBy) { public String getGroupBy() { return groupBy; } - public void setGroupBy(String groupBy) { this.groupBy = groupBy; } 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 0c9008a73e..cf96a31d62 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 @@ -63,6 +63,9 @@ label.category.tree=Category Tree label.category.expandAll=Expand All label.category.collapseAll=Collapse All label.category.join=Join +label.resources.all=All +label.resources.referenced=Referenced +label.resources.notReferenced=Not Referenced note.joinThisToThat=Join to content note.resourceCategories.summary=Resource category list note.deleteResource.areYouSure=Are you sure you want to delete the resource 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 2ef7170cb1..0eb301a9c5 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 @@ -7,6 +7,7 @@ title.Image.tab=Immagini title.Image.edit=Modifica title.Image.new=Aggiungi title.resourceManagement=Risorse + title.resourceManagement.resourceNew=Nuova Risorsa title.resourceManagement.resourceEdit=Modifica Risorsa title.resourceManagement.resourceTrash=Elimina @@ -29,6 +30,7 @@ title.resourceManagement.help=I formati delle Immagini consentiti di default son 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.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 @@ -61,6 +63,9 @@ label.category.tree=Albero Categorie label.category.expandAll=Espandi Tutto label.category.collapseAll=Riduci Tutto label.category.join=Associa +label.resources.all=Tutte +label.resources.referenced=Referenziate +label.resources.notReferenced=Non Referenziate note.joinThisToThat=Associa al Contenuto note.resourceCategories.summary=Riepilogo delle Categorie associate alla Risorsa note.deleteResource.areYouSure=Sei sicuro di voler eliminare dal sistema la Risorsa 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..667682ee3c 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 @@ -33,7 +33,7 @@

in"> -
+
<%-- groups --%> @@ -95,6 +95,28 @@
+ +
+
+ + + +
+
+ diff --git a/cms-plugin/src/test/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceManagerIntegrationTest.java b/cms-plugin/src/test/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceManagerIntegrationTest.java index 22f66c4c6c..aa8c01a64e 100644 --- a/cms-plugin/src/test/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceManagerIntegrationTest.java +++ b/cms-plugin/src/test/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceManagerIntegrationTest.java @@ -235,31 +235,35 @@ private void testAddRemoveImageResources(String mainGroup) throws Throwable { assertEquals(1, resourcesId.size()); resourceListAdded = this.resourceManager.addResources(resourceList); + List addedIds = resourceListAdded.stream().map(ResourceInterface::getId).toList(); + assertEquals(4, resourceListAdded.size()); resourcesId = resourceManager.searchResourcesId(resourceType, null, categoryCodeToAdd, allowedGroups); assertEquals(5, resourcesId.size()); - FieldSearchFilter filterType = new FieldSearchFilter(IResourceManager.RESOURCE_TYPE_FILTER_KEY, resourceType, false); - FieldSearchFilter filterOrder = new FieldSearchFilter(IResourceManager.RESOURCE_DESCR_FILTER_KEY); + FieldSearchFilter filterType = new FieldSearchFilter<>(IResourceManager.RESOURCE_TYPE_FILTER_KEY, resourceType, false); + FieldSearchFilter filterOrder = new FieldSearchFilter<>(IResourceManager.RESOURCE_DESCR_FILTER_KEY); filterOrder.setOrder(FieldSearchFilter.ASC_ORDER); FieldSearchFilter pagerFilter = new FieldSearchFilter(2, 0); FieldSearchFilter[] filters = new FieldSearchFilter[]{filterType, filterOrder, pagerFilter}; SearcherDaoPaginatedResult result = this.resourceManager.getPaginatedResourcesId(filters, Arrays.asList(categoryCodeToAdd), allowedGroups); assertEquals(5, result.getCount().intValue()); assertEquals(2, result.getList().size()); - assertEquals(resourceListAdded.get(0).getId(), result.getList().get(0)); - assertEquals(resourceListAdded.get(1).getId(), result.getList().get(1)); + assertEquals(addedIds.subList(0, 2), result.getList()); + result = this.resourceManager.getPaginatedResourcesId(filters, Arrays.asList(categoryCodeToAdd), allowedGroups, Boolean.FALSE); + assertEquals(4, result.getCount().intValue()); + assertEquals(addedIds.subList(0, 2), result.getList()); // Image 1 resourcesId = resourceManager.searchResourcesId(resourceType, resDescrToAdd1, null, allowedGroups); assertEquals(resourcesId.size(), 1); resourcesId = resourceManager.searchResourcesId(resourceType, resDescrToAdd1, categoryCodeToAdd, allowedGroups); assertEquals(resourcesId.size(), 1); - ResourceInterface res1 = this.resourceManager.loadResource(resourceListAdded.get(0).getId()); + ResourceInterface res1 = this.resourceManager.loadResource(addedIds.get(0)); assertTrue(res1 instanceof ImageResource); assertEquals("entando_logo.jpg", res1.getMasterFileName()); assertEquals(res1.getCategories().size(), 1); assertEquals(res1.getDescription(), resDescrToAdd1); - assertTrue(res1.getMetadata().size() > 0); + assertTrue(!res1.getMetadata().isEmpty()); ResourceInstance instance01 = ((ImageResource) res1).getInstance(0, null); String fileNameInstance01 = instance01.getFileName(); @@ -276,7 +280,7 @@ private void testAddRemoveImageResources(String mainGroup) throws Throwable { resourcesId = resourceManager.searchResourcesId(resourceType, resDescrToAdd2, categoryCodeToAdd, allowedGroups); assertEquals(resourcesId.size(), 1); - ResourceInterface res2 = this.resourceManager.loadResource(resourceListAdded.get(1).getId()); + ResourceInterface res2 = this.resourceManager.loadResource(addedIds.get(1)); assertTrue(res2 instanceof ImageResource); assertEquals(res2.getCategories().size(), 1); assertEquals(res2.getDescription(), resDescrToAdd2); @@ -295,7 +299,7 @@ private void testAddRemoveImageResources(String mainGroup) throws Throwable { assertEquals(resourcesId.size(), 1); resourcesId = resourceManager.searchResourcesId(resourceType, resDescrToAdd3, categoryCodeToAdd, allowedGroups); assertEquals(resourcesId.size(), 1); - ResourceInterface res3 = this.resourceManager.loadResource(resourceListAdded.get(2).getId()); + ResourceInterface res3 = this.resourceManager.loadResource(addedIds.get(2)); assertEquals("entando_logo.jpg", res3.getMasterFileName()); assertEquals(res1.getMasterFileName(), res3.getMasterFileName()); assertTrue(res3 instanceof ImageResource); @@ -308,7 +312,7 @@ private void testAddRemoveImageResources(String mainGroup) throws Throwable { assertFalse(fileNameInstance03.equals(fileNameInstance01)); assertEquals("image/jpeg", instance03.getMimeType()); - ResourceInterface res4 = this.resourceManager.loadResource(resourceListAdded.get(3).getId()); + ResourceInterface res4 = this.resourceManager.loadResource(addedIds.get(3)); assertEquals("&n%ndo logo.jpg", res4.getMasterFileName()); assertTrue(res4 instanceof ImageResource); assertEquals(res4.getCategories().size(), 1); 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..13ab91ea96 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; @@ -181,6 +182,30 @@ void testSearchByGroup_2() throws Throwable { assertEquals(1, action.getResources().size()); } + @Test + void testSearchByReference() throws Throwable { + String result = this.executeSearchResource("admin", "Image", null, null, null, null, null); + assertEquals(Action.SUCCESS, result); + ResourceFinderAction action = (ResourceFinderAction) this.getAction(); + SearcherDaoPaginatedResult paginatedResult = action.getPaginatedResourcesId(10); + List expected = List.of("82", "22", "44"); + assertEquals(expected.size(), paginatedResult.getCount()); + assertEquals(expected, paginatedResult.getList()); + + result = this.executeSearchResource("admin", "Image", null, null, null, null, "yes"); + assertEquals(Action.SUCCESS, result); + paginatedResult = ((ResourceFinderAction) this.getAction()).getPaginatedResourcesId(10); + assertEquals(1, paginatedResult.getCount()); + assertEquals("44", paginatedResult.getList().get(0)); + + result = this.executeSearchResource("admin", "Image", null, null, null, null, "no"); + assertEquals(Action.SUCCESS, result); + paginatedResult = ((ResourceFinderAction) this.getAction()).getPaginatedResourcesId(10); + expected = List.of("82", "22"); + assertEquals(expected.size(), paginatedResult.getCount()); + assertEquals(expected, paginatedResult.getList()); + } + @Test void testSearchWithOrder_1() throws Throwable { List expected = Arrays.asList(new String[]{"22", "44", "82"}); @@ -218,6 +243,11 @@ private void executeTestSearchWithOrder_1(List expected, String field) t private String executeSearchResource(String username, String resourceTypeCode, String text, String ownerGroupName, String fileName, String categoryCode) throws Throwable { + return this.executeSearchResource(username, resourceTypeCode, text, ownerGroupName, fileName, categoryCode, null); + } + + private String executeSearchResource(String username, String resourceTypeCode, + String text, String ownerGroupName, String fileName, String categoryCode, String referenced) throws Throwable { this.setUserOnSession(username); this.initAction("/do/jacms/Resource", "search"); this.addParameter("resourceTypeCode", resourceTypeCode); @@ -225,6 +255,9 @@ private String executeSearchResource(String username, String resourceTypeCode, this.addParameter("fileName", fileName); this.addParameter("ownerGroupName", ownerGroupName); this.addParameter("categoryCode", categoryCode); + if (null != referenced) { + this.addParameter("referenced", referenced); + } return this.executeAction(); } From 42d0cd21cca7196100f6b04538364d2e6e78915e Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Wed, 28 Feb 2024 10:38:38 +0100 Subject: [PATCH 60/82] ENG-5473: Fix Sonar issues --- .../jpcds/aps/system/storage/CdsRemoteCaller.java | 10 ++-------- .../jpcds/aps/system/storage/CdsRemoteCallerTest.java | 4 +++- .../aps/system/storage/CdsStorageManagerTest.java | 2 +- .../aps/system/services/resource/ResourceDAO.java | 2 +- .../jacms/apsadmin/resource/ResourceFinderAction.java | 2 -- 5 files changed, 7 insertions(+), 13 deletions(-) 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 c637a59c96..f013272843 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 @@ -209,14 +209,8 @@ public Optional getFile(URI url, Optional co } public Optional getDiskInfo(URI url, Optional config) { - logger.error("Trying to call GET (diskinfo) on url:'{}' and is config tenant empty:'{}'", url, config.isEmpty()); - Optional infos; - try { - infos = this.executeGetCall(url, null, config, false, new ParameterizedTypeReference(){}); - } catch (HttpClientErrorException e) { - throw buildExceptionWithMessage("GET", e.getStatusCode(), url.toString()); - } - return infos; + logger.debug("Trying to call GET (diskinfo) on url:'{}' and is config tenant empty:'{}'", url, config.isEmpty()); + return this.executeGetCall(url, null, config, false, new ParameterizedTypeReference(){}); } private Optional executeGetCall(URI url, List acceptableMediaTypes, Optional config, 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 d8b3662b7b..440f2069ce 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 @@ -335,7 +335,9 @@ void shouldRetrieveDiskInfo() throws Exception { ResponseEntity.status(HttpStatus.OK).body(Map.of(OAuth2AccessToken.ACCESS_TOKEN,"entando"))); Mockito.when(restTemplate.exchange(eq(urlDiskInfo),eq(HttpMethod.GET),any(), eq(new ParameterizedTypeReference(){}))) .thenThrow(new HttpClientErrorException(HttpStatus.UNAUTHORIZED)); - Exception ex = assertThrows(EntRuntimeException.class,() -> cdsRemoteCaller.getDiskInfo(urlDiskInfo, Optional.empty())); + Optional config = Optional.empty(); + System.out.println("zzzzzzzzzzzzzzzzzzzzz"); + Exception ex = assertThrows(EntRuntimeException.class,() -> cdsRemoteCaller.getDiskInfo(urlDiskInfo, config)); Assertions.assertEquals( String.format("Invalid operation 'GET', response status:'401 UNAUTHORIZED' for url:'%s'", url), ex.getMessage()); 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 539b9daf17..de656b925f 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 @@ -174,7 +174,7 @@ void shouldRetrieveDiskInfo() throws Exception { ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(URI.class); Mockito.verify(cdsRemoteCaller).getDiskInfo(uriCaptor.capture(), Mockito.any()); String expectedUrl = "http://tenant-cds-kube-service:8081/api/v1/utils/diskinfo"; - Assertions.assertThat(uriCaptor.getValue().toString()).isEqualTo(expectedUrl); + Assertions.assertThat(uriCaptor.getValue()).hasToString(expectedUrl); } @Test diff --git a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceDAO.java b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceDAO.java index a60455db18..e1760165f3 100644 --- a/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceDAO.java +++ b/cms-plugin/src/main/java/com/agiletec/plugins/jacms/aps/system/services/resource/ResourceDAO.java @@ -403,7 +403,7 @@ private String createQueryString(FieldSearchFilter[] filters, if (null != referenced) { super.verifyWhereClauseAppend(query, hasAppendWhereClause); query.append(" ").append(this.getMasterTableName()).append(".").append(this.getMasterTableIdFieldName()) - .append(((referenced) ? " IN " : " NOT IN ")) + .append((Boolean.TRUE.equals(referenced) ? " IN " : " NOT IN ")) .append("(").append(SELECT_ALL_REL_RECORD).append(") "); } if (!isCount) { 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 4459e64d85..fda6e3c9c6 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 @@ -31,8 +31,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import lombok.Getter; -import lombok.Setter; import org.apache.commons.beanutils.BeanComparator; import org.apache.commons.lang3.ArrayUtils; From adee99a0db532286c951a8136b2004ff63a432a0 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Wed, 28 Feb 2024 12:22:45 +0100 Subject: [PATCH 61/82] ENG-5473: Fix Resource Archive UI --- .../resource/ResourceFinderAction.java | 8 ++++--- .../apsadmin/jsp/resource/attachArchive.jsp | 1 + .../apsadmin/jsp/resource/imageArchive.jsp | 1 + .../jsp/resource/inc/resource_searchForm.jsp | 8 +++---- .../resource/TestResourceFinderAction.java | 24 ++++++++++++++++++- 5 files changed, 34 insertions(+), 8 deletions(-) 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 fda6e3c9c6..d9bc2c7c6e 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 @@ -84,8 +84,9 @@ public SearcherDaoPaginatedResult getPaginatedResourcesId(Integer limit) filters = ArrayUtils.add(filters, this.getPagerFilter(limit)); } List categories = (StringUtils.isBlank(this.getCategoryCode())) ? null : Arrays.asList(this.getCategoryCode()); - Boolean getReferenced = Optional.ofNullable(this.getReferenced()).map(r -> r.equalsIgnoreCase("yes")).orElse(null); - result = this.getResourceManager().getPaginatedResourcesId(filters, categories, groupCodesForSearch, getReferenced); + Boolean extractReferenced = Optional.ofNullable(this.getReferenced()) + .filter(r -> !r.isBlank() && !r.equalsIgnoreCase("all")).map(r -> r.equalsIgnoreCase("yes")).orElse(null); + result = this.getResourceManager().getPaginatedResourcesId(filters, categories, groupCodesForSearch, extractReferenced); } catch (Throwable t) { logger.error("error in getPaginateResourcesId", t); throw new RuntimeException("error in getPaginateResourcesId", t); @@ -264,7 +265,8 @@ public boolean isOpenCollapsed() { } return (this.openCollapsed || hasFilterByCat || !StringUtils.isBlank(this.getFileName()) - || !StringUtils.isBlank(this.getOwnerGroupName())); + || !StringUtils.isBlank(this.getOwnerGroupName()) + || !StringUtils.isBlank(this.getReferenced())); } public void setOpenCollapsed(boolean openCollapsed) { this.openCollapsed = openCollapsed; 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..282cb56840 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 @@ -168,6 +168,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..8e5cdb1645 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 @@ -180,6 +180,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 667682ee3c..4d4301806c 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 @@ -99,9 +99,9 @@
-
+
+
+
+ +
+
+ + + + + + + + " id="" + name="" /> + " id="ch_" class="bootstrap-switch" + checked="checked" /> + +
+
+
From ee7c2550414c7ba63fd63b7f2415c6a2a720d20d Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Fri, 15 Mar 2024 14:36:35 +0100 Subject: [PATCH 63/82] ENG-5493: New asset search parameter: resourceId --- .../resource/ResourceFinderAction.java | 27 +++++++++++++------ .../apsadmin/resource/package_en.properties | 1 + .../apsadmin/resource/package_it.properties | 2 ++ .../jsp/resource/inc/resource_searchForm.jsp | 8 ++++++ .../resource/TestResourceFinderAction.java | 23 ++++++++++++++++ 5 files changed, 53 insertions(+), 8 deletions(-) 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..e81af8aa18 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 resourceId; 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.getResourceId())) { + FieldSearchFilter idFilter = new FieldSearchFilter<>(IResourceManager.RESOURCE_ID_FILTER_KEY, this.getResourceId(), 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 getResourceId() { + return resourceId; + } + + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + public String getFileName() { return fileName; } 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 0c9008a73e..a9daaf7a28 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 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 2ef7170cb1..cf7a8b8f28 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 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..5da23207ed 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/apsadmin/resource/TestResourceFinderAction.java b/cms-plugin/src/test/java/com/agiletec/plugins/jacms/apsadmin/resource/TestResourceFinderAction.java index fd9281c0ab..86f2727b5f 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; /** @@ -86,6 +89,18 @@ private String executeShowList(String userName, String resourceTypeCode) throws return this.executeAction(); } + @Test + void testSearchResourcesById() throws Throwable { + String result = this.executeSearchResources("admin", "Image", Map.of("resourceId", "2")); + assertEquals(Action.SUCCESS, result); + ResourceFinderAction action = (ResourceFinderAction) this.getAction(); + Assertions.assertFalse(action.getResources().isEmpty()); + 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 +254,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(); + } + } From d500a8dd68df0ca2565738af6c34048fac2fd42e Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Tue, 19 Mar 2024 11:33:21 +0100 Subject: [PATCH 64/82] ENG-5493: Improvement of resource finder --- .../plugins/jacms/apsadmin/resource/ResourceFinderAction.java | 1 + .../jacms/apsadmin/resource/TestResourceFinderAction.java | 2 ++ 2 files changed, 3 insertions(+) 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 e81af8aa18..3351f7bae2 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 @@ -271,6 +271,7 @@ public boolean isOpenCollapsed() { } return (this.openCollapsed || hasFilterByCat || !StringUtils.isBlank(this.getFileName()) + || !StringUtils.isBlank(this.getResourceId()) || !StringUtils.isBlank(this.getOwnerGroupName())); } 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 86f2727b5f..3eb2e62c00 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 @@ -40,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()); @@ -95,6 +96,7 @@ void testSearchResourcesById() throws Throwable { 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")); From 9a76eef837011a6e4986990340451a53cad191a9 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Wed, 20 Mar 2024 14:27:39 +0100 Subject: [PATCH 65/82] ENG-5494: Add check for already existing files --- .../resource/MultipleResourceAction.java | 36 ++++ .../apsadmin/resource/package_en.properties | 1 + .../apsadmin/resource/package_it.properties | 1 + ...ultipleResourceActionIntegrationTest.java} | 2 +- .../resource/MultipleResourceActionTest.java | 181 ++++++++++++++++++ 5 files changed, 220 insertions(+), 1 deletion(-) rename cms-plugin/src/test/java/com/agiletec/plugins/jacms/apsadmin/resource/{TestMultipleResourceAction.java => MultipleResourceActionIntegrationTest.java} (99%) create mode 100644 cms-plugin/src/test/java/com/agiletec/plugins/jacms/apsadmin/resource/MultipleResourceActionTest.java 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 91e56c1c1c..cf4d9d8505 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 @@ -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; @@ -25,6 +27,7 @@ import java.io.*; import java.util.*; +import javax.servlet.http.HttpServletRequest; public class MultipleResourceAction extends ResourceAction { @@ -47,7 +50,9 @@ 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 { @@ -57,6 +62,7 @@ public void validate() { addFieldErrors(validateFileUploadNames()); addFieldErrors(validateFileUploadContentType()); } + addFieldErrors(validateCheckDuplicateFile()); } private void addFieldErrors(List fieldErrors) { @@ -148,6 +154,36 @@ private List validateFileUploadContentType() { } return errors; } + + private List validateCheckDuplicateFile() { + List errors = new ArrayList<>(); + try { + if (StringUtils.isBlank(this.getMainGroup())) { + return errors; + } + FieldSearchFilter groupFilter = new FieldSearchFilter<>(IResourceManager.RESOURCE_OWNER_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; + } @Override public String edit() { 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 a9daaf7a28..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 @@ -87,6 +87,7 @@ error.resource.filename.wrongCharacters=The file name contains invalid character 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 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 cf7a8b8f28..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 @@ -86,6 +86,7 @@ error.resource.filename.wrongCharacters=Il nome file contiene caratteri non cons 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/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 99% 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 56f08c4b76..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; 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..841e174f15 --- /dev/null +++ b/cms-plugin/src/test/java/com/agiletec/plugins/jacms/apsadmin/resource/MultipleResourceActionTest.java @@ -0,0 +1,181 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.agiletec.plugins.jacms.apsadmin.resource; + +import com.agiletec.aps.system.common.FieldSearchFilter; +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.ApsAdminBaseTestCase; +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) +public 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; + + + /* + protected void fetchFileDescriptions() { + fileDescriptions = fetchFields(FILE_DESCR_FIELD); + logger.debug("fetchFileDescriptions {}", fileDescriptions); + } + + protected void fetchFileUploadIDs() { + fileUploadIDs = fetchFields(FILE_UPLOAD_ID_FIELD); + logger.debug("fetchFileUploadIDs {}", fileUploadIDs); + } + + protected void fetchFileUploadContentTypes() { + fileUploadContentTypes = fetchFields(FILE_CONTENT_TYPE_FIELD); + logger.debug("fetchFileUploadContentTypes {}", fileUploadContentTypes); + } + + protected void fetchFileUploadFileNames() { + fileUploadFileNames = fetchFields(FILE_NAME_FIELD); + logger.debug("fetchFileUploadFileNames {}", fileUploadContentTypes); + + } + + public final static String FILE_DESCR_FIELD = "descr_"; + public final static String FILE_UPLOAD_ID_FIELD = "fileUploadId_"; + public final static String FILE_NAME_FIELD = "fileUploadName_"; + public final static String FILE_CONTENT_TYPE_FIELD = "fileUploadContentType_"; + + + */ + + @BeforeEach + public 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 + public 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 + public 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 + public 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 + public 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 + public 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()); + } + + +} From d2b5cdb5fed23e75579cdcd1acae271408fe23be Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Wed, 20 Mar 2024 14:52:10 +0100 Subject: [PATCH 66/82] ENG-5493: Improvement of resourceId search parameter --- .../apsadmin/resource/ResourceFinderAction.java | 16 ++++++++-------- .../apsadmin/jsp/resource/attachArchive.jsp | 1 + .../jacms/apsadmin/jsp/resource/imageArchive.jsp | 1 + .../jsp/resource/inc/resource_searchForm.jsp | 2 +- .../resource/TestResourceFinderAction.java | 2 +- 5 files changed, 12 insertions(+), 10 deletions(-) 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 3351f7bae2..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,7 +47,7 @@ public class ResourceFinderAction extends AbstractResourceAction { private static final EntLogger logger = EntLogFactory.getSanitizedLogger(ResourceFinderAction.class); private String text; - private String resourceId; + private String searchedResourceId; private String fileName; private String ownerGroupName; private String categoryCode; @@ -109,8 +109,8 @@ protected FieldSearchFilter[] createSearchFilters() { FieldSearchFilter typeCodeFilter = new FieldSearchFilter<>(IResourceManager.RESOURCE_TYPE_FILTER_KEY, this.getResourceTypeCode(), false); filters = ArrayUtils.add(filters, typeCodeFilter); } - if (StringUtils.isNotBlank(this.getResourceId())) { - FieldSearchFilter idFilter = new FieldSearchFilter<>(IResourceManager.RESOURCE_ID_FILTER_KEY, this.getResourceId(), true); + 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())) { @@ -212,12 +212,12 @@ public void setText(String text) { this.text = text; } - public String getResourceId() { - return resourceId; + public String getSearchedResourceId() { + return searchedResourceId; } - public void setResourceId(String resourceId) { - this.resourceId = resourceId; + public void setSearchedResourceId(String searchedResourceId) { + this.searchedResourceId = searchedResourceId; } public String getFileName() { @@ -271,7 +271,7 @@ public boolean isOpenCollapsed() { } return (this.openCollapsed || hasFilterByCat || !StringUtils.isBlank(this.getFileName()) - || !StringUtils.isBlank(this.getResourceId()) + || !StringUtils.isBlank(this.getSearchedResourceId()) || !StringUtils.isBlank(this.getOwnerGroupName())); } 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 5da23207ed..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 @@ -57,7 +57,7 @@

- +
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 3eb2e62c00..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 @@ -92,7 +92,7 @@ private String executeShowList(String userName, String resourceTypeCode) throws @Test void testSearchResourcesById() throws Throwable { - String result = this.executeSearchResources("admin", "Image", Map.of("resourceId", "2")); + String result = this.executeSearchResources("admin", "Image", Map.of("searchedResourceId", "2")); assertEquals(Action.SUCCESS, result); ResourceFinderAction action = (ResourceFinderAction) this.getAction(); Assertions.assertFalse(action.getResources().isEmpty()); From a7932fe798908ee9186a6b26761670de3ce555b9 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Wed, 20 Mar 2024 17:03:15 +0100 Subject: [PATCH 67/82] ENG-5494: Fix smell codes --- .../resource/MultipleResourceAction.java | 1 - .../resource/MultipleResourceActionTest.java | 61 ++++++------------- 2 files changed, 18 insertions(+), 44 deletions(-) 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 cf4d9d8505..1dee97143d 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 @@ -27,7 +27,6 @@ import java.io.*; import java.util.*; -import javax.servlet.http.HttpServletRequest; public class MultipleResourceAction extends ResourceAction { 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 index 841e174f15..8f472b7f1b 100644 --- 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 @@ -1,14 +1,21 @@ /* - * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license - * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template +* 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.common.FieldSearchFilter; 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.ApsAdminBaseTestCase; 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; @@ -27,7 +34,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -public class MultipleResourceActionTest { +class MultipleResourceActionTest { @Mock private HttpServletRequest request; @@ -48,39 +55,8 @@ public class MultipleResourceActionTest { @Spy private MultipleResourceAction action; - - /* - protected void fetchFileDescriptions() { - fileDescriptions = fetchFields(FILE_DESCR_FIELD); - logger.debug("fetchFileDescriptions {}", fileDescriptions); - } - - protected void fetchFileUploadIDs() { - fileUploadIDs = fetchFields(FILE_UPLOAD_ID_FIELD); - logger.debug("fetchFileUploadIDs {}", fileUploadIDs); - } - - protected void fetchFileUploadContentTypes() { - fileUploadContentTypes = fetchFields(FILE_CONTENT_TYPE_FIELD); - logger.debug("fetchFileUploadContentTypes {}", fileUploadContentTypes); - } - - protected void fetchFileUploadFileNames() { - fileUploadFileNames = fetchFields(FILE_NAME_FIELD); - logger.debug("fetchFileUploadFileNames {}", fileUploadContentTypes); - - } - - public final static String FILE_DESCR_FIELD = "descr_"; - public final static String FILE_UPLOAD_ID_FIELD = "fileUploadId_"; - public final static String FILE_NAME_FIELD = "fileUploadName_"; - public final static String FILE_CONTENT_TYPE_FIELD = "fileUploadContentType_"; - - - */ - @BeforeEach - public void initMocks() { + void initMocks() { MockitoAnnotations.initMocks(this); action.setServletRequest(this.request); action.setGroupManager(this.groupManager); @@ -91,7 +67,7 @@ public void initMocks() { } @Test - public void validateRightForm() { + void validateRightForm() { action.setMainGroup(Group.FREE_GROUP_NAME); action.setStrutsAction(ApsAdminSystemConstants.ADD); Map parameterMap = Map.of( @@ -106,7 +82,7 @@ public void validateRightForm() { } @Test - public void validateLongDescription() { + void validateLongDescription() { action.setMainGroup(Group.FREE_GROUP_NAME); action.setStrutsAction(ApsAdminSystemConstants.ADD); Map parameterMap = Map.of( @@ -123,7 +99,7 @@ public void validateLongDescription() { } @Test - public void validateFileAlreadyPresentOnAddExecution() throws Throwable { + void validateFileAlreadyPresentOnAddExecution() throws Throwable { action.setMainGroup(Group.FREE_GROUP_NAME); action.setStrutsAction(ApsAdminSystemConstants.ADD); Map parameterMap = Map.of( @@ -141,7 +117,7 @@ public void validateFileAlreadyPresentOnAddExecution() throws Throwable { } @Test - public void validateFileAlreadyPresentOnEditExecution() throws Throwable { + void validateFileAlreadyPresentOnEditExecution() throws Throwable { action.setMainGroup(Group.FREE_GROUP_NAME); action.setStrutsAction(ApsAdminSystemConstants.EDIT); action.setResourceId("100"); @@ -161,7 +137,7 @@ public void validateFileAlreadyPresentOnEditExecution() throws Throwable { } @Test - public void validateFileNotPresentOnEditExecution() throws Throwable { + void validateFileNotPresentOnEditExecution() throws Throwable { action.setMainGroup(Group.FREE_GROUP_NAME); action.setStrutsAction(ApsAdminSystemConstants.EDIT); action.setResourceId("120"); @@ -177,5 +153,4 @@ public void validateFileNotPresentOnEditExecution() throws Throwable { Assertions.assertTrue(action.getFieldErrors().isEmpty()); } - } From 6481f48a50650fd587b8d4c233fb57a805e76b1f Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Wed, 20 Mar 2024 17:37:15 +0100 Subject: [PATCH 68/82] ENG-5494: Added function to move file from storage --- .../aps/system/storage/CdsStorageManager.java | 24 ++++++++ .../services/storage/IStorageManager.java | 3 + .../services/storage/LocalStorageManager.java | 25 ++++++++ .../LocalStorageManagerIntegrationTest.java | 60 ++++++++++++++++++- 4 files changed, 111 insertions(+), 1 deletion(-) 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 55969f867e..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 @@ -183,6 +183,30 @@ 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)) { 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..41fcfdc1b4 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 @@ -41,6 +41,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 9c8fae05ce..ddccbeda1b 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 @@ -205,6 +205,31 @@ 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(String.format( + "Source File does not exists - path '%s' protected '%s'", + subPathSource, isProtectedResourceSource)); + return false; + } + String fullDestPath = this.createFullPath(subPathDest, isProtectedResourceDest); + File fileDest = new File(fullDestPath); + if (fileDest.exists()) { + logger.error(String.format( + "Destination already exists - path '%s' protected '%s'", + 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/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"; From cfc12430acada09d5492400bbdb39cb9e160bb61 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Tue, 26 Mar 2024 09:18:25 +0100 Subject: [PATCH 69/82] ENG-5494: Improvement of asset upgrade functionality --- .../system/storage/CdsStorageManagerTest.java | 70 ++++++++++++++++++- .../services/resource/ResourceManager.java | 26 ++++--- .../model/AbstractMonoInstanceResource.java | 9 +++ .../model/AbstractMultiInstanceResource.java | 9 ++- .../resource/model/AbstractResource.java | 45 +++++++++++- .../resource/model/ResourceInterface.java | 5 +- .../resource/MultipleResourceAction.java | 2 +- .../services/resource/ResourcesService.java | 38 ++++------ .../ResourcesControllerIntegrationTest.java | 46 ++++++------ .../services/storage/IStorageManager.java | 1 - .../services/storage/LocalStorageManager.java | 10 ++- 11 files changed, 184 insertions(+), 77 deletions(-) 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 3323a9ed93..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 @@ -152,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), @@ -558,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", 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 4e80457f7d..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 @@ -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); 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 b5b1ffca2d..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,6 +19,9 @@ 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; @@ -45,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) { 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/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 1dee97143d..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 @@ -160,7 +160,7 @@ private List validateCheckDuplicateFile() { if (StringUtils.isBlank(this.getMainGroup())) { return errors; } - FieldSearchFilter groupFilter = new FieldSearchFilter<>(IResourceManager.RESOURCE_OWNER_FILTER_KEY, this.getMainGroup(), false); + 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()){ 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 912066001a..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 @@ -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)) 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/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 41fcfdc1b4..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 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 ddccbeda1b..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 @@ -210,17 +210,15 @@ public boolean move(String subPathSource, boolean isProtectedResourceSource, String subPathDest, boolean isProtectedResourceDest) throws EntException { File file = this.getFile(subPathSource, isProtectedResourceSource); if (!file.exists()) { - logger.error(String.format( - "Source File does not exists - path '%s' protected '%s'", - subPathSource, isProtectedResourceSource)); + 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(String.format( - "Destination already exists - path '%s' protected '%s'", - subPathDest, isProtectedResourceDest)); + logger.error("Destination already exists - path '{}' protected '{}'", + subPathDest, isProtectedResourceDest); return false; } File dirDest = fileDest.getParentFile(); From 8162ee4d85250dd65c3fb0aac3450d95c9a40956 Mon Sep 17 00:00:00 2001 From: Breno de Souza Date: Thu, 28 Mar 2024 08:36:27 -0300 Subject: [PATCH 70/82] ENG-5495 Fixing ch.qos.logback:logback-classic vulnerabilities --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a0be8b24d5..96a61a2caf 100644 --- a/pom.xml +++ b/pom.xml @@ -105,7 +105,7 @@ 1.2.5 3.20.2 2.1 - 1.2.7 + 1.2.13 0.1.5 0.1.5 1.9.7 From 618a003c98ec2604be12fa7cb3e62fe47362620e Mon Sep 17 00:00:00 2001 From: Breno de Souza Date: Thu, 28 Mar 2024 10:02:30 -0300 Subject: [PATCH 71/82] ENG-5496 Fixing org.springframework:spring-web vulnerabilities --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a0be8b24d5..447255c2fe 100644 --- a/pom.xml +++ b/pom.xml @@ -81,7 +81,7 @@ jdbc:derby:memory:testPort;create=true jdbc:derby:memory:testServ;create=true - 5.3.27 + 5.3.33 5.5.7 2.5.2.RELEASE 2.5.31 From 60e337af5879c3315d0d9acba5f620713e794207 Mon Sep 17 00:00:00 2001 From: Breno de Souza Date: Fri, 29 Mar 2024 15:33:07 -0300 Subject: [PATCH 72/82] ENG-5497 Fixing struts vulnerabilities --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a0be8b24d5..7a8295c2c3 100644 --- a/pom.xml +++ b/pom.xml @@ -84,7 +84,7 @@ 5.3.27 5.5.7 2.5.2.RELEASE - 2.5.31 + 2.5.33 2.12.7 2.12.7.1 6.0.20.Final From 7278f88cd3441d85459a094718350ecd4db672f1 Mon Sep 17 00:00:00 2001 From: Breno de Souza Date: Sun, 31 Mar 2024 07:23:32 -0300 Subject: [PATCH 73/82] ENG-5499 Fixing spring-security-core vulnerability --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a0be8b24d5..73dd441047 100644 --- a/pom.xml +++ b/pom.xml @@ -82,7 +82,7 @@ jdbc:derby:memory:testServ;create=true 5.3.27 - 5.5.7 + 5.5.12 2.5.2.RELEASE 2.5.31 2.12.7 From ee13e4c4f5fdb002f6825ea704c83aaff52c565a Mon Sep 17 00:00:00 2001 From: Breno de Souza Date: Sun, 31 Mar 2024 07:28:48 -0300 Subject: [PATCH 74/82] ENG-5499 Updating to the correct version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 73dd441047..3b57b0397a 100644 --- a/pom.xml +++ b/pom.xml @@ -82,7 +82,7 @@ jdbc:derby:memory:testServ;create=true 5.3.27 - 5.5.12 + 5.7.12 2.5.2.RELEASE 2.5.31 2.12.7 From 9d986c1b198729d1d5b6af2f26de4178eec25d5e Mon Sep 17 00:00:00 2001 From: Breno de Souza Date: Sun, 31 Mar 2024 08:09:27 -0300 Subject: [PATCH 75/82] ENG-5500 Fixing com.jayway.jsonpath:json-path vulnerabilities CVE-2023-51074: https://security.snyk.io/vuln/SNYK-JAVA-COMJAYWAYJSONPATH-6140361 CVE-2023-1370: https://security.snyk.io/vuln/SNYK-JAVA-NETMINIDEV-3369748 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a0be8b24d5..86a7d6656c 100644 --- a/pom.xml +++ b/pom.xml @@ -172,7 +172,7 @@ 2.7.0 6.1.5.RELEASE 1.5.20 - 2.6.0 + 2.9.0 2.3.2 2.15.0.1 2.1.6 From 3ac82a1821ed1339d4e04eba9b20e6160824b2ca Mon Sep 17 00:00:00 2001 From: Breno de Souza Date: Sun, 31 Mar 2024 09:37:17 -0300 Subject: [PATCH 76/82] ENG-5501 Fixing io.lettuce:lettuce-core vulnerability --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a0be8b24d5..3cc2b2a940 100644 --- a/pom.xml +++ b/pom.xml @@ -170,7 +170,7 @@ 1.21 2.1.9.RELEASE 2.7.0 - 6.1.5.RELEASE + 6.2.5.RELEASE 1.5.20 2.6.0 2.3.2 From 57e34ff796b5ddbb7c7fc81a88fd988076f7c807 Mon Sep 17 00:00:00 2001 From: Breno de Souza Date: Sun, 31 Mar 2024 10:31:40 -0300 Subject: [PATCH 77/82] ENG-5501 Updating netty-common to a compatible version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3cc2b2a940..2264d78219 100644 --- a/pom.xml +++ b/pom.xml @@ -191,7 +191,7 @@ 3.19.6 2.12.2 7.4.1 - 4.1.77.Final + 4.1.94.Final 1.5 9.4.0 1.5.5 From 521200902aa3b4ecd7d63f5243ee56977615bd5b Mon Sep 17 00:00:00 2001 From: Breno de Souza Date: Mon, 1 Apr 2024 06:32:37 -0300 Subject: [PATCH 78/82] ENG-5502 Fixing org.apache.commons:commons-compress vulnerabilities This will fix the following vulnerabilities: CVE-2024-26308: https://security.snyk.io/vuln/SNYK-JAVA-ORGAPACHECOMMONS-6254297 CVE-2024-25710: https://security.snyk.io/vuln/SNYK-JAVA-ORGAPACHECOMMONS-6254296 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a0be8b24d5..dc265fa772 100644 --- a/pom.xml +++ b/pom.xml @@ -167,7 +167,7 @@ 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 From c70a22d00d98ac2538fd7d3ba5b9d8783c6709ac Mon Sep 17 00:00:00 2001 From: Breno de Souza Date: Mon, 1 Apr 2024 07:32:59 -0300 Subject: [PATCH 79/82] ENG-5503 Fixing jettison vulnerabilities --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a0be8b24d5..2aa435e248 100644 --- a/pom.xml +++ b/pom.xml @@ -113,7 +113,7 @@ 4.4.3 1.7.31 2.20.0 - 1.4.1 + 1.5.4 4.5.13 3.0.1 3.0.1 From 4b82ed86a20dd2e15c530b027865b2973786955e Mon Sep 17 00:00:00 2001 From: Breno de Souza Date: Mon, 1 Apr 2024 08:25:41 -0300 Subject: [PATCH 80/82] ENG-5504 Fixing hibernate-validator vulnerabilities This will fix the following vulnerabilities: CVE-2023-1932: https://security.snyk.io/vuln/SNYK-JAVA-ORGHIBERNATEVALIDATOR-6247635 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a0be8b24d5..9742367db7 100644 --- a/pom.xml +++ b/pom.xml @@ -87,7 +87,7 @@ 2.5.31 2.12.7 2.12.7.1 - 6.0.20.Final + 6.2.0.Final 8.9.0 2.6.0 2.10.5 From 5e3f9dfa119ba5546890fc8509b4d8926a79a2bc Mon Sep 17 00:00:00 2001 From: Breno de Souza Date: Mon, 1 Apr 2024 09:30:24 -0300 Subject: [PATCH 81/82] ENG-5505 Fixing liquibase vulnerabilities This will fix the following vulnerabilities: CVE-2022-0839: https://security.snyk.io/vuln/SNYK-JAVA-ORGLIQUIBASE-2419059 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a0be8b24d5..721b03c489 100644 --- a/pom.xml +++ b/pom.xml @@ -110,7 +110,7 @@ 0.1.5 1.9.7 4.40 - 4.4.3 + 4.8.0 1.7.31 2.20.0 1.4.1 From 0a3f8c81cd8c518d39dc092333b2bdf7bcd839e2 Mon Sep 17 00:00:00 2001 From: Eugenio Santoboni Date: Tue, 28 May 2024 12:01:44 +0200 Subject: [PATCH 82/82] ENG-5473: Added resource delete message and disk size bar --- .../apsadmin/global-messages_en.properties | 1 + .../apsadmin/global-messages_it.properties | 1 + .../jsp/filebrowser/filebrowser-list.jsp | 9 +- .../apsadmin/global-messages_en.properties | 7 +- .../apsadmin/global-messages_it.properties | 6 +- .../apsadmin/jpversioning-tiles.xml | 91 ++++++++++--------- .../jsp/resource/defaultTrashResource.jsp | 78 ++++++++++++++++ webapp/src/main/webapp/WEB-INF/web.xml | 5 +- 8 files changed, 149 insertions(+), 49 deletions(-) create mode 100644 versioning-plugin/src/main/webapp/WEB-INF/plugins/jpversioning/apsadmin/jsp/resource/defaultTrashResource.jsp diff --git a/admin-console/src/main/java/com/agiletec/apsadmin/global-messages_en.properties b/admin-console/src/main/java/com/agiletec/apsadmin/global-messages_en.properties index 852738fb81..3afc35efab 100644 --- a/admin-console/src/main/java/com/agiletec/apsadmin/global-messages_en.properties +++ b/admin-console/src/main/java/com/agiletec/apsadmin/global-messages_en.properties @@ -85,6 +85,7 @@ label.template.preview=Template preview label.key=Key label.info=Info +label.warning=Warning label.description=Name label.empty.f=Empty label.empty.m=Empty diff --git a/admin-console/src/main/java/com/agiletec/apsadmin/global-messages_it.properties b/admin-console/src/main/java/com/agiletec/apsadmin/global-messages_it.properties index d654d7885f..d50c43a7b4 100644 --- a/admin-console/src/main/java/com/agiletec/apsadmin/global-messages_it.properties +++ b/admin-console/src/main/java/com/agiletec/apsadmin/global-messages_it.properties @@ -91,6 +91,7 @@ label.filter=Filtro label.key=Chiave label.info=Info +label.warning=Attenzione label.description=Descrizione label.descr=Descrizione label.empty.f=Vuota diff --git a/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/filebrowser/filebrowser-list.jsp b/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/filebrowser/filebrowser-list.jsp index b2f33e3d6e..1f47b15f9b 100644 --- a/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/filebrowser/filebrowser-list.jsp +++ b/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/filebrowser/filebrowser-list.jsp @@ -29,13 +29,18 @@
 :   /   – -  %  + +  %  +
+
%"> + % Complete (danger) +
+
-
diff --git a/versioning-plugin/src/main/java/com/agiletec/plugins/jpversioning/apsadmin/global-messages_en.properties b/versioning-plugin/src/main/java/com/agiletec/plugins/jpversioning/apsadmin/global-messages_en.properties index 2f3ded3116..759737fb6d 100644 --- a/versioning-plugin/src/main/java/com/agiletec/plugins/jpversioning/apsadmin/global-messages_en.properties +++ b/versioning-plugin/src/main/java/com/agiletec/plugins/jpversioning/apsadmin/global-messages_en.properties @@ -17,4 +17,9 @@ jpversioning.resources.images.help=TO be inserted jpversioning.recover.help=TO be inserted jpversioning.resourceManagement.resourceTrash.help=TO be inserted -title.jpversioning.contentHistoryManagement=History Management \ No newline at end of file +title.jpversioning.contentHistoryManagement=History Management + +jpversioning.label.deletionWillBePermanent=The deletion will be permanent and cannot be undone + +jpversioning.label.deletionCanBeUndone=The deletion can be undone throw the functionalities of versioning module + diff --git a/versioning-plugin/src/main/java/com/agiletec/plugins/jpversioning/apsadmin/global-messages_it.properties b/versioning-plugin/src/main/java/com/agiletec/plugins/jpversioning/apsadmin/global-messages_it.properties index 47953461a8..d413efdcb3 100644 --- a/versioning-plugin/src/main/java/com/agiletec/plugins/jpversioning/apsadmin/global-messages_it.properties +++ b/versioning-plugin/src/main/java/com/agiletec/plugins/jpversioning/apsadmin/global-messages_it.properties @@ -17,4 +17,8 @@ jpversioning.resources.images.help=Da inserire jpversioning.recover.help=Da inserire jpversioning.resourceManagement.resourceTrash.help=Da inserire -title.jpversioning.contentHistoryManagement=Gestione Versionamenti \ No newline at end of file +title.jpversioning.contentHistoryManagement=Gestione Versionamenti + +jpversioning.label.deletionWillBePermanent=La cancellazione sar\u00e0 permanente e non potr\u00e0 essere revertata + +jpversioning.label.deletionCanBeUndone=La cancellazione potr\u00e0 essere revertata con le funzionalit\u00e0 del modulo versioning diff --git a/versioning-plugin/src/main/webapp/WEB-INF/plugins/jpversioning/apsadmin/jpversioning-tiles.xml b/versioning-plugin/src/main/webapp/WEB-INF/plugins/jpversioning/apsadmin/jpversioning-tiles.xml index 6fef434096..bc4ccc3339 100644 --- a/versioning-plugin/src/main/webapp/WEB-INF/plugins/jpversioning/apsadmin/jpversioning-tiles.xml +++ b/versioning-plugin/src/main/webapp/WEB-INF/plugins/jpversioning/apsadmin/jpversioning-tiles.xml @@ -4,36 +4,36 @@ "http://tiles.apache.org/dtds/tiles-config_3_0.dtd"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -41,19 +41,26 @@ - - - - + + + + - - - - + + + + - - - - + + + + + + + + + + + diff --git a/versioning-plugin/src/main/webapp/WEB-INF/plugins/jpversioning/apsadmin/jsp/resource/defaultTrashResource.jsp b/versioning-plugin/src/main/webapp/WEB-INF/plugins/jpversioning/apsadmin/jsp/resource/defaultTrashResource.jsp new file mode 100644 index 0000000000..730ddf85df --- /dev/null +++ b/versioning-plugin/src/main/webapp/WEB-INF/plugins/jpversioning/apsadmin/jsp/resource/defaultTrashResource.jsp @@ -0,0 +1,78 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ taglib uri="/aps-core" prefix="wp" %> +<%@ taglib uri="/apsadmin-core" prefix="wpsa" %> +<%@ taglib prefix="wpsf" uri="/apsadmin-form" %> + + + + + + + +

+
+ +
+

+
+
+
+
+ +
diff --git a/webapp/src/main/webapp/WEB-INF/web.xml b/webapp/src/main/webapp/WEB-INF/web.xml index 4b5d50504c..f82e839b1e 100644 --- a/webapp/src/main/webapp/WEB-INF/web.xml +++ b/webapp/src/main/webapp/WEB-INF/web.xml @@ -20,12 +20,11 @@ Struts2Config - struts-default.xml,struts-plugin.xml,struts.xml,entando-struts-plugin.xml,japs-struts-plugin.xml - + struts-default.xml,struts-plugin.xml,struts.xml,entando-struts-plugin.xml,japs-struts-plugin.xml org.apache.tiles.definition.DefinitionsFactory.DEFINITIONS_CONFIG - /WEB-INF/apsadmin/tiles.xml,/WEB-INF/plugins/**/apsadmin/**tiles.xml + /WEB-INF/apsadmin/tiles.xml,/WEB-INF/plugins/**/apsadmin/**tiles.xml,/WEB-INF/plugins/jpversioning/apsadmin/jpversioning-tiles.xml contextClass