/* * 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.isis.core.metamodel.facets.actions.action; import java.lang.reflect.Method; import org.apache.isis.applib.annotation.Action; import org.apache.isis.applib.annotation.ActionInteraction; import org.apache.isis.applib.annotation.ActionSemantics; import org.apache.isis.applib.annotation.Bulk; import org.apache.isis.applib.annotation.Command; import org.apache.isis.applib.annotation.Disabled; import org.apache.isis.applib.annotation.Hidden; import org.apache.isis.applib.annotation.Idempotent; import org.apache.isis.applib.annotation.PostsActionInvokedEvent; import org.apache.isis.applib.annotation.Prototype; import org.apache.isis.applib.annotation.PublishedAction; import org.apache.isis.applib.annotation.QueryOnly; import org.apache.isis.applib.annotation.TypeOf; import org.apache.isis.applib.services.HasTransactionId; import org.apache.isis.applib.services.eventbus.ActionDomainEvent; import org.apache.isis.applib.services.eventbus.ActionInvokedEvent; import org.apache.isis.core.commons.config.IsisConfiguration; import org.apache.isis.core.metamodel.facetapi.FacetHolder; import org.apache.isis.core.metamodel.facetapi.FacetUtil; import org.apache.isis.core.metamodel.facetapi.FeatureType; import org.apache.isis.core.metamodel.facetapi.MetaModelValidatorRefiner; import org.apache.isis.core.metamodel.facets.Annotations; import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract; import org.apache.isis.core.metamodel.facets.FacetedMethod; import org.apache.isis.core.metamodel.facets.actcoll.typeof.TypeOfFacet; import org.apache.isis.core.metamodel.facets.actions.action.bulk.BulkFacetForActionAnnotation; import org.apache.isis.core.metamodel.facets.actions.action.bulk.BulkFacetForBulkAnnotation; import org.apache.isis.core.metamodel.facets.actions.action.bulk.BulkFacetObjectOnly; import org.apache.isis.core.metamodel.facets.actions.action.command.CommandFacetForActionAnnotation; import org.apache.isis.core.metamodel.facets.actions.action.command.CommandFacetForCommandAnnotation; import org.apache.isis.core.metamodel.facets.actions.action.disabled.DisabledFacetForDisabledAnnotationOnAction; import org.apache.isis.core.metamodel.facets.actions.action.hidden.HiddenFacetForActionAnnotation; import org.apache.isis.core.metamodel.facets.actions.action.hidden.HiddenFacetForHiddenAnnotationOnAction; import org.apache.isis.core.metamodel.facets.actions.action.invocation.ActionDomainEventFacetAbstract; import org.apache.isis.core.metamodel.facets.actions.action.invocation.ActionDomainEventFacetDefault; import org.apache.isis.core.metamodel.facets.actions.action.invocation.ActionDomainEventFacetForActionAnnotation; import org.apache.isis.core.metamodel.facets.actions.action.invocation.ActionDomainEventFacetForActionInteractionAnnotation; import org.apache.isis.core.metamodel.facets.actions.action.invocation.ActionInvocationFacetForDomainEventAbstract; import org.apache.isis.core.metamodel.facets.actions.action.invocation.ActionInvocationFacetForDomainEventFromActionAnnotation; import org.apache.isis.core.metamodel.facets.actions.action.invocation.ActionInvocationFacetForDomainEventFromActionInteractionAnnotation; import org.apache.isis.core.metamodel.facets.actions.action.invocation.ActionInvocationFacetForDomainEventFromDefault; import org.apache.isis.core.metamodel.facets.actions.action.invocation.ActionInvocationFacetForPostsActionInvokedEventAnnotation; import org.apache.isis.core.metamodel.facets.actions.action.prototype.PrototypeFacetForActionAnnotation; import org.apache.isis.core.metamodel.facets.actions.action.prototype.PrototypeFacetForPrototypeAnnotation; import org.apache.isis.core.metamodel.facets.actions.action.publishing.PublishedActionFacetForActionAnnotation; import org.apache.isis.core.metamodel.facets.actions.action.publishing.PublishedActionFacetForPublishedActionAnnotation; import org.apache.isis.core.metamodel.facets.actions.action.semantics.ActionSemanticsFacetFallbackToNonIdempotent; import org.apache.isis.core.metamodel.facets.actions.action.semantics.ActionSemanticsFacetForActionAnnotation; import org.apache.isis.core.metamodel.facets.actions.action.semantics.ActionSemanticsFacetForActionSemanticsAnnotation; import org.apache.isis.core.metamodel.facets.actions.action.semantics.ActionSemanticsFacetFromIdempotentAnnotation; import org.apache.isis.core.metamodel.facets.actions.action.semantics.ActionSemanticsFacetFromQueryOnlyAnnotation; import org.apache.isis.core.metamodel.facets.actions.action.typeof.TypeOfFacetForActionAnnotation; import org.apache.isis.core.metamodel.facets.actions.action.typeof.TypeOfFacetOnActionForTypeOfAnnotation; import org.apache.isis.core.metamodel.facets.actions.bulk.BulkFacet; import org.apache.isis.core.metamodel.facets.actions.command.CommandFacet; import org.apache.isis.core.metamodel.facets.actions.prototype.PrototypeFacet; import org.apache.isis.core.metamodel.facets.actions.publish.PublishedActionFacet; import org.apache.isis.core.metamodel.facets.actions.semantics.ActionSemanticsFacet; import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet; import org.apache.isis.core.metamodel.facets.members.disabled.DisabledFacet; import org.apache.isis.core.metamodel.services.ServicesInjector; import org.apache.isis.core.metamodel.spec.ObjectSpecification; import org.apache.isis.core.metamodel.specloader.CollectionUtils; import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorComposite; import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorForDeprecatedAnnotation; import org.apache.isis.core.metamodel.util.EventUtil; public class ActionAnnotationFacetFactory extends FacetFactoryAbstract implements MetaModelValidatorRefiner { private final MetaModelValidatorForDeprecatedAnnotation actionSemanticsValidator = new MetaModelValidatorForDeprecatedAnnotation(ActionSemantics.class); private final MetaModelValidatorForDeprecatedAnnotation actionInteractionValidator = new MetaModelValidatorForDeprecatedAnnotation(ActionInteraction.class); private final MetaModelValidatorForDeprecatedAnnotation postsActionInvokedEventValidator = new MetaModelValidatorForDeprecatedAnnotation(PostsActionInvokedEvent.class); private final MetaModelValidatorForDeprecatedAnnotation bulkValidator = new MetaModelValidatorForDeprecatedAnnotation(Bulk.class); private final MetaModelValidatorForDeprecatedAnnotation commandValidator = new MetaModelValidatorForDeprecatedAnnotation(Command.class); private final MetaModelValidatorForDeprecatedAnnotation queryOnlyValidator = new MetaModelValidatorForDeprecatedAnnotation(QueryOnly.class); private final MetaModelValidatorForDeprecatedAnnotation idempotentValidator = new MetaModelValidatorForDeprecatedAnnotation(Idempotent.class); private final MetaModelValidatorForDeprecatedAnnotation publishedActionValidator = new MetaModelValidatorForDeprecatedAnnotation(PublishedAction.class); private final MetaModelValidatorForDeprecatedAnnotation typeOfValidator = new MetaModelValidatorForDeprecatedAnnotation(TypeOf.class); private final MetaModelValidatorForDeprecatedAnnotation hiddenValidator = new MetaModelValidatorForDeprecatedAnnotation(Hidden.class); private final MetaModelValidatorForDeprecatedAnnotation disabledValidator = new MetaModelValidatorForDeprecatedAnnotation(Disabled.class); private final MetaModelValidatorForDeprecatedAnnotation prototypeValidator = new MetaModelValidatorForDeprecatedAnnotation(Prototype.class); public ActionAnnotationFacetFactory() { super(FeatureType.ACTIONS_ONLY); } @Override public void process(final ProcessMethodContext processMethodContext) { processInvocation(processMethodContext); processHidden(processMethodContext); processDisabled(processMethodContext); processRestrictTo(processMethodContext); processSemantics(processMethodContext); processBulk(processMethodContext); // must come after processing semantics processCommand(processMethodContext); // must come after processing semantics processPublishing(processMethodContext); processTypeOf(processMethodContext); } void processInvocation(final ProcessMethodContext processMethodContext) { final Method actionMethod = processMethodContext.getMethod(); try { final Class<?> returnType = actionMethod.getReturnType(); final ObjectSpecification returnSpec = getSpecificationLoader().loadSpecification(returnType); if (returnSpec == null) { return; } final Class<?> cls = processMethodContext.getCls(); final ObjectSpecification typeSpec = getSpecificationLoader().loadSpecification(cls); final FacetHolder holder = processMethodContext.getFacetHolder(); // // Set up ActionDomainEventFacet, which will act as the hiding/disabling/validating advisor // final PostsActionInvokedEvent postsActionInvokedEvent = Annotations.getAnnotation(actionMethod, PostsActionInvokedEvent.class); final ActionInteraction actionInteraction =Annotations.getAnnotation(actionMethod, ActionInteraction.class); final Action action = Annotations.getAnnotation(actionMethod, Action.class); final Class<? extends ActionDomainEvent<?>> actionDomainEventType; final ActionDomainEventFacetAbstract actionDomainEventFacet; // can't really do this, because would result in the event being fired for the // hidden/disable/validate phases, most likely breaking existing code. // // search for @PostsActionInvoked(value=...) // if(postsActionInvokedEvent != null) { // actionDomainEventType = postsActionInvokedEvent.value(); // actionDomainEventFacet = new ActionDomainEventFacetForPostsActionInvokedEventAnnotation( // actionDomainEventType, servicesInjector, getSpecificationLoader(), holder); // } else // search for @ActionInteraction(value=...) if(actionInteraction != null) { actionDomainEventType = actionInteraction.value(); actionDomainEventFacet = new ActionDomainEventFacetForActionInteractionAnnotation( actionDomainEventType, servicesInjector, getSpecificationLoader(), holder); } else // search for @Action(domainEvent=...) if(action != null && action.domainEvent() != null) { actionDomainEventType = action.domainEvent(); actionDomainEventFacet = new ActionDomainEventFacetForActionAnnotation( actionDomainEventType, servicesInjector, getSpecificationLoader(), holder); } else // else use default event type { actionDomainEventType = ActionDomainEvent.Default.class; actionDomainEventFacet = new ActionDomainEventFacetDefault( actionDomainEventType, servicesInjector, getSpecificationLoader(), holder); } if(EventUtil.eventTypeIsPostable( actionDomainEventFacet.getEventType(), ActionDomainEvent.Noop.class, ActionDomainEvent.Default.class, "isis.reflector.facet.actionAnnotation.domainEvent.postForDefault", getConfiguration())) { FacetUtil.addFacet(actionDomainEventFacet); } // replace the current actionInvocationFacet with one that will // emit the appropriate domain event and then delegate onto the underlying final ActionInvocationFacetForDomainEventAbstract actionInvocationFacet; // deprecated if (postsActionInvokedEvent != null) { final Class<? extends ActionInvokedEvent<?>> actionInvokedEventType = postsActionInvokedEvent.value(); actionInvocationFacet = actionInteractionValidator.flagIfPresent( new ActionInvocationFacetForPostsActionInvokedEventAnnotation( actionInvokedEventType, actionMethod, typeSpec, returnSpec, holder, servicesInjector ), processMethodContext); } else // deprecated (but more recently) if (actionInteraction != null) { actionInvocationFacet = actionInteractionValidator.flagIfPresent( new ActionInvocationFacetForDomainEventFromActionInteractionAnnotation( actionDomainEventType, actionMethod, typeSpec, returnSpec, holder, servicesInjector ), processMethodContext); } else // current if (action != null) { actionInvocationFacet = new ActionInvocationFacetForDomainEventFromActionAnnotation( actionDomainEventType, actionMethod, typeSpec, returnSpec, holder, servicesInjector ); } else // default { actionInvocationFacet = new ActionInvocationFacetForDomainEventFromDefault( actionDomainEventType, actionMethod, typeSpec, returnSpec, holder, servicesInjector ); } FacetUtil.addFacet(actionInvocationFacet); } finally { processMethodContext.removeMethod(actionMethod); } } void processHidden(final ProcessMethodContext processMethodContext) { final Method method = processMethodContext.getMethod(); final FacetHolder holder = processMethodContext.getFacetHolder(); // check for deprecated @Hidden final Hidden hiddenAnnotation = Annotations.getAnnotation(processMethodContext.getMethod(), Hidden.class); HiddenFacet facet = hiddenValidator .flagIfPresent(HiddenFacetForHiddenAnnotationOnAction.create(hiddenAnnotation, holder), processMethodContext); // else search for @Action(hidden=...) final Action action = Annotations.getAnnotation(method, Action.class); if(facet == null) { facet = HiddenFacetForActionAnnotation.create(action, holder); } FacetUtil.addFacet(facet); } void processDisabled(final ProcessMethodContext processMethodContext) { final Method method = processMethodContext.getMethod(); final FacetHolder holder = processMethodContext.getFacetHolder(); // check for deprecated @Disabled final Disabled annotation = Annotations.getAnnotation(method, Disabled.class); DisabledFacet facet = disabledValidator .flagIfPresent(DisabledFacetForDisabledAnnotationOnAction.create(annotation, holder), processMethodContext); // there is no equivalent in @Action(...) FacetUtil.addFacet(facet); } void processRestrictTo(final ProcessMethodContext processMethodContext) { final Method method = processMethodContext.getMethod(); final FacetHolder holder = processMethodContext.getFacetHolder(); // check for deprecated @Prototype final Prototype annotation = Annotations.getAnnotation(method, Prototype.class); final PrototypeFacet facet1 = PrototypeFacetForPrototypeAnnotation.create(annotation, holder, getDeploymentCategory()); FacetUtil.addFacet(prototypeValidator.flagIfPresent(facet1, processMethodContext)); PrototypeFacet facet = facet1; // else search for @Action(restrictTo=...) final Action action = Annotations.getAnnotation(method, Action.class); if(facet == null) { facet = PrototypeFacetForActionAnnotation.create(action, holder, getDeploymentCategory()); } FacetUtil.addFacet(facet); } void processSemantics(final ProcessMethodContext processMethodContext) { final Method method = processMethodContext.getMethod(); final FacetHolder holder = processMethodContext.getFacetHolder(); ActionSemanticsFacet facet; // check for the deprecated @QueryOnly... final QueryOnly queryOnly = Annotations.getAnnotation(processMethodContext.getMethod(), QueryOnly.class); facet = queryOnlyValidator.flagIfPresent(ActionSemanticsFacetFromQueryOnlyAnnotation.create(queryOnly, holder), processMethodContext); // else check for the deprecated @Idempotent... if(facet == null) { final Idempotent idempotent = Annotations.getAnnotation(processMethodContext.getMethod(), Idempotent.class); facet = idempotentValidator.flagIfPresent(ActionSemanticsFacetFromIdempotentAnnotation.create(idempotent, holder), processMethodContext); } // else check for the deprecated @ActionSemantics ... if(facet == null) { final ActionSemantics actionSemantics = Annotations.getAnnotation(method, ActionSemantics.class); facet = actionSemanticsValidator.flagIfPresent(ActionSemanticsFacetForActionSemanticsAnnotation.create(actionSemantics, holder), processMethodContext); } // else check for @Action(semantics=...) if(facet == null) { final Action action = Annotations.getAnnotation(method, Action.class); facet = ActionSemanticsFacetForActionAnnotation.create(action, holder); } // else fallback if(facet == null) { facet = new ActionSemanticsFacetFallbackToNonIdempotent(holder); } FacetUtil.addFacet(facet); } void processBulk(final ProcessMethodContext processMethodContext) { final Method method = processMethodContext.getMethod(); final Action action = Annotations.getAnnotation(method, Action.class); final FacetHolder holder = processMethodContext.getFacetHolder(); BulkFacet facet; // check for the deprecated @Bulk annotation first final Bulk annotation = Annotations.getAnnotation(method, Bulk.class); facet = bulkValidator.flagIfPresent(BulkFacetForBulkAnnotation.create(annotation, holder), processMethodContext); // else check for @Action(invokeOn=...) if(facet == null) { facet = BulkFacetForActionAnnotation.create(action, holder); } if(facet == null) { facet = new BulkFacetObjectOnly(holder); } FacetUtil.addFacet(facet); } void processCommand(final ProcessMethodContext processMethodContext) { final Class<?> cls = processMethodContext.getCls(); final Method method = processMethodContext.getMethod(); final Action action = Annotations.getAnnotation(method, Action.class); final FacetedMethod facetHolder = processMethodContext.getFacetHolder(); final FacetHolder holder = facetHolder; // // this rule inspired by a similar rule for auditing and publishing, see DomainObjectAnnotationFacetFactory // if(HasTransactionId.class.isAssignableFrom(processMethodContext.getCls())) { // do not install on any implementation of HasTransactionId // (ie commands, audit entries, published events). return; } CommandFacet commandFacet; // check for deprecated @Command annotation first final Command annotation = Annotations.getAnnotation(method, Command.class); commandFacet = commandValidator.flagIfPresent( CommandFacetForCommandAnnotation.create(annotation, processMethodContext.getFacetHolder()), processMethodContext); // else check for @Action(command=...) if(commandFacet == null) { commandFacet = CommandFacetForActionAnnotation.create(action, getConfiguration(), holder); } FacetUtil.addFacet(commandFacet); } void processPublishing(final ProcessMethodContext processMethodContext) { final Method method = processMethodContext.getMethod(); final Action action = Annotations.getAnnotation(method, Action.class); final FacetHolder holder = processMethodContext.getFacetHolder(); // // this rule inspired by a similar rule for auditing and publishing, see DomainObjectAnnotationFacetFactory // and for commands, see above // if(HasTransactionId.class.isAssignableFrom(processMethodContext.getCls())) { // do not install on any implementation of HasTransactionId // (ie commands, audit entries, published events). return; } PublishedActionFacet facet; // check for deprecated @PublishedAction annotation first final PublishedAction annotation = Annotations.getAnnotation(processMethodContext.getMethod(), PublishedAction.class); facet = publishedActionValidator.flagIfPresent( PublishedActionFacetForPublishedActionAnnotation.create(annotation, holder), processMethodContext); // else check for @Action(publishing=...) if(facet == null) { facet = PublishedActionFacetForActionAnnotation.create(action, getConfiguration(), holder); } FacetUtil.addFacet(facet); } void processTypeOf(final ProcessMethodContext processMethodContext) { final Method method = processMethodContext.getMethod(); final FacetedMethod holder = processMethodContext.getFacetHolder(); final Class<?> methodReturnType = method.getReturnType(); if (!CollectionUtils.isCollectionType(methodReturnType) && !CollectionUtils.isArrayType(methodReturnType)) { return; } TypeOfFacet typeOfFacet = null; // check for deprecated @TypeOf final TypeOf annotation = Annotations.getAnnotation(method, TypeOf.class); typeOfFacet = typeOfValidator.flagIfPresent( TypeOfFacetOnActionForTypeOfAnnotation.create(annotation, getSpecificationLoader(), holder), processMethodContext); // check for @Action(typeOf=...) if(typeOfFacet == null) { final Action action = Annotations.getAnnotation(method, Action.class); if (action != null) { final Class<?> typeOf = action.typeOf(); if(typeOf != null && typeOf != Object.class) { typeOfFacet = new TypeOfFacetForActionAnnotation(typeOf, getSpecificationLoader(), holder); } } } // infer from return type if(typeOfFacet == null) { final Class<?> returnType = method.getReturnType(); typeOfFacet = TypeOfFacet.Util.inferFromArrayType(holder, returnType, getSpecificationLoader()); } // infer from generic return type if(typeOfFacet == null) { final Class<?> cls = processMethodContext.getCls(); typeOfFacet = TypeOfFacet.Util.inferFromGenericReturnType(cls, method, holder, getSpecificationLoader()); } FacetUtil.addFacet(typeOfFacet); } // /////////////////////////////////////////////////////////////// @Override public void refineMetaModelValidator(final MetaModelValidatorComposite metaModelValidator, final IsisConfiguration configuration) { metaModelValidator.add(actionSemanticsValidator); metaModelValidator.add(actionInteractionValidator); metaModelValidator.add(postsActionInvokedEventValidator); metaModelValidator.add(bulkValidator); metaModelValidator.add(commandValidator); metaModelValidator.add(queryOnlyValidator); metaModelValidator.add(idempotentValidator); metaModelValidator.add(publishedActionValidator); metaModelValidator.add(typeOfValidator); metaModelValidator.add(hiddenValidator); metaModelValidator.add(disabledValidator); metaModelValidator.add(prototypeValidator); } // /////////////////////////////////////////////////////////////// @Override public void setServicesInjector(final ServicesInjector servicesInjector) { super.setServicesInjector(servicesInjector); final IsisConfiguration configuration = getConfiguration(); actionSemanticsValidator.setConfiguration(configuration); actionInteractionValidator.setConfiguration(configuration); postsActionInvokedEventValidator.setConfiguration(configuration); bulkValidator.setConfiguration(configuration); commandValidator.setConfiguration(configuration); queryOnlyValidator.setConfiguration(configuration); idempotentValidator.setConfiguration(configuration); publishedActionValidator.setConfiguration(configuration); typeOfValidator.setConfiguration(configuration); hiddenValidator.setConfiguration(configuration); disabledValidator.setConfiguration(configuration); prototypeValidator.setConfiguration(configuration); } }