Skip to content

Commit af94563

Browse files
committed
Actualize access-decision module content
1 parent 2695203 commit af94563

19 files changed

Lines changed: 356 additions & 243 deletions

access-decision/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,21 @@
5050
<version>${spotbugs.version}</version>
5151
<scope>provided</scope>
5252
</dependency>
53+
<dependency>
54+
<groupId>org.apache.commons</groupId>
55+
<artifactId>commons-lang3</artifactId>
56+
<version>3.20.0</version>
57+
</dependency>
58+
<dependency>
59+
<groupId>org.apache.commons</groupId>
60+
<artifactId>commons-collections4</artifactId>
61+
<version>4.5.0</version>
62+
</dependency>
63+
<dependency>
64+
<groupId>com.fasterxml.jackson.core</groupId>
65+
<artifactId>jackson-annotations</artifactId>
66+
<version>2.21</version>
67+
</dependency>
5368

5469
<dependency>
5570
<groupId>org.junit.jupiter</groupId>
Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,49 @@
11
package by.andd3dfx.accessdecision.back;
22

33
import by.andd3dfx.accessdecision.front.Reason;
4+
import by.andd3dfx.accessdecision.front.ReasonLayer;
5+
import by.andd3dfx.accessdecision.front.ReasonType;
46
import lombok.Getter;
7+
import org.apache.commons.collections4.CollectionUtils;
8+
import org.apache.commons.lang3.StringUtils;
59

10+
import java.util.ArrayList;
611
import java.util.List;
7-
import java.util.stream.Collectors;
812

913
@Getter
1014
public abstract class AbstractAccessDecision {
1115

12-
private final boolean isGranted;
16+
private final ReasonLayer reasonLayer;
1317
private final List<Reason> reasons;
18+
// Optimization to speed up calculation of isNotGranted(), getExceptionMessage()
19+
private final List<String> negativeReasonsMessages;
1420

15-
protected AbstractAccessDecision(boolean isGranted, List<Reason> reasons) {
16-
this.isGranted = isGranted;
17-
this.reasons = reasons;
21+
protected AbstractAccessDecision(List<Reason> reasons, ReasonLayer reasonLayer) {
22+
this.reasons = new ArrayList<>(reasons);
23+
this.reasonLayer = reasonLayer;
24+
this.negativeReasonsMessages = new ArrayList<>(reasons.stream()
25+
.filter(Reason::isNegative)
26+
.map(Reason::message).toList());
27+
}
28+
29+
public void addGrant(String message) {
30+
reasons.add(new Reason(reasonLayer, ReasonType.POSITIVE, message));
31+
}
32+
33+
public void addDeny(String message) {
34+
reasons.add(new Reason(reasonLayer, ReasonType.NEGATIVE, message));
35+
negativeReasonsMessages.add(message);
1836
}
1937

2038
public boolean isNotGranted() {
21-
return !isGranted;
39+
return !negativeReasonsMessages.isEmpty();
2240
}
2341

2442
public String getExceptionMessage() {
25-
if (reasons == null) {
26-
throw new IllegalStateException("No reasons present!");
43+
if (CollectionUtils.isEmpty(negativeReasonsMessages)) {
44+
throw new IllegalStateException("No negative reasons present!");
2745
}
28-
return reasons.stream()
29-
.map(Reason::message)
30-
.collect(Collectors.joining(","));
46+
47+
return StringUtils.join(negativeReasonsMessages, ",");
3148
}
3249
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package by.andd3dfx.accessdecision.back;
2+
3+
public abstract class AbstractJudge {
4+
5+
/**
6+
* Purpose of this method is to call it from check* function of some service when throwing exception expected
7+
* in case if validation not passed
8+
*/
9+
public void makeJudgement(AccessDecisionVerdict verdict) {
10+
ViewModeRightsAccessDecision viewModeRights = verdict.viewModeRightsSupplier().get();
11+
if (viewModeRights.isNotGranted()) {
12+
viewModeRightsViolated(viewModeRights.getExceptionMessage());
13+
}
14+
15+
PermissionsAccessDecision permissions = verdict.permissionsSupplier().get();
16+
if (permissions.isNotGranted()) {
17+
permissionsViolated(permissions.getExceptionMessage());
18+
}
19+
20+
BusinessRuleRightsAccessDecision businessRuleRights = verdict.businessRuleRightsSupplier().get();
21+
if (businessRuleRights.isNotGranted()) {
22+
businessRuleRightsViolated(businessRuleRights.getExceptionMessage());
23+
}
24+
}
25+
26+
protected abstract void viewModeRightsViolated(String exceptionMessage);
27+
28+
protected abstract void permissionsViolated(String exceptionMessage);
29+
30+
protected abstract void businessRuleRightsViolated(String exceptionMessage);
31+
}
Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,42 @@
11
package by.andd3dfx.accessdecision.back;
22

33
import by.andd3dfx.accessdecision.front.Reason;
4+
import by.andd3dfx.accessdecision.front.ReasonLayer;
45
import by.andd3dfx.accessdecision.front.ReasonType;
56
import lombok.AccessLevel;
67
import lombok.AllArgsConstructor;
78

9+
import java.util.ArrayList;
10+
import java.util.Arrays;
811
import java.util.List;
912
import java.util.Objects;
10-
import java.util.function.BiFunction;
13+
import java.util.function.Function;
1114

1215
/**
1316
* Factory for creating access decisions. Used to create type-safe Access Decisions for each layer
1417
*/
1518
@AllArgsConstructor(access = AccessLevel.PACKAGE)
1619
public final class AccessDecisionFactory<T extends AbstractAccessDecision> {
1720

18-
private final String defaultDenialMessage;
19-
private final ReasonType reasonType;
20-
private final BiFunction<Boolean, List<Reason>, T> constructor;
21+
private final ReasonLayer reasonLayer;
22+
private final Function<List<Reason>, T> accessDecisionConstructor;
2123

22-
public T granted() {
23-
return constructor.apply(true, List.of());
24+
public T create() {
25+
return accessDecisionConstructor.apply(new ArrayList<>());
2426
}
2527

26-
public T granted(String message) {
27-
return granted(List.of(message));
28+
public T granted(String... messages) {
29+
return accessDecisionConstructor.apply(messagesToReasons(ReasonType.POSITIVE, messages));
2830
}
2931

30-
public T granted(List<String> messages) {
31-
return constructor.apply(true, messagesToReasons(messages, reasonType));
32+
public T denied(String... messages) {
33+
return accessDecisionConstructor.apply(messagesToReasons(ReasonType.NEGATIVE, messages));
3234
}
3335

34-
public T denied() {
35-
return denied(defaultDenialMessage);
36-
}
37-
38-
public T denied(String message) {
39-
return denied(List.of(message));
40-
}
41-
42-
public T denied(List<String> messages) {
43-
return constructor.apply(false, messagesToReasons(messages, reasonType));
44-
}
45-
46-
public static List<Reason> messagesToReasons(List<String> messages, ReasonType type) {
47-
return messages.stream()
36+
private List<Reason> messagesToReasons(ReasonType reasonType, String... messages) {
37+
return Arrays.stream(messages).toList().stream()
4838
.filter(Objects::nonNull)
49-
.map(m -> new Reason(m, type, null))
39+
.map(msg -> new Reason(reasonLayer, reasonType, msg))
5040
.toList();
5141
}
5242
}

access-decision/src/main/java/by/andd3dfx/accessdecision/back/AccessDecisionVerdict.java

Lines changed: 37 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,61 +8,62 @@
88
import java.util.Collection;
99
import java.util.Comparator;
1010
import java.util.List;
11-
import java.util.function.Predicate;
11+
import java.util.function.Supplier;
1212

1313
/**
14-
* Access decision verdict with view, permission and action levels
14+
* Access decision verdict with ViewModeRights, Permission and BusinessRuleRights levels.
15+
* Suppliers used to allow making calculation in lazy way (avoid check extra layers if negative result already known).
16+
*
17+
* @param viewModeRightsSupplier Supplier of ViewMode-level access (e.g. is program visible in this view mode)
18+
* @param permissionsSupplier Supplier of Grant-level permissions (regions, roles)
19+
* @param businessRuleRightsSupplier Supplier of Runtime/Business rule rights (entity state, business rules)
1520
*/
1621
public record AccessDecisionVerdict(
17-
/*
18-
View-level access (e.g. program visible in view)
19-
*/
20-
@NotNull ViewRightAccessDecision viewRight,
21-
/*
22-
Grant-level permission (regions, roles)
23-
*/
24-
@NotNull PermissionAccessDecision permission,
25-
/*
26-
Runtime/business action right (entity state, business rules)
27-
*/
28-
@NotNull ActionRightAccessDecision actionRight
22+
@NotNull Supplier<ViewModeRightsAccessDecision> viewModeRightsSupplier,
23+
@NotNull Supplier<PermissionsAccessDecision> permissionsSupplier,
24+
@NotNull Supplier<BusinessRuleRightsAccessDecision> businessRuleRightsSupplier
2925
) {
3026

3127
/**
32-
* Maps this aggregate to a UI action state:
33-
* view denied → INVISIBLE; permission or action denied → DISABLED; all granted → ENABLED.
28+
* <pre>
29+
* Maps AccessRightAggregate to ActionState for frontend:
30+
* - viewModeRights denied → INVISIBLE;
31+
* - permissions or businessRuleRights denied → DISABLED;
32+
* - all granted → ENABLED.
33+
*
34+
* Calculation performed in lazy way to avoid extra calculation in case if negative result already known.
35+
* </pre>
3436
*/
37+
@SuppressWarnings("checkstyle:ReturnCount")
3538
public ActionVisibilityState toActionState() {
36-
if (viewRight.isNotGranted()) {
37-
return ActionVisibilityState.invisible(collectNegativeReasons(viewRight));
39+
ViewModeRightsAccessDecision viewModeRights = viewModeRightsSupplier.get();
40+
if (viewModeRights.isNotGranted()) {
41+
return ActionVisibilityState.invisible(collectReasons(viewModeRights));
3842
}
3943

40-
if (permission.isNotGranted() || actionRight.isNotGranted()) {
41-
return ActionVisibilityState.disabled(collectNegativeReasons(permission, actionRight));
44+
PermissionsAccessDecision permissions = permissionsSupplier.get();
45+
if (permissions.isNotGranted()) {
46+
return ActionVisibilityState.disabled(collectReasons(viewModeRights, permissions));
4247
}
4348

44-
return ActionVisibilityState.enabled(collectPositiveReasons(viewRight, permission, actionRight));
45-
}
46-
47-
private List<Reason> collectNegativeReasons(AbstractAccessDecision... accessDecisions) {
48-
return collectReasons(AbstractAccessDecision::isNotGranted, accessDecisions);
49-
}
49+
BusinessRuleRightsAccessDecision businessRuleRights = businessRuleRightsSupplier.get();
50+
if (businessRuleRights.isNotGranted()) {
51+
return ActionVisibilityState.disabled(collectReasons(viewModeRights, permissions, businessRuleRights));
52+
}
5053

51-
private List<Reason> collectPositiveReasons(AbstractAccessDecision... accessDecisions) {
52-
return collectReasons(AbstractAccessDecision::isGranted, accessDecisions);
54+
return ActionVisibilityState.enabled(collectReasons(viewModeRights, permissions, businessRuleRights));
5355
}
5456

55-
private List<Reason> collectReasons(Predicate<? super AbstractAccessDecision> predicate,
56-
AbstractAccessDecision... accessDecisions) {
57+
private List<Reason> collectReasons(AbstractAccessDecision... accessDecisions) {
5758
return Arrays.stream(accessDecisions)
58-
.filter(predicate)
5959
.map(AbstractAccessDecision::getReasons)
6060
.flatMap(Collection::stream)
61-
.sorted(Comparator.comparing(Reason::type).thenComparing(Reason::message))
61+
// Make sorting to get determinism & wanted items order in result JSON:
62+
// - layer: VIEW_MODE_RIGHTS, PERMISSIONS, BUSINESS_RULE_RIGHTS
63+
// - type: POSITIVE, NEGATIVE
64+
.sorted(Comparator.comparingInt((Reason value) -> value.layer().ordinal())
65+
.thenComparingInt((Reason value) -> value.type().ordinal())
66+
.thenComparing(Reason::message))
6267
.toList();
6368
}
64-
65-
public boolean isGranted() {
66-
return viewRight.isGranted() && permission.isGranted() && actionRight.isGranted();
67-
}
6869
}
Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,28 @@
11
package by.andd3dfx.accessdecision.back;
22

3-
import by.andd3dfx.accessdecision.front.ReasonType;
3+
import by.andd3dfx.accessdecision.front.ReasonLayer;
44
import lombok.experimental.UtilityClass;
55

66
/**
7-
* Shared factories for view, permission, and action-right decisions.
7+
* Aggregate of factories for creating access decisions.
8+
* <p>
9+
* Use VIEW_MODE_RIGHTS, PERMISSIONS, BUSINESS_RULE_RIGHTS factories to create type-safe Access Decisions for each layer.
810
*/
911
@UtilityClass
1012
public class AccessDecisions {
1113

12-
public static final AccessDecisionFactory<ViewRightAccessDecision> VIEW_RIGHT =
14+
public static final AccessDecisionFactory<ViewModeRightsAccessDecision> VIEW_MODE_RIGHTS =
1315
new AccessDecisionFactory<>(
14-
"Not allowed for current view",
15-
ReasonType.VIEW_RIGHT,
16-
ViewRightAccessDecision::new);
16+
ReasonLayer.VIEW_MODE_RIGHTS,
17+
ViewModeRightsAccessDecision::new);
1718

18-
public static final AccessDecisionFactory<PermissionAccessDecision> PERMISSION =
19+
public static final AccessDecisionFactory<PermissionsAccessDecision> PERMISSIONS =
1920
new AccessDecisionFactory<>(
20-
"Additional permissions needed",
21-
ReasonType.PERMISSION,
22-
PermissionAccessDecision::new);
21+
ReasonLayer.PERMISSIONS,
22+
PermissionsAccessDecision::new);
2323

24-
public static final AccessDecisionFactory<ActionRightAccessDecision> ACTION_RIGHT =
24+
public static final AccessDecisionFactory<BusinessRuleRightsAccessDecision> BUSINESS_RULE_RIGHTS =
2525
new AccessDecisionFactory<>(
26-
"Action is not allowed from a business perspective",
27-
ReasonType.ACTION_RIGHT,
28-
ActionRightAccessDecision::new);
26+
ReasonLayer.BUSINESS_RULE_RIGHTS,
27+
BusinessRuleRightsAccessDecision::new);
2928
}

access-decision/src/main/java/by/andd3dfx/accessdecision/back/ActionRightAccessDecision.java

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package by.andd3dfx.accessdecision.back;
2+
3+
import by.andd3dfx.accessdecision.front.Reason;
4+
import by.andd3dfx.accessdecision.front.ReasonLayer;
5+
6+
import java.util.List;
7+
8+
/**
9+
* Access Decision based on runtime/business rules
10+
*/
11+
public final class BusinessRuleRightsAccessDecision extends AbstractAccessDecision {
12+
13+
public BusinessRuleRightsAccessDecision(List<Reason> reasons) {
14+
super(reasons, ReasonLayer.BUSINESS_RULE_RIGHTS);
15+
}
16+
17+
public BusinessRuleRightsAccessDecision addReason(boolean conditionFlag, String universalReasonMessage) {
18+
return addReason(conditionFlag, universalReasonMessage, universalReasonMessage);
19+
}
20+
21+
public BusinessRuleRightsAccessDecision addReason(boolean conditionFlag, String positiveReasonMessage, String negativeReasonMessage) {
22+
if (conditionFlag) {
23+
addGrant(positiveReasonMessage);
24+
} else {
25+
addDeny(negativeReasonMessage);
26+
}
27+
return this;
28+
}
29+
}

access-decision/src/main/java/by/andd3dfx/accessdecision/back/PermissionAccessDecision.java

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)