From 3c40470dfc932aec85e42a7f50d092aeb10c8a96 Mon Sep 17 00:00:00 2001 From: harish Date: Thu, 24 Oct 2024 14:51:03 +0530 Subject: [PATCH] updated code for hub new changes --- .../tendermanagement/dao/ApplicationDao.java | 1 + .../tendermanagement/dao/CompanyDao.java | 10 +-- .../tendermanagement/dao/DashboardDao.java | 2 +- .../gepafin/tendermanagement/dao/UserDao.java | 20 +++--- .../entities/ApplicationEntity.java | 3 + .../entities/BeneficiaryEntity.java | 3 + .../entities/CompanyEntity.java | 7 ++ .../repositories/CompanyRepository.java | 12 ++-- .../repositories/UserRepository.java | 16 +---- .../UserWithCompanyRepository.java | 6 +- .../service/impl/AuthenticationService.java | 3 +- .../BeneficiaryPreferredCallServiceImpl.java | 8 +-- .../service/impl/CompanyServiceImpl.java | 4 +- .../tendermanagement/util/Validator.java | 16 ++++- .../db/changelog/db.changelog-1.0.0.xml | 66 +++++++++++++++++++ 15 files changed, 130 insertions(+), 47 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java index 82503677..fe86a5c6 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/ApplicationDao.java @@ -175,6 +175,7 @@ public class ApplicationDao { entity.setUserId(user.getId()); entity.setCompany(companyEntity); entity.setCall(call); + entity.setHubId(call.getHub().getId()); entity.setIsDeleted(false); entity.setStatus(ApplicationStatusTypeEnum.DRAFT.getValue()); return entity; diff --git a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java index 2208962b..0f59fc08 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/CompanyDao.java @@ -40,7 +40,7 @@ public class CompanyDao { public CompanyResponse createCompany(UserEntity userEntity, CompanyRequest companyRequest) { - CompanyEntity existingCompany = companyRepository.findByVatNumber(companyRequest.getVatNumber()); + CompanyEntity existingCompany = companyRepository.findByVatNumberAndHubId(companyRequest.getVatNumber(), userEntity.getHub().getId()); UserWithCompanyEntity userWithCompanyEntity = null; if (existingCompany != null) { UserWithCompanyEntity existingRelation = userWithCompanyRepository.findByUserIdAndCompanyIdAndIsDeletedFalse(userEntity.getId(), existingCompany.getId()) @@ -53,7 +53,7 @@ public class CompanyDao { } return convertCompanyEntityToCompanyResponse(existingCompany, userWithCompanyEntity); } else { - validateCompany(companyRequest); + validateCompany(userEntity, companyRequest); CompanyEntity companyEntity = convertCompanyRequestToCompanyEntity(companyRequest); companyRepository.save(companyEntity); userWithCompanyEntity = createUserWithCompanyRelation(userEntity, companyEntity, companyRequest.getIsLegalRepresentant()); @@ -62,7 +62,7 @@ public class CompanyDao { } - private void validateCompany(CompanyRequest companyRequest) { + private void validateCompany(UserEntity userEntity, CompanyRequest companyRequest) { if (Boolean.FALSE.equals(StringUtils.isEmpty(companyRequest.getEmail())) && Boolean.FALSE.equals(Utils.isValidEmail(companyRequest.getEmail()))) { @@ -73,7 +73,7 @@ public class CompanyDao { throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.VATNUMBER_MANDATORY)); } - if (companyRepository.existsByVatNumber(companyRequest.getVatNumber())) { + if (companyRepository.existsByVatNumberAndHubId(companyRequest.getVatNumber(), userEntity.getHub().getId())) { throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.VATNUMBER_ALREADY_EXISTS)); } @@ -186,7 +186,7 @@ public class CompanyDao { public List getCompanyByUserId(Long userId) { UserEntity userEntity = userService.validateUser(userId); List activeCompanyIds = userWithCompanyRepository.findActiveCompanyIdsByUserId(userEntity.getId()); - List companies = companyRepository.findByIdIn(activeCompanyIds); + List companies = companyRepository.findByIdInAndHubId(activeCompanyIds, userEntity.getHub().getId()); return companies.stream().map(companyEntity -> { UserWithCompanyEntity userWithCompanyEntity = getUserWithCompany(userEntity.getId(), companyEntity.getId()); return convertCompanyEntityToCompanyResponse(companyEntity, userWithCompanyEntity); diff --git a/src/main/java/net/gepafin/tendermanagement/dao/DashboardDao.java b/src/main/java/net/gepafin/tendermanagement/dao/DashboardDao.java index a849f67d..de20f607 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/DashboardDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/DashboardDao.java @@ -94,7 +94,7 @@ public class DashboardDao { } private void setNumberOfCompanies(Widget1 widget1, UserEntity requestedUserEntity) { - Long numberOfCompanies = companyRepository.countTotalCompanies(); + Long numberOfCompanies = companyRepository.countTotalCompaniesByHubId(requestedUserEntity.getHub().getId()); if (numberOfCompanies != null) { widget1.setNumberOfCompany(numberOfCompanies); } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/UserDao.java b/src/main/java/net/gepafin/tendermanagement/dao/UserDao.java index ae75d8bd..6a776f07 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/UserDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/UserDao.java @@ -6,6 +6,7 @@ import net.gepafin.tendermanagement.config.SamlSuccessHandler; import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.entities.BeneficiaryEntity; +import net.gepafin.tendermanagement.entities.HubEntity; import net.gepafin.tendermanagement.entities.RoleEntity; import net.gepafin.tendermanagement.entities.UserEntity; import net.gepafin.tendermanagement.enums.RoleStatusEnum; @@ -84,17 +85,17 @@ public class UserDao { if(StringUtils.isEmpty(userReq.getHubUuid())) { userReq.setHubUuid(defaultHubUuid); } - validateUserRequest(request, tempToken, userReq); + HubEntity hub = hubService.getHubByUuid(userReq.getHubUuid()); + validateUserRequest(request, tempToken, userReq, hub); validatePassword(userReq.getPassword(), userReq.getConfPassword(), tempToken); - RoleEntity roleEntity = getRoleEntity(userReq.getRoleId()); - BeneficiaryEntity beneficiary = createBeneficiary(roleEntity, userReq); - UserEntity userEntity = convertUserRequestToUserEntity(beneficiary, roleEntity, userReq); + BeneficiaryEntity beneficiary = createBeneficiary(roleEntity, userReq, hub); + UserEntity userEntity = convertUserRequestToUserEntity(beneficiary, roleEntity, userReq, hub); log.info("User created with ID: {}", userEntity.getId()); return authService.getJWTTokenBean(userEntity, Boolean.TRUE); } - private BeneficiaryEntity createBeneficiary(RoleEntity roleEntity, UserReq userReq) { + private BeneficiaryEntity createBeneficiary(RoleEntity roleEntity, UserReq userReq, HubEntity hub) { BeneficiaryEntity beneficiaryEntity = null; if (RoleStatusEnum.ROLE_BENEFICIARY.getValue().equals(roleEntity.getRoleType())) { beneficiaryEntity = new BeneficiaryEntity(); @@ -114,12 +115,13 @@ public class UserDao { beneficiaryEntity.setMarketing(userReq.getMarketing()); beneficiaryEntity.setThirdParty(userReq.getThirdParty()); beneficiaryEntity.setEmailPec(userReq.getEmailPec()); + beneficiaryEntity.setHubId(hub.getId()); beneficiaryEntity =beneficiaryRepository.save(beneficiaryEntity); } return beneficiaryEntity; } - private void validateUserRequest(HttpServletRequest request, String tempToken, UserReq userReq) { + private void validateUserRequest(HttpServletRequest request, String tempToken, UserReq userReq, HubEntity hub) { if (tempToken == null) { validator.validateRequest(request,RoleStatusEnum.ROLE_SUPER_ADMIN); @@ -139,7 +141,7 @@ public class UserDao { Translator.toLocale(GepafinConstant.EMAIL_ALREADY_EXISTS)); } if (Boolean.FALSE.equals(StringUtils.isEmpty(userReq.getCodiceFiscale())) - && userRepository.existsByBeneficiaryCodiceFiscale(userReq.getCodiceFiscale())) { + && userRepository.existsByBeneficiaryCodiceFiscaleAndHubId(userReq.getCodiceFiscale(), hub.getId())) { log.error("User creation failed: CodiceFiscale {} already exists", userReq.getCodiceFiscale()); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.CODICE_FISCALE_EXISTS)); @@ -206,7 +208,7 @@ public class UserDao { return convertUserEntityToUserResponse(userEntity); } - private UserEntity convertUserRequestToUserEntity(BeneficiaryEntity beneficiary, RoleEntity roleEntity, UserReq userReq) { + private UserEntity convertUserRequestToUserEntity(BeneficiaryEntity beneficiary, RoleEntity roleEntity, UserReq userReq, HubEntity hub) { UserEntity userEntity = new UserEntity(); if(Boolean.FALSE.equals(StringUtils.isEmpty(userReq.getPassword()))) { userEntity.setPassword(passwordEncoder.encode(userReq.getPassword())); @@ -215,7 +217,7 @@ public class UserDao { userEntity.setEmail(userReq.getEmail()); userEntity.setStatus(UserStatusEnum.ACTIVE.getValue()); userEntity.setBeneficiary(beneficiary); - userEntity.setHub(hubService.getHubByUuid(userReq.getHubUuid())); + userEntity.setHub(hub); if (Boolean.FALSE.equals(RoleStatusEnum.ROLE_BENEFICIARY.getValue().equals(roleEntity.getRoleType()))) { userEntity.setFirstName(userReq.getFirstName()); userEntity.setLastName(userReq.getLastName()); diff --git a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java index cc0f929e..5ddebe5c 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/ApplicationEntity.java @@ -39,4 +39,7 @@ public class ApplicationEntity extends BaseEntity { @OneToOne @JoinColumn(name = "PROTOCOL_NUMBER") private ProtocolEntity protocol; + + @Column(name = "HUB_ID") + private Long hubId; } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/entities/BeneficiaryEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/BeneficiaryEntity.java index bae88c90..2e84dd51 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/BeneficiaryEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/BeneficiaryEntity.java @@ -61,4 +61,7 @@ public class BeneficiaryEntity extends BaseEntity { @Column(name = "EMAIL_PEC") private String emailPec; + + @Column(name = "HUB_ID") + private Long hubId; } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/CompanyEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/CompanyEntity.java index c35a8cf0..ed50268f 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/CompanyEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/CompanyEntity.java @@ -4,6 +4,8 @@ import java.math.BigDecimal; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.Data; @@ -56,4 +58,9 @@ public class CompanyEntity extends BaseEntity{ @Column(name = "CONTACT_EMAIL") private String contactEmail; + + @ManyToOne + @JoinColumn(name = "HUB_ID") + private HubEntity hub; + } diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/CompanyRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/CompanyRepository.java index c0c5f75d..b9395883 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/CompanyRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/CompanyRepository.java @@ -4,6 +4,7 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import net.gepafin.tendermanagement.entities.CompanyEntity; @@ -11,13 +12,14 @@ import net.gepafin.tendermanagement.entities.CompanyEntity; @Repository public interface CompanyRepository extends JpaRepository { - List findByIdIn(List companyIds); + List findByIdInAndHubId(List companyIds, Long hubId); - Boolean existsByVatNumber(String vatNumber); - CompanyEntity findByVatNumber(String vatNumber); + Boolean existsByVatNumberAndHubId(String vatNumber, Long hubId); - @Query("SELECT COUNT(c) FROM CompanyEntity c") - long countTotalCompanies(); + @Query("SELECT COUNT(c) FROM CompanyEntity c where c.hub.id = :hubId") + long countTotalCompaniesByHubId(@Param("hubId") Long hubId); + + CompanyEntity findByVatNumberAndHubId(String vatNumber, Long hubId); } diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/UserRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/UserRepository.java index c9122eb7..47ab16b8 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/UserRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/UserRepository.java @@ -10,21 +10,9 @@ import java.util.Optional; @Repository public interface UserRepository extends JpaRepository { -// Optional findByEmailIgnoreCase(String email); - -// boolean existsByEmailIgnoreCase(String email); - -// UserEntity findByEmail(String email); - - Optional findByBeneficiaryCodiceFiscale(String codiceFiscale); - - boolean existsByBeneficiaryCodiceFiscale(String codiceFiscale); - UserEntity findByBeneficiaryId(Long beneficiaryId); - Long countByStatusAndRoleEntityRoleType(String status, String roleName); - - Optional findByEmailIgnoreCaseAndHubUniqueUuid(String email, String hubId); + Optional findByEmailIgnoreCaseAndHubUniqueUuid(String email, String hubUuid); boolean existsByEmailIgnoreCaseAndHubUniqueUuid(String email, String hubUuid); @@ -35,4 +23,6 @@ public interface UserRepository extends JpaRepository { Long countByStatusAndRoleEntityRoleTypeAndHubId(String status, String roleName, Long hubId); Optional findByBeneficiaryCodiceFiscaleAndHubId(String codiceFiscale, Long hubId); + + boolean existsByBeneficiaryCodiceFiscaleAndHubId(String codiceFiscale, Long hubId); } diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/UserWithCompanyRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/UserWithCompanyRepository.java index d17b93e2..ec93f2f6 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/UserWithCompanyRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/UserWithCompanyRepository.java @@ -14,10 +14,8 @@ public interface UserWithCompanyRepository extends JpaRepository findActiveCompanyIdsByUserId(@Param("userId") Long userId); - - + @Query("SELECT u.companyId FROM UserWithCompanyEntity u WHERE u.userId = :userId AND u.isDeleted = false") + List findActiveCompanyIdsByUserId(@Param("userId") Long userId); Optional findByUserIdAndCompanyIdAndIsDeletedFalse(Long userId, Long companyId); diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/AuthenticationService.java b/src/main/java/net/gepafin/tendermanagement/service/impl/AuthenticationService.java index 3fe819a4..0d3dab12 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/AuthenticationService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/AuthenticationService.java @@ -211,10 +211,11 @@ public class AuthenticationService { throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.INVALID_TOKEN_MSG)); } + HubEntity hub = hubService.getHubByUuid(samlResponseLogEntity.getHubUuid()); Map> userAttributes = Utils .convertStringIntoMap(samlResponseLogEntity.getAuthenticationObject()); String cf = userAttributes.get("CodiceFiscale").get(0).toString(); - if (userRepository.existsByBeneficiaryCodiceFiscale(cf)) { + if (userRepository.existsByBeneficiaryCodiceFiscaleAndHubId(cf, hub.getId())) { throw new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.USER_ALREADY_EXIST_MSG)); } diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/BeneficiaryPreferredCallServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/BeneficiaryPreferredCallServiceImpl.java index 4484027e..48b07bff 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/BeneficiaryPreferredCallServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/BeneficiaryPreferredCallServiceImpl.java @@ -9,7 +9,6 @@ import net.gepafin.tendermanagement.enums.BeneficiaryCallStatus; import net.gepafin.tendermanagement.model.request.BeneficiaryPreferredCallReq; import net.gepafin.tendermanagement.model.response.BeneficiaryPreferredCallResponseBean; -import net.gepafin.tendermanagement.repositories.UserRepository; import net.gepafin.tendermanagement.service.BeneficiaryPreferredCallService; import net.gepafin.tendermanagement.service.UserService; import net.gepafin.tendermanagement.util.Validator; @@ -17,7 +16,6 @@ import net.gepafin.tendermanagement.web.rest.api.errors.CustomValidationExceptio import net.gepafin.tendermanagement.web.rest.api.errors.Status; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; import java.util.List; @@ -26,10 +24,10 @@ public class BeneficiaryPreferredCallServiceImpl implements BeneficiaryPreferred @Autowired private BeneficiaryPreferredCallDao beneficiaryPreferredCallDao; + @Autowired private Validator validator; - @Autowired - private UserRepository userRepository; + @Autowired private UserService userService; @@ -81,7 +79,7 @@ public class BeneficiaryPreferredCallServiceImpl implements BeneficiaryPreferred } if(beneficiaryId!=null){ UserEntity user = userService.getUserByBeneficiaryId(beneficiaryId); - return validator.validateUserId(request,user.getId()); + return validator.validateUserId(request, user.getId()); } else{ return validator.validateUserId(request, userId); diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyServiceImpl.java index 34c8777f..47656b4d 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/CompanyServiceImpl.java @@ -56,6 +56,7 @@ public class CompanyServiceImpl implements CompanyService { @Transactional(readOnly = true) public CompanyResponse getCompany(HttpServletRequest request, Long companyId) { UserEntity userEntity =validator.validateUser(request); + validator.validateUserWithCompany(request, companyId); return companyDao.getCompany(userEntity, companyId); } @@ -63,13 +64,14 @@ public class CompanyServiceImpl implements CompanyService { @Transactional(rollbackFor = Exception.class) public void deleteCompany(HttpServletRequest request, Long companyId) { UserEntity userEntity =validator.validateUser(request); + validator.validateUserWithCompany(request, companyId); companyDao.deleteCompany(userEntity, companyId); } @Override @Transactional(readOnly = true) public List getCompanyByUserId(HttpServletRequest request, Long userId) { - validator.validateUser(request); + validator.validateUserId(request, userId); return companyDao.getCompanyByUserId(userId); } diff --git a/src/main/java/net/gepafin/tendermanagement/util/Validator.java b/src/main/java/net/gepafin/tendermanagement/util/Validator.java index 09563378..785ad3b9 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/Validator.java +++ b/src/main/java/net/gepafin/tendermanagement/util/Validator.java @@ -4,7 +4,6 @@ import jakarta.servlet.http.HttpServletRequest; import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.config.jwt.TokenProvider; import net.gepafin.tendermanagement.constants.GepafinConstant; -import net.gepafin.tendermanagement.dao.CallDao; import net.gepafin.tendermanagement.entities.CallEntity; import net.gepafin.tendermanagement.entities.CompanyEntity; import net.gepafin.tendermanagement.entities.UserEntity; @@ -73,8 +72,14 @@ public class Validator { } public CompanyEntity validateUserWithCompany(HttpServletRequest request, Long companyId) { + UserEntity user = validateUser(request); + CompanyEntity companyEntity = companyService.validateCompany(companyId); + if (Boolean.FALSE.equals(user.getHub().getId().equals(companyEntity.getHub().getId()))) { + throw new ForbiddenAccessException(Status.FORBIDDEN, + Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); + } if (checkIsSuperAdmin()) { - return companyService.validateCompany(companyId); + return companyEntity; } Map userInfo = tokenProvider.getUserInfoAndUserIdFromToken(request); companyService.validateUserWithCompny(getUserId(userInfo), companyId); @@ -100,10 +105,15 @@ public class Validator { public UserEntity validateUserId(HttpServletRequest request, Long userId) { UserEntity user = validateUser(request); + UserEntity requestedUser = userService.validateUser(userId); + + if(Boolean.FALSE.equals(requestedUser.getHub().getId().equals(user.getHub().getId()))) { + throw new ForbiddenAccessException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); + } if(user.getRoleEntity().getRoleType().equals(RoleStatusEnum.ROLE_BENEFICIARY.getValue()) && Boolean.FALSE.equals(user.getId().equals(userId))) { throw new ForbiddenAccessException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PERMISSION_DENIED)); } - return userService.validateUser(userId); + return requestedUser; } private Long getUserIdFromToken(HttpServletRequest request) { diff --git a/src/main/resources/db/changelog/db.changelog-1.0.0.xml b/src/main/resources/db/changelog/db.changelog-1.0.0.xml index 5df3a2b7..41a10a1b 100644 --- a/src/main/resources/db/changelog/db.changelog-1.0.0.xml +++ b/src/main/resources/db/changelog/db.changelog-1.0.0.xml @@ -1328,4 +1328,70 @@ referencedColumnNames="id"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +