Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
db9e465
CAUSEWAY-3752: flattens HiddenObjectFacetViaMethod
andi-huber Nov 27, 2025
2e22940
CAUSEWAY-3752: adds filter (SPI)
andi-huber Nov 27, 2025
8517f20
CAUSEWAY-3752: flattens PrototypeFacetForActionAnnotation
andi-huber Nov 27, 2025
b1cba84
CAUSEWAY-3752: converts to record TenantedAuthorizationFacetDefault
andi-huber Nov 27, 2025
76d23a8
CAUSEWAY-3752: flattens HideForContextFacet(s)
andi-huber Nov 27, 2025
7e1a221
CAUSEWAY-3752: flattens AuthorizationFacetImpl
andi-huber Nov 27, 2025
81d0557
CAUSEWAY-3752: flattens ActionParameterHiddenFacetViaMethod
andi-huber Nov 27, 2025
e0450b9
CAUSEWAY-3752: removes superfluous interfaces
andi-huber Nov 27, 2025
3300864
CAUSEWAY-3752: more hierarchy cleanup
andi-huber Nov 27, 2025
eb03546
CAUSEWAY-3752: converts to record NavigationFacetFromHiddenType
andi-huber Nov 27, 2025
184abce
CAUSEWAY-3752: aligning facet names for hiding advisors
andi-huber Nov 27, 2025
e381b35
CAUSEWAY-3752: HiddenFacetForFeatureFilter stub
andi-huber Nov 28, 2025
7485517
Merge remote-tracking branch 'origin/main' into 3752-appfeat.filter.s…
andi-huber Dec 3, 2025
f351ca1
CAUSEWAY-3752: backtracking https://github.com/apache/causeway/pull/3264
andi-huber Dec 3, 2025
2135efc
CAUSEWAY-3752: backporting some code from abandoned PR
andi-huber Dec 3, 2025
0504eb9
CAUSEWAY-3752: work on access restriction
andi-huber Dec 6, 2025
23f1340
CAUSEWAY-3752: simplified handling of VisibilityContext providing for
andi-huber Dec 6, 2025
e1d936b
CAUSEWAY-3752: lets viewers provide their visibility contraints
andi-huber Dec 6, 2025
e227dd6
CAUSEWAY-3752: ro cleanup
andi-huber Dec 6, 2025
b15d4d9
CAUSEWAY-3752: work on removal of invalid visibility constraints (1)
andi-huber Dec 6, 2025
989aa58
CAUSEWAY-3752: work on removal of invalid visibility constraints (2)
andi-huber Dec 6, 2025
11f538c
CAUSEWAY-3752: param visibility
andi-huber Dec 6, 2025
320e0a0
CAUSEWAY-3752: more work on invalid vis. contexts
andi-huber Dec 7, 2025
a4337a1
CAUSEWAY-3752: table cell visibility
andi-huber Dec 7, 2025
61ae46c
CAUSEWAY-3752: minor: javadoc
andi-huber Dec 7, 2025
607d911
CAUSEWAY-3752: working on object visibility (1)
andi-huber Dec 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.causeway.applib.services.appfeat;

/**
* The various viewer implementations will individually honor any filters registered with Spring,
* based on a matching qualifier ('Graphql', 'Restful', etc.).
*
* <p>All filters that match a qualifier are consulted until any one rejects the {@link ApplicationFeature}.
*
* <p>If no filters match a qualifier, any {@link ApplicationFeature} is accepted.
*
* <p>'NoViewer' is a reserved string internally used to mean 'no filtering', hence it should not be used to qualify a filter.
*
* @since 4.0 {@index}
*/
@FunctionalInterface
public interface ApplicationFeatureFilter {

/**
* Whether to include given {@link ApplicationFeature}.
*/
boolean filter(ApplicationFeature feature);

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@

import java.util.UUID;

import org.jspecify.annotations.Nullable;
import org.springframework.util.StringUtils;

import org.apache.causeway.applib.annotation.Where;
import org.apache.causeway.applib.services.iactnlayer.InteractionContext;
import org.apache.causeway.commons.collections.Can;
import org.apache.causeway.schema.cmd.v2.CommandDto;
Expand All @@ -31,7 +32,7 @@
/**
* Controls the way that a (synchronous) wrapper works.
*
* @since 2.0 revised for 3.4 {@index}
* @since 2.0 revised for 3.4 and 4.0 {@index}
*/
public record SyncControl(
/**
Expand All @@ -50,7 +51,12 @@ public record SyncControl(
*
* <p>The default behaviour is to rethrow the exception.
*/
ExceptionHandler exceptionHandler) {
ExceptionHandler exceptionHandler,
/**
* Simulated viewerId, honoring feature filtering.
*/
String viewerId,
Where where) {

//TODO can this be further simplified, or is there already an API we can reuse?

Expand Down Expand Up @@ -79,50 +85,50 @@ public void onCommand(
}

public static SyncControl defaults() {
return new SyncControl(false, false, null, null);
return new SyncControl(false, false, null, null, null, null);
}

public SyncControl(
boolean isSkipRules,
boolean isSkipExecute,
@Nullable Can<CommandListener> commandListeners,
@Nullable ExceptionHandler exceptionHandler) {
this.isSkipRules = isSkipRules;
this.isSkipExecute = isSkipExecute;
this.commandListeners = commandListeners!=null
? commandListeners
: Can.empty();
this.exceptionHandler = exceptionHandler!=null
? exceptionHandler
: exception -> { throw exception; };
public SyncControl {
commandListeners = commandListeners!=null
? commandListeners
: Can.empty();
exceptionHandler = exceptionHandler!=null
? exceptionHandler
: exception -> { throw exception; };
viewerId = StringUtils.hasText(viewerId)
? viewerId
: "NoViewer";
where = where!=null
? where
: Where.ANYWHERE;
}

/**
* Skip checking business rules (hide/disable/validate) before
* executing the underlying property or action
*/
public SyncControl withSkipRules() {
return new SyncControl(true, isSkipExecute, commandListeners, exceptionHandler);
return new SyncControl(true, isSkipExecute, commandListeners, exceptionHandler, viewerId, where);
}
public SyncControl withCheckRules() {
return new SyncControl(false, isSkipExecute, commandListeners, exceptionHandler);
return new SyncControl(false, isSkipExecute, commandListeners, exceptionHandler, viewerId, where);
}

/**
* Explicitly set the action to be executed.
*/
public SyncControl withExecute() {
return new SyncControl(isSkipRules, false, commandListeners, exceptionHandler);
return new SyncControl(isSkipRules, false, commandListeners, exceptionHandler, viewerId, where);
}
/**
* Explicitly set the action to <i>not</i> be executed, in other words a 'dry run'.
*/
public SyncControl withNoExecute() {
return new SyncControl(isSkipRules, true, commandListeners, exceptionHandler);
return new SyncControl(isSkipRules, true, commandListeners, exceptionHandler, viewerId, where);
}

public SyncControl listen(@NonNull CommandListener commandListener) {
return new SyncControl(isSkipRules, isSkipExecute, commandListeners.add(commandListener), exceptionHandler);
public SyncControl listen(@NonNull final CommandListener commandListener) {
return new SyncControl(isSkipRules, isSkipExecute, commandListeners.add(commandListener), exceptionHandler, viewerId, where);
}

/**
Expand All @@ -131,13 +137,21 @@ public SyncControl listen(@NonNull CommandListener commandListener) {
* <p>The default behaviour is to rethrow the exception.
*/
public SyncControl withExceptionHandler(final @NonNull ExceptionHandler exceptionHandler) {
return new SyncControl(isSkipRules, isSkipExecute, commandListeners, exceptionHandler);
return new SyncControl(isSkipRules, isSkipExecute, commandListeners, exceptionHandler, viewerId, where);
}

public SyncControl withViewerId(final String viewerId) {
return new SyncControl(isSkipRules, isSkipExecute, commandListeners, exceptionHandler, viewerId, where);
}

public SyncControl withWhere(final Where where) {
return new SyncControl(isSkipRules, isSkipExecute, commandListeners, exceptionHandler, viewerId, where);
}

/**
* @return whether this and other share the same execution mode, ignoring exceptionHandling
*/
public boolean isEquivalent(SyncControl other) {
public boolean isEquivalent(final SyncControl other) {
return this.isSkipExecute == other.isSkipExecute
&& this.isSkipRules == other.isSkipRules;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ public <T> Optional<T> get(final @NonNull Class<T> requiredType) {
* @see #select(Class, Annotation[])
* @see #getSingletonElseFail(Class)
*/
@SuppressWarnings("javadoc")
public <T> Can<T> select(final @NonNull Class<T> requiredType) {
var allMatchingBeans = springContext.getBeanProvider(requiredType)
.orderedStream()
Expand All @@ -128,7 +127,6 @@ public <T> Can<T> select(final @NonNull Class<T> requiredType) {
*
* @see #select(Class)
*/
@SuppressWarnings("javadoc")
public <T> Can<T> select(
final @NonNull Class<T> requiredType,
final @Nullable Annotation[] qualifiers) {
Expand Down Expand Up @@ -165,7 +163,6 @@ public <T> Can<T> select(
* @return IoC managed singleton
* @throws NoSuchElementException - if the singleton is not resolvable
*/
@SuppressWarnings("javadoc")
public <T> T getSingletonElseFail(final @NonNull Class<T> type) {
var candidates = select(type);
if (candidates.getCardinality() == Cardinality.ZERO) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.apache.causeway.commons.net.DataUri;

import lombok.RequiredArgsConstructor;

class DataUriTest {
Expand Down
1 change: 1 addition & 0 deletions core/interaction/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
module org.apache.causeway.core.interaction {
exports org.apache.causeway.core.interaction;
exports org.apache.causeway.core.interaction.core;
exports org.apache.causeway.core.interaction.scope;
exports org.apache.causeway.core.interaction.session;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.causeway.core.metamodel.facets.object.hidden;
package org.apache.causeway.core.interaction.core;

public interface HiddenTypeFacet extends HiddenInstanceFacet {
import java.util.Optional;

import org.apache.causeway.core.metamodel.consent.Consent;

public record AccessRestriction(
boolean canView,
boolean canEdit,
Optional<Consent.VetoReason> vetoReasonOpt
) {
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.causeway.core.interaction.core;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;

import org.jspecify.annotations.Nullable;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.StringUtils;

import org.apache.causeway.applib.services.appfeat.ApplicationFeatureFilter;
import org.apache.causeway.commons.collections.Can;
import org.apache.causeway.commons.internal.base._NullSafe;
import org.apache.causeway.commons.internal.collections._Multimaps;

record ApplicationFeatureFilters(
Can<ApplicationFeatureFilter> unqualifiedFilters,
Map<String, Can<ApplicationFeatureFilter>> filtersByQualifier) {

// -- FACTORIES

static ApplicationFeatureFilters empty() {
return new ApplicationFeatureFilters(Can.empty(), Collections.emptyMap());
}

static ApplicationFeatureFilters collectFrom(final @Nullable ApplicationContext springContext) {
if(springContext==null)
//JUnit Support
return ApplicationFeatureFilters.empty();

var unqualifiedFilters = new ArrayList<ApplicationFeatureFilter>();
var filtersByQualifier = _Multimaps.<String, ApplicationFeatureFilter>newListMultimap();

Stream.of(springContext.getBeanNamesForType(ApplicationFeatureFilter.class))
.forEach(beanName->{
var filterBean = springContext.getBean(beanName, ApplicationFeatureFilter.class);
@SuppressWarnings("deprecation")
Set<Qualifier> qualifiers = AnnotationUtils.getRepeatableAnnotations(filterBean.getClass(), Qualifier.class);
if(!_NullSafe.isEmpty(qualifiers)) {
qualifiers.forEach(qualifier->{
if(StringUtils.hasText(qualifier.value())) {
filtersByQualifier.putElement(qualifier.value(), filterBean);
} else {
unqualifiedFilters.add(filterBean);
}
});
} else {
unqualifiedFilters.add(filterBean);
}
});

// Sanitize (no duplicates, when already in unqualified Can) and make immutable
var unqualifiedFiltersCanned = Can.ofCollection(unqualifiedFilters).distinct();
var filtersByQualifierCanned = new HashMap<String, Can<ApplicationFeatureFilter>>();
filtersByQualifier.forEach((k, v)->filtersByQualifierCanned.put(k, Can.ofCollection(v).distinct().filter(it->!unqualifiedFiltersCanned.contains(it))));
return new ApplicationFeatureFilters(
unqualifiedFiltersCanned,
Collections.unmodifiableMap(filtersByQualifierCanned));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.causeway.core.interaction.core;

import org.apache.causeway.applib.annotation.Where;
import org.apache.causeway.core.metamodel.consent.InteractionInitiatedBy;
import org.apache.causeway.core.metamodel.interactions.VisibilityConstraint;
import org.apache.causeway.core.metamodel.interactions.WhatViewer;

public record InteractionConstraint(
WhatViewer whatViewer,
InteractionInitiatedBy initiatedBy,
Where where
) {

public InteractionConstraint withWhere(final Where where) {
return new InteractionConstraint(whatViewer, initiatedBy, where);
}

public InteractionConstraint withInitiatedBy(final InteractionInitiatedBy initiatedBy) {
return new InteractionConstraint(whatViewer, initiatedBy, where);
}

public VisibilityConstraint asVisibilityConstraint() {
return new VisibilityConstraint(whatViewer, where);
}

}
Loading