This is an automated email from the ASF dual-hosted git repository. ahuber pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/isis.git
commit 5de3c32d619696ee65152c983ead7079fd487e62 Author: Andi Huber <ahu...@apache.org> AuthorDate: Thu Apr 30 07:07:03 2020 +0200 ISIS-2340: share logic of ObjectAdapterAccessHelper/UpdateHelper (14) --- .../spec/interaction/ActionInteraction.java | 149 ++++++++++++++++++--- .../spec/interaction/InteractionVeto.java | 7 +- .../metamodel/spec/interaction/ManagedAction.java | 18 +++ .../spec/interaction/MemberInteraction.java | 17 ++- .../domainobjects/ObjectAndActionInvocation.java | 18 +++ .../rendering/service/RepresentationService.java | 5 +- .../RepresentationServiceContentNegotiator.java | 4 +- .../resources/DomainObjectResourceServerside.java | 18 +-- .../viewer/resources/DomainResourceHelper.java | 86 ++++++------ .../resources/InteractionFailureHandler.java | 86 ++++++------ .../viewer/resources/ObjectActionArgHelper.java | 2 +- 11 files changed, 283 insertions(+), 127 deletions(-) diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/interaction/ActionInteraction.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/interaction/ActionInteraction.java index 9fa7c6a..9d4e2ee 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/interaction/ActionInteraction.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/interaction/ActionInteraction.java @@ -18,12 +18,23 @@ */ package org.apache.isis.core.metamodel.spec.interaction; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import org.apache.isis.core.commons.collections.Can; import org.apache.isis.core.commons.internal.base._Either; import org.apache.isis.core.commons.internal.exceptions._Exceptions; +import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy; +import org.apache.isis.core.metamodel.consent.Veto; import org.apache.isis.core.metamodel.spec.ManagedObject; +import org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter; import org.apache.isis.core.metamodel.spec.interaction.ManagedMember.MemberType; +import lombok.Data; import lombok.NonNull; +import lombok.Value; import lombok.val; public final class ActionInteraction extends MemberInteraction<ManagedAction, ActionInteraction> { @@ -34,33 +45,40 @@ public final class ActionInteraction extends MemberInteraction<ManagedAction, Ac SAFE } + @Value(staticConstructor = "of") + public static class Result { + private final ManagedAction managedAction; + private final List<ManagedObject> parameterList; + private final ManagedObject actionReturnedObject; + } + public static final ActionInteraction start( @NonNull final ManagedObject owner, @NonNull final String memberId) { - + val managedAction = ManagedAction.lookupAction(owner, memberId); - + final _Either<ManagedAction, InteractionVeto> chain = managedAction.isPresent() ? _Either.left(managedAction.get()) : _Either.right(InteractionVeto.notFound(MemberType.ACTION, memberId)); - + return new ActionInteraction(chain); } - + ActionInteraction(@NonNull _Either<ManagedAction, InteractionVeto> chain) { super(chain); } public ActionInteraction checkSemanticConstraint(@NonNull SemanticConstraint semanticConstraint) { - + chain = chain.leftRemap(action->{ - + val actionSemantics = action.getAction().getSemantics(); - + switch(semanticConstraint) { case NONE: return _Either.left(action); - + case IDEMPOTENT: return (! actionSemantics.isIdempotentInNature()) ? _Either.right(InteractionVeto.actionNotIdempotent(action)) @@ -72,24 +90,113 @@ public final class ActionInteraction extends MemberInteraction<ManagedAction, Ac default: throw _Exceptions.unmatchedCase(semanticConstraint); // unexpected code reach } - + }); - + return this; } + @Value(staticConstructor = "of") + public static class ManagedParameter { + @NonNull private final ManagedAction owningAction; + @NonNull private final ObjectActionParameter parameter; + @NonNull private final ManagedObject value; + public ManagedObject getOwningObject() { + return getOwningAction().getOwner(); + } + //TODO unchecked length + public static Can<ManagedParameter> listOf(ManagedAction owningAction, List<ManagedObject> paramValueList) { + val paramValueIterator = paramValueList.iterator(); + return owningAction.getAction().getParameters() + .map(param->{ + final ManagedObject paramValue = Optional + .ofNullable(paramValueIterator.next()) + .orElse(ManagedObject.of(param.getSpecification(), null)); + return ManagedParameter.of(owningAction, param, paramValue); + }); + } + public Optional<InteractionVeto> validate() { + return Optional.ofNullable( + getParameter() + .isValid(getOwningObject(), getValue(), InteractionInitiatedBy.USER)) + .map(reasonNotValid->InteractionVeto.actionParamInvalid(new Veto(reasonNotValid))); + } + } + + public static interface ParameterInvalidCallback { + void onParameterInvalid(ManagedParameter managedParameter, InteractionVeto veto); + } + + + public ActionInteraction useParameters( + @NonNull final Function<ManagedAction, List<ManagedObject>> actionParameterProvider, + final ParameterInvalidCallback parameterInvalidCallback) { -// public PropertyHandle modifyProperty( -// @NonNull final Function<ManagedProperty, ManagedObject> newProperyValueProvider) { -// -// chain = chain.leftRemap(property->{ -// val validityVeto = property.modifyProperty(newProperyValueProvider.apply(property)); -// return validityVeto.isPresent() -// ? _Either.right(validityVeto.get()) -// : _Either.left(property); -// }); -// return this; -// } + chain = chain.leftRemap(action->{ + + state.setParameterList(actionParameterProvider.apply(action)); + + val managedParameters = ManagedParameter.listOf(action, state.getParameterList()); + + boolean invalid = false; + for(val managedParameter : managedParameters) { + // validate each individual argument + val veto = managedParameter.validate(); + if(veto.isPresent()) { + invalid = true; + if(parameterInvalidCallback!=null) { + parameterInvalidCallback.onParameterInvalid(managedParameter, veto.get()); + } + } + } + + if(invalid) { + //TODO veto + } + + // validate entire param-list + val validityVeto = action.getAction() + .isArgumentSetValid(action.getOwner(), state.getParameterList(), InteractionInitiatedBy.USER); + return validityVeto.isVetoed() + ? _Either.right(InteractionVeto.actionParamInvalid(validityVeto)) + : _Either.left(action); + + }); + return this; + } + + public <X extends Throwable> + Result getResultElseThrow(Function<InteractionVeto, ? extends X> onFailure) throws X { + + chain = chain.leftRemap(action->{ + val actionResultOrVeto = action.invoke(state.getParameterList()); + + if(actionResultOrVeto.isLeft()) { + val actionResult = actionResultOrVeto.leftIfAny(); + state.setInteractionResult(Result.of(action, state.getParameterList(), actionResult)); + return _Either.left(action); + } else { + return _Either.right(actionResultOrVeto.rightIfAny()); + } + + }); + + if (chain.isLeft()) { + return state.getInteractionResult(); + } else { + throw onFailure.apply(chain.rightIfAny()); + } + } + + // -- HELPER + + private final State state = new State(); + @Data + private static class State { + @NonNull private List<ManagedObject> parameterList = Collections.emptyList(); + private Result interactionResult; + } + } diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/interaction/InteractionVeto.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/interaction/InteractionVeto.java index e323bbb..547afc5 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/interaction/InteractionVeto.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/interaction/InteractionVeto.java @@ -45,7 +45,8 @@ public class InteractionVeto implements Serializable { INVALID, ACTION_NOT_SAFE, - ACTION_NOT_IDEMPOTENT, + ACTION_NOT_IDEMPOTENT, + ACTION_PARAM_INVALID, } @NonNull private final VetoType vetoType; @@ -83,6 +84,10 @@ public class InteractionVeto implements Serializable { action.getId()); return of(VetoType.ACTION_NOT_IDEMPOTENT, new Veto(reason)); } + + public static InteractionVeto actionParamInvalid(@NonNull Consent vetoConsent) { + return of(VetoType.ACTION_PARAM_INVALID, vetoConsent); + } public String getReason() { return getVetoConsent().getReason(); diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/interaction/ManagedAction.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/interaction/ManagedAction.java index a849770..434fb95 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/interaction/ManagedAction.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/interaction/ManagedAction.java @@ -18,13 +18,17 @@ */ package org.apache.isis.core.metamodel.spec.interaction; +import java.util.List; import java.util.Optional; +import org.apache.isis.core.commons.internal.base._Either; +import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy; import org.apache.isis.core.metamodel.spec.ManagedObject; import org.apache.isis.core.metamodel.spec.feature.ObjectAction; import lombok.Getter; import lombok.NonNull; +import lombok.val; public final class ManagedAction extends ManagedMember { @@ -65,6 +69,20 @@ public final class ManagedAction extends ManagedMember { public MemberType getMemberType() { return MemberType.ACTION; } + + // -- INTERACTION + + public _Either<ManagedObject, InteractionVeto> invoke(@NonNull List<ManagedObject> actionParameters) { + + //TODO validate params, and handle invocation exceptions + + final ManagedObject mixedInAdapter = null; // filled in automatically ? + val actionResult = getAction() + .execute(getOwner(), mixedInAdapter , actionParameters, InteractionInitiatedBy.USER); + + return _Either.left(actionResult); + + } } diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/interaction/MemberInteraction.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/interaction/MemberInteraction.java index 242a614..8612cbb 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/interaction/MemberInteraction.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/interaction/MemberInteraction.java @@ -19,7 +19,6 @@ package org.apache.isis.core.metamodel.spec.interaction; import java.util.Optional; -import java.util.function.Consumer; import java.util.function.Function; import org.apache.isis.applib.annotation.Where; @@ -77,13 +76,18 @@ public abstract class MemberInteraction<T extends ManagedMember, H extends Membe } return _Casts.uncheckedCast(this); } - - @Deprecated - public H peekOnFailure(Consumer<InteractionVeto> onFailure) { - chain.right().ifPresent(onFailure); - return _Casts.uncheckedCast(this); + + public <X extends Throwable> + H validateElseThrow(Function<InteractionVeto, ? extends X> onFailure) throws X { + val veto = chain.rightIfAny(); + if (veto == null) { + return _Casts.uncheckedCast(this); + } else { + throw onFailure.apply(veto); + } } + @Deprecated // use more specialized methods public <X extends Throwable> T getOrElseThrow(Function<InteractionVeto, ? extends X> onFailure) throws X { val value = chain.leftIfAny(); @@ -94,6 +98,7 @@ public abstract class MemberInteraction<T extends ManagedMember, H extends Membe } } + @Deprecated public Optional<T> get() { return chain.left(); } diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndActionInvocation.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndActionInvocation.java index 07d0ba1..f541a6e 100644 --- a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndActionInvocation.java +++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectAndActionInvocation.java @@ -25,11 +25,27 @@ import org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet; import org.apache.isis.core.metamodel.spec.ManagedObject; import org.apache.isis.core.metamodel.spec.ObjectSpecification; import org.apache.isis.core.metamodel.spec.feature.ObjectAction; +import org.apache.isis.core.metamodel.spec.interaction.ActionInteraction; import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation; import org.apache.isis.viewer.restfulobjects.applib.domainobjects.ActionResultRepresentation; +import lombok.NonNull; + public class ObjectAndActionInvocation { + public static ObjectAndActionInvocation of( + @NonNull ActionInteraction.Result actionInteractionResult, + @NonNull JsonRepresentation argsJsonRepr, + @NonNull ActionResultReprRenderer.SelfLink selfLink) { + return new ObjectAndActionInvocation( + actionInteractionResult.getManagedAction().getOwner(), + actionInteractionResult.getManagedAction().getAction(), + argsJsonRepr, + actionInteractionResult.getParameterList(), + actionInteractionResult.getActionReturnedObject(), + selfLink); + } + private final ManagedObject objectAdapter; private final ObjectAction action; private final JsonRepresentation arguments; @@ -103,4 +119,6 @@ public class ObjectAndActionInvocation { return ActionResultRepresentation.ResultType.DOMAIN_OBJECT; } + + } \ No newline at end of file diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/RepresentationService.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/RepresentationService.java index 9462088..4f6d777 100644 --- a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/RepresentationService.java +++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/RepresentationService.java @@ -25,7 +25,6 @@ import org.apache.isis.core.metamodel.spec.interaction.ManagedAction; import org.apache.isis.core.metamodel.spec.interaction.ManagedCollection; import org.apache.isis.core.metamodel.spec.interaction.ManagedProperty; import org.apache.isis.viewer.restfulobjects.rendering.IResourceContext; -import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ActionResultReprRenderer; import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectAndActionInvocation; /** @@ -75,8 +74,6 @@ public interface RepresentationService { Response actionResult( IResourceContext resourceContext, - ObjectAndActionInvocation objectAndActionInvocation, - ActionResultReprRenderer.SelfLink selfLink); - + ObjectAndActionInvocation objectAndActionInvocation); } diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/RepresentationServiceContentNegotiator.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/RepresentationServiceContentNegotiator.java index 53434ee..bd0b518 100644 --- a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/RepresentationServiceContentNegotiator.java +++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/RepresentationServiceContentNegotiator.java @@ -37,7 +37,6 @@ import org.apache.isis.core.metamodel.spec.interaction.ManagedAction; import org.apache.isis.core.metamodel.spec.interaction.ManagedCollection; import org.apache.isis.core.metamodel.spec.interaction.ManagedProperty; import org.apache.isis.viewer.restfulobjects.rendering.IResourceContext; -import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ActionResultReprRenderer.SelfLink; import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectAndActionInvocation; import org.apache.isis.viewer.restfulobjects.rendering.service.conneg.ContentNegotiationService; import org.apache.isis.viewer.restfulobjects.rendering.service.conneg.ContentNegotiationServiceForRestfulObjectsV1_0; @@ -109,8 +108,7 @@ public class RepresentationServiceContentNegotiator implements RepresentationSer @Override public Response actionResult( final IResourceContext renderContext, - final ObjectAndActionInvocation objectAndActionInvocation, - final SelfLink selfLink) { + final ObjectAndActionInvocation objectAndActionInvocation) { final ResponseBuilder responseBuilder = buildResponse( connegService -> connegService.buildResponse(renderContext, objectAndActionInvocation)); diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java index 506b9d2..dc8f0ee 100644 --- a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java +++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainObjectResourceServerside.java @@ -460,13 +460,10 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements return proposedNewValue; }) - .getOrElseThrow(InteractionFailureHandler::onFailure); + .validateElseThrow(InteractionFailureHandler::onFailure); - val domainResourceHelper = DomainResourceHelper.ofObjectResource(resourceContext, objectAdapter); - return domainResourceHelper.propertyDetails( - propertyId, - ManagedMember.RepresentationMode.WRITE - ); + return DomainResourceHelper.ofObjectResource(resourceContext, objectAdapter) + .propertyDetails(propertyId, ManagedMember.RepresentationMode.WRITE); } @Override @@ -490,13 +487,10 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements .checkVisibility(resourceContext.getWhere()) .checkUsability(resourceContext.getWhere(), AccessIntent.MUTATE) .modifyProperty(property->null) - .getOrElseThrow(InteractionFailureHandler::onFailure); + .validateElseThrow(InteractionFailureHandler::onFailure); - val domainResourceHelper = DomainResourceHelper.ofObjectResource(resourceContext, objectAdapter); - return domainResourceHelper.propertyDetails( - propertyId, - ManagedMember.RepresentationMode.WRITE - ); + return DomainResourceHelper.ofObjectResource(resourceContext, objectAdapter) + .propertyDetails(propertyId, ManagedMember.RepresentationMode.WRITE); } @Override diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainResourceHelper.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainResourceHelper.java index befd24a..3dd27f0 100644 --- a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainResourceHelper.java +++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainResourceHelper.java @@ -22,10 +22,11 @@ import javax.ws.rs.core.Response; import org.apache.isis.applib.annotation.SemanticsOf; import org.apache.isis.applib.services.xactn.TransactionService; -import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy; import org.apache.isis.core.metamodel.spec.ManagedObject; +import org.apache.isis.core.metamodel.spec.interaction.ActionInteraction; +import org.apache.isis.core.metamodel.spec.interaction.InteractionVeto; +import org.apache.isis.core.metamodel.spec.interaction.ActionInteraction.ManagedParameter; import org.apache.isis.core.metamodel.spec.interaction.ActionInteraction.SemanticConstraint; -import org.apache.isis.core.metamodel.spec.interaction.ManagedAction; import org.apache.isis.core.metamodel.spec.interaction.ManagedMember; import org.apache.isis.core.metamodel.spec.interaction.MemberInteraction.AccessIntent; import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation; @@ -38,6 +39,7 @@ import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectAndAc import org.apache.isis.viewer.restfulobjects.rendering.service.RepresentationService; import org.apache.isis.viewer.restfulobjects.viewer.context.ResourceContext; +import lombok.NonNull; import lombok.val; class DomainResourceHelper { @@ -155,11 +157,9 @@ class DomainResourceHelper { */ public Response invokeActionQueryOnly(final String actionId, final JsonRepresentation arguments) { - val action = ObjectAdapterAccessHelper.of(resourceContext, objectAdapter) - .getObjectActionThatIsVisibleForIntentAndSemanticConstraint( - actionId, AccessIntent.MUTATE, SemanticConstraint.SAFE); - - return invokeActionUsingAdapters(action, arguments, ActionResultReprRenderer.SelfLink.INCLUDED); + return invokeAction( + actionId, AccessIntent.MUTATE, SemanticConstraint.SAFE, + arguments, ActionResultReprRenderer.SelfLink.EXCLUDED); } /** @@ -174,11 +174,9 @@ class DomainResourceHelper { */ public Response invokeActionIdempotent(final String actionId, final JsonRepresentation arguments) { - val action = ObjectAdapterAccessHelper.of(resourceContext, objectAdapter) - .getObjectActionThatIsVisibleForIntentAndSemanticConstraint( - actionId, AccessIntent.MUTATE, SemanticConstraint.IDEMPOTENT); - - return invokeActionUsingAdapters(action, arguments, ActionResultReprRenderer.SelfLink.EXCLUDED); + return invokeAction( + actionId, AccessIntent.MUTATE, SemanticConstraint.IDEMPOTENT, + arguments, ActionResultReprRenderer.SelfLink.EXCLUDED); } /** @@ -188,42 +186,50 @@ class DomainResourceHelper { */ public Response invokeAction(final String actionId, final JsonRepresentation arguments) { - val action = ObjectAdapterAccessHelper.of(resourceContext, objectAdapter) - .getObjectActionThatIsVisibleForIntentAndSemanticConstraint( - actionId, AccessIntent.MUTATE, SemanticConstraint.NONE); - - return invokeActionUsingAdapters(action, arguments, ActionResultReprRenderer.SelfLink.EXCLUDED); + return invokeAction( + actionId, AccessIntent.MUTATE, SemanticConstraint.NONE, + arguments, ActionResultReprRenderer.SelfLink.EXCLUDED); } - - private Response invokeActionUsingAdapters( - final ManagedAction managedAction, - final JsonRepresentation arguments, - final ActionResultReprRenderer.SelfLink selfLink) { - - val action = managedAction.getAction(); - val objectAdapter = this.objectAdapter; - val argHelper = new ObjectActionArgHelper(resourceContext, managedAction); - val argAdapters = argHelper.parseAndValidateArguments(arguments); - + private Response invokeAction( + @NonNull final String actionId, + @NonNull final AccessIntent intent, + @NonNull final SemanticConstraint semanticConstraint, + @NonNull final JsonRepresentation arguments, + @NonNull final ActionResultReprRenderer.SelfLink selfLink) { + + val where = resourceContext.getWhere(); + + val actionInteraction = ActionInteraction.start(objectAdapter, actionId) + .checkVisibility(where) + .checkUsability(where, intent) + .checkSemanticConstraint(semanticConstraint) + .useParameters(action->{ + + val argAdapters = ObjectActionArgHelper.of(resourceContext, action) + .parseAndValidateArguments(arguments); + + return argAdapters; + + }, + (ManagedParameter managedParameter, InteractionVeto veto)->{ + InteractionFailureHandler.onParameterInvalid(managedParameter, veto, arguments); + } + ); + if(resourceContext.isValidateOnly()) { - // nothing more to do. - // if there had been a validation error, then an exception would have been thrown above. + actionInteraction.validateElseThrow(InteractionFailureHandler::onFailure); return Response.noContent().build(); } - - // invoke - final ManagedObject mixedInAdapter = null; // action will automatically fill in if a mixin - final ManagedObject returnedAdapter = action.execute( - objectAdapter, mixedInAdapter, argAdapters, - InteractionInitiatedBy.USER); - - final ObjectAndActionInvocation objectAndActionInvocation = - new ObjectAndActionInvocation(objectAdapter, action, arguments, argAdapters, returnedAdapter, selfLink); + + val actionInteractionResult = actionInteraction + .getResultElseThrow(InteractionFailureHandler::onFailure); + + val objectAndActionInvocation = ObjectAndActionInvocation.of(actionInteractionResult, arguments, selfLink); // response transactionService.flushTransaction(); - return representationService.actionResult(resourceContext, objectAndActionInvocation, selfLink); + return representationService.actionResult(resourceContext, objectAndActionInvocation); } diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/InteractionFailureHandler.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/InteractionFailureHandler.java index e12199b..0759dfc 100644 --- a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/InteractionFailureHandler.java +++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/InteractionFailureHandler.java @@ -20,68 +20,76 @@ package org.apache.isis.viewer.restfulobjects.viewer.resources; import javax.annotation.Nullable; +import org.apache.isis.core.metamodel.spec.interaction.ActionInteraction.ManagedParameter; import org.apache.isis.core.metamodel.spec.interaction.InteractionVeto; +import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation; import org.apache.isis.viewer.restfulobjects.applib.RestfulResponse; import org.apache.isis.viewer.restfulobjects.rendering.RestfulObjectsApplicationException; +import lombok.val; + public class InteractionFailureHandler { - public static RestfulObjectsApplicationException onFailure(@Nullable final InteractionVeto failure) { - - if(failure==null) { + public static RestfulObjectsApplicationException onFailure( + @Nullable final InteractionVeto veto) { + + if(veto==null) { return RestfulObjectsApplicationException .createWithMessage(RestfulResponse.HttpStatusCode.INTERNAL_SERVER_ERROR, "unexpected empty failure holder"); } - - switch(failure.getVetoType()) { + + switch(veto.getVetoType()) { case NOT_FOUND: case HIDDEN: return RestfulObjectsApplicationException - .createWithMessage(RestfulResponse.HttpStatusCode.NOT_FOUND, - failure.getReason()); + .createWithMessage(RestfulResponse.HttpStatusCode.NOT_FOUND, + veto.getReason()); + case READONLY: case INVALID: return RestfulObjectsApplicationException - .createWithMessage(RestfulResponse.HttpStatusCode.FORBIDDEN, - failure.getReason()); - + .createWithMessage(RestfulResponse.HttpStatusCode.FORBIDDEN, + veto.getReason()); + case ACTION_NOT_SAFE: case ACTION_NOT_IDEMPOTENT: return RestfulObjectsApplicationException - .createWithMessage(RestfulResponse.HttpStatusCode.METHOD_NOT_ALLOWED, - failure.getReason()); + .createWithMessage(RestfulResponse.HttpStatusCode.METHOD_NOT_ALLOWED, + veto.getReason()); + + case ACTION_PARAM_INVALID: + return RestfulObjectsApplicationException + .createWithMessage(RestfulResponse.HttpStatusCode.VALIDATION_FAILED, + veto.getReason()); } - + return RestfulObjectsApplicationException .createWithMessage(RestfulResponse.HttpStatusCode.INTERNAL_SERVER_ERROR, - "unmatched veto type " + failure.getVetoType()); - + "unmatched veto type " + veto.getVetoType()); + } - - -// public static void onFailure(@Nullable final InteractionVeto failure) { -// -// if(failure==null) { -// return; -// } -// -// switch(failure.getVetoType()) { -// case NOT_FOUND: -// case HIDDEN: -// throw RestfulObjectsApplicationException -// .createWithMessage(RestfulResponse.HttpStatusCode.NOT_FOUND, -// failure.getReason()); -// case READONLY: -// case INVALID: -// throw RestfulObjectsApplicationException -// .createWithMessage(RestfulResponse.HttpStatusCode.FORBIDDEN, -// failure.getReason()); -// } -// -// } - - + public static RestfulObjectsApplicationException onParameterListInvalid( + @Nullable final InteractionVeto veto, + @Nullable final JsonRepresentation arguments) { + + arguments.mapPut("x-ro-invalidReason", veto.getReason()); + return RestfulObjectsApplicationException + .createWithBody(RestfulResponse.HttpStatusCode.VALIDATION_FAILED, + arguments, + "Validation failed, see body for details"); + } + // collect info for each individual param that is not valid + public static void onParameterInvalid( + @Nullable final ManagedParameter managedParameter, + @Nullable final InteractionVeto veto, + @Nullable final JsonRepresentation arguments) { + + val paramId = managedParameter.getParameter().getId(); + val argRepr = arguments.getRepresentation(paramId); + argRepr.mapPut("invalidReason", veto.getReason()); + } + } diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/ObjectActionArgHelper.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/ObjectActionArgHelper.java index 9929406..50e1638 100644 --- a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/ObjectActionArgHelper.java +++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/ObjectActionArgHelper.java @@ -41,7 +41,7 @@ import lombok.val; * Utility class that encapsulates the logic for parsing arguments to be invoked by an * {@link ObjectAction}. */ -@RequiredArgsConstructor +@RequiredArgsConstructor(staticName = "of") public class ObjectActionArgHelper { private final IResourceContext resourceContext;