/* 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.spec.feature;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import org.apache.isis.applib.Identifier;
import org.apache.isis.applib.annotation.ActionLayout;
import org.apache.isis.applib.annotation.ActionSemantics;
import org.apache.isis.applib.annotation.Bulk;
import org.apache.isis.applib.annotation.Where;
import org.apache.isis.applib.filter.Filter;
import org.apache.isis.applib.value.Blob;
import org.apache.isis.applib.value.Clob;
import org.apache.isis.core.commons.lang.StringFunctions;
import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
import org.apache.isis.core.metamodel.consent.Consent;
import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
import org.apache.isis.core.metamodel.deployment.DeploymentCategory;
import org.apache.isis.core.metamodel.facetapi.Facet;
import org.apache.isis.core.metamodel.facetapi.FacetFilters;
import org.apache.isis.core.metamodel.facets.actions.action.invocation.ActionInvocationFacet;
import org.apache.isis.core.metamodel.facets.actions.bulk.BulkFacet;
import org.apache.isis.core.metamodel.facets.actions.position.ActionPositionFacet;
import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
import org.apache.isis.core.metamodel.facets.members.cssclass.CssClassFacet;
import org.apache.isis.core.metamodel.facets.members.cssclassfa.CssClassFaFacet;
import org.apache.isis.core.metamodel.facets.members.cssclassfa.CssClassFaPosition;
import org.apache.isis.core.metamodel.facets.members.order.MemberOrderFacet;
import org.apache.isis.core.metamodel.facets.object.wizard.WizardFacet;
import org.apache.isis.core.metamodel.interactions.ValidatingInteractionAdvisor;
import org.apache.isis.core.metamodel.layout.memberorderfacet.MemberOrderFacetComparator;
import org.apache.isis.core.metamodel.spec.ActionType;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
public interface ObjectAction extends ObjectMember {
//region > getSemantics, getOnType
/**
* The semantics of this action.
*/
ActionSemantics.Of getSemantics();
/**
* Returns the specification for the type of object that this action can be
* invoked upon.
*/
ObjectSpecification getOnType();
//endregion
//region > getType, isPrototype
ActionType getType();
boolean isPrototype();
//endregion
//region > ReturnType
/**
* Returns the specifications for the return type.
*/
ObjectSpecification getReturnType();
/**
* Returns <tt>true</tt> if the represented action returns a non-void object,
* else returns false.
*/
boolean hasReturn();
//endregion
//region > execute, executeWithRuleChecking
/**
* Invokes the action's method on the target object given the specified set
* of parameters, checking the visibility, usability and validity first.
*
* @param mixedInAdapter - will be null for regular actions, and for mixin actions. When a mixin action invokes its underlying mixedIn action, then will be populated (so that the ActionDomainEvent can correctly provide the underlying mixin)
*/
ObjectAdapter executeWithRuleChecking(
final ObjectAdapter target,
final ObjectAdapter mixedInAdapter,
final ObjectAdapter[] parameters,
final InteractionInitiatedBy interactionInitiatedBy,
final Where where) throws AuthorizationException;
/**
* Invokes the action's method on the target object given the specified set
* of parameters.
*
* @param mixedInAdapter - will be null for regular actions, and for mixin actions. When a mixin action invokes its underlying mixedIn action, then will be populated (so that the ActionDomainEvent can correctly provide the underlying mixin)
*/
ObjectAdapter execute(
ObjectAdapter targetAdapter,
ObjectAdapter mixedInAdapter,
ObjectAdapter[] parameters,
final InteractionInitiatedBy interactionInitiatedBy);
//endregion
//region > isProposedArgumentSetValid
/**
* Whether the provided argument set is valid, represented as a {@link Consent}.
*/
Consent isProposedArgumentSetValid(
ObjectAdapter object,
ObjectAdapter[] proposedArguments,
final InteractionInitiatedBy interactionInitiatedBy);
//endregion
//region > Parameters (declarative)
/**
* Returns the number of parameters used by this method.
*/
int getParameterCount();
/**
* Returns set of parameter information.
*
* <p>
* Implementations may build this array lazily or eagerly as required.
*
* @return
*/
List<ObjectActionParameter> getParameters();
/**
* Returns the {@link ObjectSpecification type} of each of the {@link #getParameters() parameters}.
*/
List<ObjectSpecification> getParameterTypes();
/**
* Returns set of parameter information matching the supplied filter.
*
* @return
*/
List<ObjectActionParameter> getParameters(
@SuppressWarnings("deprecation") Filter<ObjectActionParameter> filter);
/**
* Returns the parameter with provided id.
*/
ObjectActionParameter getParameterById(String paramId);
/**
* Returns the parameter with provided name.
*/
ObjectActionParameter getParameterByName(String paramName);
//endregion
//region > Parameters (per instance)
/**
* Returns the defaults references/values to be used for the action.
*/
ObjectAdapter[] getDefaults(ObjectAdapter target);
/**
* Returns a list of possible references/values for each parameter, which
* the user can choose from.
*/
ObjectAdapter[][] getChoices(
final ObjectAdapter target,
final InteractionInitiatedBy interactionInitiatedBy);
//endregion
//region > setupBulkActionInvocationContext
/**
* internal API, called by {@link ActionInvocationFacet} if the action is actually executed (ie in the foreground).
*/
void setupBulkActionInvocationContext(
final ObjectAdapter targetAdapter);
//endregion
//region > Util
public static final class Util {
final static MemberOrderFacetComparator memberOrderFacetComparator = new MemberOrderFacetComparator(false);
private Util() {
}
public static String nameFor(final ObjectAction objAction) {
final String actionName = objAction.getName();
if (actionName != null) {
return actionName;
}
final NamedFacet namedFacet = objAction.getFacet(NamedFacet.class);
if (namedFacet != null) {
return namedFacet.value();
}
return "(no name)";
}
public static boolean returnsBlobOrClob(final ObjectAction objectAction) {
final ObjectSpecification returnType = objectAction.getReturnType();
if (returnType != null) {
Class<?> cls = returnType.getCorrespondingClass();
if (Blob.class.isAssignableFrom(cls) || Clob.class.isAssignableFrom(cls)) {
return true;
}
}
return false;
}
public static String actionIdentifierFor(final ObjectAction action) {
@SuppressWarnings("unused")
final Identifier identifier = action.getIdentifier();
final String className = action.getOnType().getShortIdentifier();
final String actionId = action.getId();
return className + "-" + actionId;
}
public static String descriptionOf(ObjectAction action) {
return action.getDescription();
}
public static ActionLayout.Position actionLayoutPositionOf(ObjectAction action) {
final ActionPositionFacet layoutFacet = action.getFacet(ActionPositionFacet.class);
return layoutFacet != null ? layoutFacet.position() : ActionLayout.Position.BELOW;
}
public static String cssClassFaFor(final ObjectAction action) {
final CssClassFaFacet cssClassFaFacet = action.getFacet(CssClassFaFacet.class);
return cssClassFaFacet != null ? cssClassFaFacet.value() : null;
}
public static CssClassFaPosition cssClassFaPositionFor(final ObjectAction action) {
CssClassFaFacet facet = action.getFacet(CssClassFaFacet.class);
return facet != null ? facet.getPosition() : CssClassFaPosition.LEFT;
}
public static String cssClassFor(final ObjectAction action, final ObjectAdapter objectAdapter) {
final CssClassFacet cssClassFacet = action.getFacet(CssClassFacet.class);
return cssClassFacet != null ? cssClassFacet.cssClass(objectAdapter) : null;
}
public static List<ObjectAction> findTopLevel(
final ObjectAdapter adapter,
final DeploymentCategory deploymentCategory) {
final List<ObjectAction> topLevelActions = Lists.newArrayList();
addTopLevelActions(adapter, ActionType.USER, topLevelActions);
if(deploymentCategory.isPrototyping()) {
addTopLevelActions(adapter, ActionType.PROTOTYPE, topLevelActions);
}
return topLevelActions;
}
static void addTopLevelActions(
final ObjectAdapter adapter,
final ActionType actionType,
final List<ObjectAction> topLevelActions) {
final ObjectSpecification adapterSpec = adapter.getSpecification();
@SuppressWarnings({ "unchecked", "deprecation" })
Filter<ObjectAction> filter = org.apache.isis.applib.filter.Filters.and(
Filters.memberOrderNotAssociationOf(adapterSpec),
Filters.dynamicallyVisible(adapter, InteractionInitiatedBy.USER, Where.ANYWHERE),
Filters.notBulkOnly(),
Filters.excludeWizardActions(adapterSpec));
final List<ObjectAction> userActions = adapterSpec.getObjectActions(actionType, Contributed.INCLUDED, filter);
topLevelActions.addAll(userActions);
}
public static List<ObjectAction> findForAssociation(
final ObjectAdapter adapter,
final ObjectAssociation association, final DeploymentCategory deploymentCategory) {
final List<ObjectAction> associatedActions = Lists.newArrayList();
addActions(adapter, ActionType.USER, association, associatedActions);
if(deploymentCategory.isPrototyping()) {
addActions(adapter, ActionType.PROTOTYPE, association, associatedActions);
}
Collections.sort(associatedActions, new Comparator<ObjectAction>() {
@Override
public int compare(ObjectAction o1, ObjectAction o2) {
final MemberOrderFacet m1 = o1.getFacet(MemberOrderFacet.class);
final MemberOrderFacet m2 = o2.getFacet(MemberOrderFacet.class);
return memberOrderFacetComparator.compare(m1, m2);
}
});
return associatedActions;
}
static List<ObjectAction> addActions(
final ObjectAdapter adapter,
final ActionType type,
final ObjectAssociation association, final List<ObjectAction> associatedActions) {
final ObjectSpecification objectSpecification = adapter.getSpecification();
@SuppressWarnings({ "unchecked", "deprecation" })
Filter<ObjectAction> filter = org.apache.isis.applib.filter.Filters.and(
Filters.memberOrderOf(association),
// visibility needs to be determined at point of rendering, by ActionLink itself
// Filters.dynamicallyVisible(adapter, InteractionInitiatedBy.USER, Where.ANYWHERE),
Filters.notBulkOnly(),
Filters.excludeWizardActions(objectSpecification));
final List<ObjectAction> userActions = objectSpecification.getObjectActions(type, Contributed.INCLUDED, filter);
associatedActions.addAll(userActions);
return userActions;
}
}
//endregion
//region > Predicates
public static final class Predicates {
private Predicates() {
}
public static Predicate<ObjectAction> dynamicallyVisible(
final ObjectAdapter target,
final InteractionInitiatedBy interactionInitiatedBy,
final Where where) {
return org.apache.isis.applib.filter.Filters
.asPredicate(Filters.dynamicallyVisible(target, interactionInitiatedBy, where));
}
public static Predicate<ObjectAction> withId(final String actionId) {
return org.apache.isis.applib.filter.Filters.asPredicate(Filters.withId(actionId));
}
public static Predicate<ObjectAction> withNoValidationRules() {
return org.apache.isis.applib.filter.Filters.asPredicate(Filters.withNoValidationRules());
}
public static Predicate<ObjectAction> ofType(final ActionType type) {
return org.apache.isis.applib.filter.Filters.asPredicate(Filters.ofType(type));
}
public static Predicate<ObjectAction> bulk() {
return org.apache.isis.applib.filter.Filters.asPredicate(Filters.bulk());
}
// UNUSED?
public static Predicate<ObjectAction> notBulkOnly() {
return org.apache.isis.applib.filter.Filters.asPredicate(Filters.notBulkOnly());
}
public static Predicate<ObjectAction> memberOrderOf(ObjectAssociation association) {
return org.apache.isis.applib.filter.Filters.asPredicate(Filters.memberOrderOf(association));
}
}
//endregion
//region > Filters
public static final class Filters {
private Filters() {
}
/**
* @deprecated -use {@link com.google.common.base.Predicate equivalent}
*/
@Deprecated
public static Filter<ObjectAction> dynamicallyVisible(
final ObjectAdapter target,
final InteractionInitiatedBy interactionInitiatedBy,
final Where where) {
return new Filter<ObjectAction>() {
@Override
public boolean accept(final ObjectAction objectAction) {
final Consent visible = objectAction.isVisible(target, interactionInitiatedBy, where);
return visible.isAllowed();
}
};
}
/**
* @deprecated -use {@link com.google.common.base.Predicate equivalent}
*/
@Deprecated
public static Filter<ObjectAction> withId(final String actionId) {
return new Filter<ObjectAction>() {
@Override
public boolean accept(ObjectAction objectAction) {
return objectAction.getId().equals(actionId);
}
};
}
/**
* @deprecated -use {@link com.google.common.base.Predicate equivalent}
*/
@Deprecated
public static Filter<ObjectAction> withNoValidationRules() {
return new Filter<ObjectAction>() {
@Override
public boolean accept(final ObjectAction objectAction) {
final List<Facet> validatingFacets = objectAction.getFacets(FacetFilters
.isA(ValidatingInteractionAdvisor.class));
return validatingFacets.isEmpty();
}
};
}
/**
* @deprecated -use {@link com.google.common.base.Predicate equivalent}
*/
@Deprecated
public static Filter<ObjectAction> ofType(final ActionType type) {
return new Filter<ObjectAction>() {
@Override
public boolean accept(ObjectAction oa) {
return oa.getType() == type;
}
};
}
/**
* @deprecated -use {@link com.google.common.base.Predicate equivalent}
*/
@Deprecated
public static Filter<ObjectAction> bulk() {
return new Filter<ObjectAction>() {
@Override
public boolean accept(ObjectAction oa) {
final BulkFacet bulkFacet = oa.getFacet(BulkFacet.class);
if(bulkFacet == null || bulkFacet.isNoop() || bulkFacet.value() == Bulk.AppliesTo.REGULAR_ONLY) {
return false;
}
if (oa.getParameterCount() != 0) {
return false;
}
// currently don't support returning Blobs or Clobs
// (because haven't figured out how to rerender the current page, but also to do a download)
ObjectSpecification returnSpec = oa.getReturnType();
if (returnSpec != null) {
Class<?> returnType = returnSpec.getCorrespondingClass();
if (returnType == Blob.class || returnType == Clob.class) {
return false;
}
}
return true;
}
};
}
@Deprecated
public static Filter<ObjectAction> notBulkOnly() {
return new Filter<ObjectAction>() {
@Override
public boolean accept(ObjectAction t) {
BulkFacet facet = t.getFacet(BulkFacet.class);
return facet == null || facet.value() != Bulk.AppliesTo.BULK_ONLY;
}
};
}
public static Filter<ObjectAction> excludeWizardActions(final ObjectSpecification objectSpecification) {
return org.apache.isis.applib.filter.Filters.not(wizardActions(objectSpecification));
// return wizardActions(objectSpecification);
}
private static Filter<ObjectAction> wizardActions(final ObjectSpecification objectSpecification) {
return new Filter<ObjectAction>() {
@Override
public boolean accept(ObjectAction input) {
if (objectSpecification == null) {
return false;
}
final WizardFacet wizardFacet = objectSpecification.getFacet(WizardFacet.class);
return wizardFacet != null && wizardFacet.isWizardAction(input);
}
};
}
@SuppressWarnings("deprecation")
public static Filter<ObjectAction> memberOrderOf(ObjectAssociation association) {
final String assocName = association.getName();
final String assocId = association.getId();
return new Filter<ObjectAction>() {
@Override
public boolean accept(ObjectAction t) {
final MemberOrderFacet memberOrderFacet = t.getFacet(MemberOrderFacet.class);
if (memberOrderFacet == null || Strings.isNullOrEmpty(memberOrderFacet.name())) {
return false;
}
final String memberOrderName = memberOrderFacet.name().toLowerCase();
if (Strings.isNullOrEmpty(memberOrderName)) {
return false;
}
return memberOrderName.equalsIgnoreCase(assocName) || memberOrderName.equalsIgnoreCase(assocId);
}
};
}
public static Filter<ObjectAction> memberOrderNotAssociationOf(final ObjectSpecification adapterSpec) {
final List<ObjectAssociation> associations = adapterSpec.getAssociations(Contributed.INCLUDED);
final List<String> associationNames = Lists.transform(associations,
com.google.common.base.Functions.compose(StringFunctions.toLowerCase(), ObjectAssociation.Functions.toName()));
final List<String> associationIds = Lists.transform(associations,
com.google.common.base.Functions.compose(StringFunctions.toLowerCase(), ObjectAssociation.Functions.toId()));
return new Filter<ObjectAction>() {
@Override
public boolean accept(ObjectAction t) {
final MemberOrderFacet memberOrderFacet = t.getFacet(MemberOrderFacet.class);
if (memberOrderFacet == null || Strings.isNullOrEmpty(memberOrderFacet.name())) {
return true;
}
String memberOrderName = memberOrderFacet.name().toLowerCase();
if (Strings.isNullOrEmpty(memberOrderName)) {
return false;
}
return !associationNames.contains(memberOrderName) && !associationIds.contains(memberOrderName);
}
};
}
}
//endregion
}