diff --git a/src/main/java/org/hostsharing/hsadminng/service/CustomerService.java b/src/main/java/org/hostsharing/hsadminng/service/CustomerService.java index 9bff0e66..06d8d978 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/CustomerService.java +++ b/src/main/java/org/hostsharing/hsadminng/service/CustomerService.java @@ -18,7 +18,7 @@ import java.util.Optional; */ @Service @Transactional -public class CustomerService implements DtoLoader { +public class CustomerService implements IdToDtoResolver { private final Logger log = LoggerFactory.getLogger(CustomerService.class); diff --git a/src/main/java/org/hostsharing/hsadminng/service/DtoLoader.java b/src/main/java/org/hostsharing/hsadminng/service/IdToDtoResolver.java similarity index 75% rename from src/main/java/org/hostsharing/hsadminng/service/DtoLoader.java rename to src/main/java/org/hostsharing/hsadminng/service/IdToDtoResolver.java index ae30d79b..14b15828 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/DtoLoader.java +++ b/src/main/java/org/hostsharing/hsadminng/service/IdToDtoResolver.java @@ -2,6 +2,6 @@ package org.hostsharing.hsadminng.service; import java.util.Optional; -public interface DtoLoader { +public interface IdToDtoResolver { Optional findOne(Long id); } diff --git a/src/main/java/org/hostsharing/hsadminng/service/MembershipService.java b/src/main/java/org/hostsharing/hsadminng/service/MembershipService.java index d611e713..473f5d7e 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/MembershipService.java +++ b/src/main/java/org/hostsharing/hsadminng/service/MembershipService.java @@ -19,7 +19,7 @@ import java.util.Optional; */ @Service @Transactional -public class MembershipService implements DtoLoader { +public class MembershipService implements IdToDtoResolver { private final Logger log = LoggerFactory.getLogger(MembershipService.class); diff --git a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonAccessFilter.java b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonAccessFilter.java index 86d88bba..45d2b7ac 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonAccessFilter.java +++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonAccessFilter.java @@ -1,17 +1,16 @@ package org.hostsharing.hsadminng.service.accessfilter; -import org.apache.commons.lang3.NotImplementedException; import org.hostsharing.hsadminng.security.SecurityUtils; -import org.hostsharing.hsadminng.service.CustomerService; -import org.hostsharing.hsadminng.service.DtoLoader; -import org.hostsharing.hsadminng.service.MembershipService; -import org.hostsharing.hsadminng.service.dto.CustomerDTO; +import org.hostsharing.hsadminng.service.IdToDtoResolver; import org.hostsharing.hsadminng.service.dto.MembershipDTO; import org.hostsharing.hsadminng.service.util.ReflectionUtil; import org.springframework.context.ApplicationContext; +import javax.persistence.EntityNotFoundException; import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; abstract class JSonAccessFilter { private final ApplicationContext ctx; @@ -50,43 +49,51 @@ abstract class JSonAccessFilter { */ Role getLoginUserRole() { final Role roleOnSelf = getLoginUserRoleOnSelf(); - if ( roleOnSelf.isIndependent() ) { + if (roleOnSelf.isIndependent()) { return roleOnSelf; } return getLoginUserRoleOnAncestorOfDtoClassIfHigher(roleOnSelf, dto); } private Role getLoginUserRoleOnSelf() { - return SecurityUtils.getLoginUserRoleFor(dto.getClass(), getId() ); + return SecurityUtils.getLoginUserRoleFor(dto.getClass(), getId()); } private Role getLoginUserRoleOnAncestorOfDtoClassIfHigher(final Role baseRole, final Object dto) { final Field parentIdField = determineFieldWithAnnotation(dto.getClass(), ParentId.class); - if ( parentIdField == null ) { + if (parentIdField == null) { return baseRole; } final ParentId parentIdAnnot = parentIdField.getAnnotation(ParentId.class); - final Class parentDtoClass = parentIdAnnot.value(); + final Class parentDtoLoader = parentIdAnnot.resolver(); + final Class parentDtoClass = getGenericClassParameter(parentDtoLoader); final Long parentId = (Long) ReflectionUtil.getValue(dto, parentIdField); final Role roleOnParent = SecurityUtils.getLoginUserRoleFor(parentDtoClass, parentId); - final Object parentEntity = findParentDto(parentDtoClass, parentId); + final Object parentEntity = findParentDto(parentDtoLoader, parentId); return Role.broadest(baseRole, getLoginUserRoleOnAncestorOfDtoClassIfHigher(roleOnParent, parentEntity)); } - private Object findParentDto(Class parentDtoClass, Long parentId) { - // TODO: generalize, e.g. via "all beans that implement DtoLoader - if ( parentDtoClass == MembershipDTO.class ) { - final DtoLoader dtoLoader = ctx.getAutowireCapableBeanFactory().createBean(MembershipService.class); - return dtoLoader.findOne(parentId).get(); + @SuppressWarnings("unchecked") + private Class getGenericClassParameter(Class parentDtoLoader) { + for (Type genericInterface : parentDtoLoader.getGenericInterfaces()) { + if (genericInterface instanceof ParameterizedType) { + final ParameterizedType parameterizedType = (ParameterizedType) genericInterface; + if (parameterizedType.getRawType()== IdToDtoResolver.class) { + return (Class) parameterizedType.getActualTypeArguments()[0]; + } + } + } - if ( parentDtoClass == CustomerDTO.class ) { - final DtoLoader dtoLoader = ctx.getAutowireCapableBeanFactory().createBean(CustomerService.class); - return dtoLoader.findOne(parentId).get(); - } - throw new NotImplementedException("no DtoLoader implemented for " + parentDtoClass); + throw new AssertionError(parentDtoLoader.getSimpleName() + " expected to implement " + IdToDtoResolver.class.getSimpleName() + "<...DTO>"); + } + + @SuppressWarnings("unchecked") + private Object findParentDto(final Class parentDtoLoader, final Long parentId) { + final IdToDtoResolver idToDtoResolver = ctx.getAutowireCapableBeanFactory().createBean(parentDtoLoader); + return idToDtoResolver.findOne(parentId).orElseThrow(() -> new EntityNotFoundException("Can't resolve parent entity ID " + parentId + " via " + parentDtoLoader)); } private static Field determineFieldWithAnnotation(final Class dtoClass, final Class idAnnotationClass) { diff --git a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/ParentId.java b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/ParentId.java index 51ddc0a2..e392374e 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/ParentId.java +++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/ParentId.java @@ -1,5 +1,7 @@ package org.hostsharing.hsadminng.service.accessfilter; +import org.hostsharing.hsadminng.service.IdToDtoResolver; + import java.lang.annotation.*; /** @@ -12,6 +14,6 @@ import java.lang.annotation.*; @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface ParentId { - /// The DTO class of the referenced entity. - Class value(); + /// The service which can load the referenced DTO. + Class> resolver(); } diff --git a/src/main/java/org/hostsharing/hsadminng/service/dto/MembershipDTO.java b/src/main/java/org/hostsharing/hsadminng/service/dto/MembershipDTO.java index c255c3a2..141614d9 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/dto/MembershipDTO.java +++ b/src/main/java/org/hostsharing/hsadminng/service/dto/MembershipDTO.java @@ -1,5 +1,6 @@ package org.hostsharing.hsadminng.service.dto; +import org.hostsharing.hsadminng.service.CustomerService; import org.hostsharing.hsadminng.service.accessfilter.AccessFor; import org.hostsharing.hsadminng.service.accessfilter.ParentId; import org.hostsharing.hsadminng.service.accessfilter.Role; @@ -36,7 +37,7 @@ public class MembershipDTO implements Serializable { @AccessFor(init = Role.ADMIN, read = Role.SUPPORTER) private String remark; - @ParentId(CustomerDTO.class) + @ParentId(resolver = CustomerService.class) @AccessFor(init = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT}) private Long customerId; diff --git a/src/main/java/org/hostsharing/hsadminng/service/dto/ShareDTO.java b/src/main/java/org/hostsharing/hsadminng/service/dto/ShareDTO.java index eff5bad4..b9e79209 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/dto/ShareDTO.java +++ b/src/main/java/org/hostsharing/hsadminng/service/dto/ShareDTO.java @@ -1,6 +1,7 @@ package org.hostsharing.hsadminng.service.dto; import org.hostsharing.hsadminng.domain.enumeration.ShareAction; +import org.hostsharing.hsadminng.service.MembershipService; import org.hostsharing.hsadminng.service.accessfilter.AccessFor; import org.hostsharing.hsadminng.service.accessfilter.ParentId; import org.hostsharing.hsadminng.service.accessfilter.Role; @@ -41,7 +42,7 @@ public class ShareDTO implements Serializable { @AccessFor(init = Role.ADMIN, read = Role.SUPPORTER) private String remark; - @ParentId(MembershipDTO.class) + @ParentId(resolver = MembershipService.class) @AccessFor(init = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT}) private Long membershipId; diff --git a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilterUnitTest.java b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilterUnitTest.java index 9c0340b8..da59b241 100644 --- a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilterUnitTest.java +++ b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilterUnitTest.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.core.TreeNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.tuple.ImmutablePair; +import org.hostsharing.hsadminng.service.IdToDtoResolver; import org.hostsharing.hsadminng.web.rest.errors.BadRequestAlertException; import org.junit.Before; import org.junit.Rule; @@ -211,6 +212,9 @@ public class JSonDeserializerWithAccessFilterUnitTest { Long openLongField; } + abstract class GivenService implements IdToDtoResolver { + } + public static class GivenChildDto { @SelfId @@ -218,7 +222,7 @@ public class JSonDeserializerWithAccessFilterUnitTest { Long id; @AccessFor(init = Role.CONTRACTUAL_CONTACT, update = Role.CONTRACTUAL_CONTACT, read = Role.ACTUAL_CUSTOMER_USER) - @ParentId(GivenDto.class) + @ParentId(resolver = GivenService.class) Long parentId; @AccessFor(init = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT}, update = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT}) diff --git a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilterUnitTest.java b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilterUnitTest.java index 8cd09db8..f953bcf1 100644 --- a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilterUnitTest.java +++ b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilterUnitTest.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomUtils; +import org.hostsharing.hsadminng.service.IdToDtoResolver; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -110,9 +111,12 @@ public class JSonSerializerWithAccessFilterUnitTest { } + private abstract class GivenCustomerService implements IdToDtoResolver { + } + private static class GivenDto { - @ParentId(GivenCustomerDto.class) + @ParentId(resolver = GivenCustomerService.class) Long customerId; @AccessFor(read = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT})