/*
* 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.specloader.specimpl;
import java.util.List;
import java.util.Objects;
import org.apache.isis.applib.Identifier;
import org.apache.isis.applib.annotation.When;
import org.apache.isis.applib.annotation.Where;
import org.apache.isis.applib.filter.Filter;
import org.apache.isis.applib.services.bookmark.Bookmark;
import org.apache.isis.applib.services.command.Command;
import org.apache.isis.applib.services.command.CommandContext;
import org.apache.isis.core.commons.lang.StringExtensions;
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.consent.InteractionResult;
import org.apache.isis.core.metamodel.facetapi.Facet;
import org.apache.isis.core.metamodel.facetapi.FacetHolder;
import org.apache.isis.core.metamodel.facetapi.FeatureType;
import org.apache.isis.core.metamodel.facetapi.MultiTypedFacet;
import org.apache.isis.core.metamodel.facets.FacetedMethod;
import org.apache.isis.core.metamodel.facets.actions.action.invocation.CommandUtil;
import org.apache.isis.core.metamodel.facets.actions.command.CommandFacet;
import org.apache.isis.core.metamodel.facets.all.describedas.DescribedAsFacet;
import org.apache.isis.core.metamodel.facets.all.help.HelpFacet;
import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet;
import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
import org.apache.isis.core.metamodel.facets.object.mixin.MixinFacet;
import org.apache.isis.core.metamodel.interactions.AccessContext;
import org.apache.isis.core.metamodel.interactions.DisablingInteractionAdvisor;
import org.apache.isis.core.metamodel.interactions.HidingInteractionAdvisor;
import org.apache.isis.core.metamodel.interactions.InteractionContext;
import org.apache.isis.core.metamodel.interactions.InteractionUtils;
import org.apache.isis.core.metamodel.interactions.UsabilityContext;
import org.apache.isis.core.metamodel.interactions.VisibilityContext;
import org.apache.isis.core.metamodel.services.ServicesInjector;
import org.apache.isis.core.metamodel.services.command.CommandDtoServiceInternal;
import org.apache.isis.core.metamodel.services.persistsession.PersistenceSessionServiceInternal;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
import org.apache.isis.schema.cmd.v1.CommandDto;
import org.apache.isis.schema.utils.CommandDtoUtils;
public abstract class ObjectMemberAbstract implements ObjectMember {
public static ObjectSpecification getSpecification(final SpecificationLoader specificationLookup, final Class<?> type) {
return type == null ? null : specificationLookup.loadSpecification(type);
}
//region > fields
private final String id;
private final FacetedMethod facetedMethod;
private final FeatureType featureType;
private final SpecificationLoader specificationLoader;
private final ServicesInjector servicesInjector;
private final PersistenceSessionServiceInternal persistenceSessionServiceInternal;
//endregion
protected ObjectMemberAbstract(
final FacetedMethod facetedMethod,
final FeatureType featureType,
final ServicesInjector servicesInjector) {
final String id = facetedMethod.getIdentifier().getMemberName();
if (id == null) {
throw new IllegalArgumentException("Id must always be set");
}
this.facetedMethod = facetedMethod;
this.featureType = featureType;
this.id = id;
this.servicesInjector = servicesInjector;
this.specificationLoader = servicesInjector.getSpecificationLoader();
this.persistenceSessionServiceInternal = servicesInjector.getPersistenceSessionServiceInternal();
}
//region > Identifiers
@Override
public String getId() {
return id;
}
@Override
public Identifier getIdentifier() {
return getFacetedMethod().getIdentifier();
}
@Override
public FeatureType getFeatureType() {
return featureType;
}
//endregion
//region > Facets
public FacetedMethod getFacetedMethod() {
return facetedMethod;
}
protected FacetHolder getFacetHolder() {
return getFacetedMethod();
}
@Override
public boolean containsFacet(final Class<? extends Facet> facetType) {
return getFacetHolder().containsFacet(facetType);
}
@Override
public boolean containsDoOpFacet(final Class<? extends Facet> facetType) {
return getFacetHolder().containsDoOpFacet(facetType);
}
@Override
public boolean containsDoOpNotDerivedFacet(final Class<? extends Facet> facetType) {
return getFacetHolder().containsDoOpNotDerivedFacet(facetType);
}
@Override
public <T extends Facet> T getFacet(final Class<T> cls) {
return getFacetHolder().getFacet(cls);
}
@Override
public Class<? extends Facet>[] getFacetTypes() {
return getFacetHolder().getFacetTypes();
}
@Override
public List<Facet> getFacets(final Filter<Facet> filter) {
return getFacetHolder().getFacets(filter);
}
@Override
public void addFacet(final Facet facet) {
getFacetHolder().addFacet(facet);
}
@Override
public void addFacet(final MultiTypedFacet facet) {
getFacetHolder().addFacet(facet);
}
@Override
public void removeFacet(final Facet facet) {
getFacetHolder().removeFacet(facet);
}
@Override
public void removeFacet(final Class<? extends Facet> facetType) {
getFacetHolder().removeFacet(facetType);
}
//endregion
//region > Name, Description, Help (convenience for facets)
/**
* Return the default label for this member. This is based on the name of
* this member.
*
* @see #getId()
*/
@Override
public String getName() {
final NamedFacet facet = getFacet(NamedFacet.class);
final String name = facet.value();
if (name != null) {
return name;
}
else {
// this should now be redundant, see NamedFacetDefault
return StringExtensions.asNaturalName2(getId());
}
}
@Override
public String getDescription() {
final DescribedAsFacet facet = getFacet(DescribedAsFacet.class);
return facet.value();
}
@Override
public String getHelp() {
final HelpFacet facet = getFacet(HelpFacet.class);
return facet.value();
}
//endregion
//region > Hidden (or visible)
/**
* Create an {@link InteractionContext} to represent an attempt to view this
* member (that is, to check if it is visible or not).
*
* <p>
* Typically it is easier to just call
* {@link ObjectMember#isVisible(ObjectAdapter, InteractionInitiatedBy, Where)}; this is
* provided as API for symmetry with interactions (such as
* {@link AccessContext} accesses) have no corresponding vetoing methods.
*/
protected abstract VisibilityContext<?> createVisibleInteractionContext(
final ObjectAdapter targetObjectAdapter,
final InteractionInitiatedBy interactionInitiatedBy,
final Where where);
@Override
public boolean isAlwaysHidden() {
final HiddenFacet facet = getFacet(HiddenFacet.class);
return facet != null &&
!facet.isNoop() &&
facet.when() == When.ALWAYS &&
(facet.where() == Where.EVERYWHERE || facet.where() == Where.ANYWHERE)
;
}
/**
* Loops over all {@link HidingInteractionAdvisor} {@link Facet}s and
* returns <tt>true</tt> only if none hide the member.
*/
@Override
public Consent isVisible(
final ObjectAdapter target,
final InteractionInitiatedBy interactionInitiatedBy,
final Where where) {
return isVisibleResult(target, interactionInitiatedBy, where).createConsent();
}
private InteractionResult isVisibleResult(
final ObjectAdapter target,
final InteractionInitiatedBy interactionInitiatedBy,
final Where where) {
final VisibilityContext<?> ic = createVisibleInteractionContext(target, interactionInitiatedBy, where);
return InteractionUtils.isVisibleResult(this, ic);
}
//endregion
//region > Disabled (or enabled)
/**
* Create an {@link InteractionContext} to represent an attempt to
* use this member (that is, to check if it is usable or not).
*
* <p>
* Typically it is easier to just call
* {@link ObjectMember#isUsable(ObjectAdapter, InteractionInitiatedBy, Where)}; this is
* provided as API for symmetry with interactions (such as
* {@link AccessContext} accesses) have no corresponding vetoing methods.
*/
protected abstract UsabilityContext<?> createUsableInteractionContext(
final ObjectAdapter target,
final InteractionInitiatedBy interactionInitiatedBy,
final Where where);
/**
* Loops over all {@link DisablingInteractionAdvisor} {@link Facet}s and
* returns <tt>true</tt> only if none disables the member.
*/
@Override
public Consent isUsable(
final ObjectAdapter target,
final InteractionInitiatedBy interactionInitiatedBy,
final Where where) {
return isUsableResult(target, interactionInitiatedBy, where).createConsent();
}
private InteractionResult isUsableResult(
final ObjectAdapter target,
final InteractionInitiatedBy interactionInitiatedBy,
final Where where) {
final UsabilityContext<?> ic = createUsableInteractionContext(target, interactionInitiatedBy, where);
return InteractionUtils.isUsableResult(this, ic);
}
//endregion
//region > isAssociation, isAction
@Override
public boolean isAction() {
return featureType.isAction();
}
@Override
public boolean isPropertyOrCollection() {
return featureType.isPropertyOrCollection();
}
@Override
public boolean isOneToManyAssociation() {
return featureType.isCollection();
}
@Override
public boolean isOneToOneAssociation() {
return featureType.isProperty();
}
//endregion
//region > mixinAdapterFor
/**
* For mixins
*/
protected ObjectAdapter mixinAdapterFor(
final Class<?> mixinType,
final ObjectAdapter mixedInAdapter) {
final ObjectSpecification objectSpecification = getSpecificationLoader().loadSpecification(mixinType);
final MixinFacet mixinFacet = objectSpecification.getFacet(MixinFacet.class);
final Object mixinPojo = mixinFacet.instantiate(mixedInAdapter.getObject());
return getPersistenceSessionService().adapterFor(mixinPojo);
}
public static String determineNameFrom(final ObjectAction mixinAction) {
return StringExtensions.asCapitalizedName(suffix(mixinAction));
}
static String determineIdFrom(final ObjectActionDefault mixinAction) {
final String id = StringExtensions.asCamelLowerFirst(compress(suffix(mixinAction)));
return id;
}
private static String compress(final String suffix) {
return suffix.replaceAll(" ","");
}
static String suffix(final ObjectAction mixinAction) {
return suffix(mixinAction.getOnType().getSingularName());
}
static String suffix(final String singularName) {
final String deriveFromUnderscore = derive(singularName, "_");
if(!Objects.equals(singularName, deriveFromUnderscore)) {
return deriveFromUnderscore;
}
final String deriveFromDollar = derive(singularName, "$");
if(!Objects.equals(singularName, deriveFromDollar)) {
return deriveFromDollar;
}
return singularName;
}
private static String derive(final String singularName, final String separator) {
final int indexOfSeparator = singularName.lastIndexOf(separator);
return occursNotAtEnd(singularName, indexOfSeparator)
? singularName.substring(indexOfSeparator + 1)
: singularName;
}
private static boolean occursNotAtEnd(final String singularName, final int indexOfUnderscore) {
return indexOfUnderscore != -1 && indexOfUnderscore != singularName.length() - 1;
}
//endregion
//region > toString
@Override
public String toString() {
return String.format("id=%s,name='%s'", getId(), getName());
}
//endregion
//region > Dependencies
public SpecificationLoader getSpecificationLoader() {
return specificationLoader;
}
public ServicesInjector getServicesInjector() {
return servicesInjector;
}
public PersistenceSessionServiceInternal getPersistenceSessionService() {
return persistenceSessionServiceInternal;
}
protected <T> T lookupService(final Class<T> serviceClass) {
return getServicesInjector().lookupService(serviceClass);
}
protected CommandContext getCommandContext() {
CommandContext commandContext = lookupService(CommandContext.class);
if (commandContext == null) {
throw new IllegalStateException("The CommandContext service is not registered!");
}
return commandContext;
}
protected CommandDtoServiceInternal getCommandDtoService() {
return lookupService(CommandDtoServiceInternal.class);
}
//endregion
//region > command (setup)
protected void setupCommandTarget(final ObjectAdapter targetAdapter, final String arguments) {
final CommandContext commandContext = getCommandContext();
final Command command = commandContext.getCommand();
if (command.getExecutor() != Command.Executor.USER) {
return;
}
if(command.getTarget() != null) {
// is set up by the outer-most action; inner actions (invoked via the WrapperFactory) must not overwrite
return;
}
command.setTargetClass(CommandUtil.targetClassNameFor(targetAdapter));
command.setTargetAction(CommandUtil.targetMemberNameFor(this));
command.setArguments(arguments);
final Bookmark targetBookmark = CommandUtil.bookmarkFor(targetAdapter);
command.setTarget(targetBookmark);
}
protected void setupCommandMemberIdentifier() {
final CommandContext commandContext = getCommandContext();
final Command command = commandContext.getCommand();
if (command.getExecutor() != Command.Executor.USER) {
return;
}
if (command.getMemberIdentifier() != null) {
// any contributed/mixin actions will fire after the main action
// the guard here prevents them from trashing the command's memberIdentifier
return;
}
command.setMemberIdentifier(CommandUtil.memberIdentifierFor(this));
}
protected void setupCommandDtoAndExecutionContext(final CommandDto dto) {
final CommandContext commandContext = getCommandContext();
final Command command = commandContext.getCommand();
if (command.getExecutor() != Command.Executor.USER) {
return;
}
if (command.getMemento() != null) {
// guard here to prevent subsequent contributed/mixin actions from
// trampling over the command's memento and execution context
return;
}
// memento
final String mementoXml = CommandDtoUtils.toXml(dto);
command.setMemento(mementoXml);
// copy over the command execution 'context' (if available)
final CommandFacet commandFacet = getFacetHolder().getFacet(CommandFacet.class);
if(commandFacet != null && !commandFacet.isDisabled()) {
command.setExecuteIn(commandFacet.executeIn());
command.setPersistence(commandFacet.persistence());
} else {
// if no facet, assume do want to execute right now, but only persist (eventually) if hinted.
command.setExecuteIn(org.apache.isis.applib.annotation.Command.ExecuteIn.FOREGROUND);
command.setPersistence(org.apache.isis.applib.annotation.Command.Persistence.IF_HINTED);
}
}
//endregion
}