/*
* 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.properties.property.modify;
import com.google.common.base.Objects;
import org.apache.isis.applib.services.clock.ClockService;
import org.apache.isis.applib.services.command.Command;
import org.apache.isis.applib.services.command.CommandContext;
import org.apache.isis.applib.services.command.spi.CommandService;
import org.apache.isis.applib.services.eventbus.AbstractDomainEvent;
import org.apache.isis.applib.services.eventbus.PropertyDomainEvent;
import org.apache.isis.applib.services.iactn.Interaction;
import org.apache.isis.applib.services.iactn.InteractionContext;
import org.apache.isis.core.commons.exceptions.IsisException;
import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
import org.apache.isis.core.metamodel.facetapi.Facet;
import org.apache.isis.core.metamodel.facetapi.FacetHolder;
import org.apache.isis.core.metamodel.facets.DomainEventHelper;
import org.apache.isis.core.metamodel.facets.SingleValueFacetAbstract;
import org.apache.isis.core.metamodel.facets.actions.action.invocation.CommandUtil;
import org.apache.isis.core.metamodel.facets.propcoll.accessor.PropertyOrCollectionAccessorFacet;
import org.apache.isis.core.metamodel.facets.properties.publish.PublishedPropertyFacet;
import org.apache.isis.core.metamodel.facets.properties.update.clear.PropertyClearFacet;
import org.apache.isis.core.metamodel.facets.properties.update.modify.PropertySetterFacet;
import org.apache.isis.core.metamodel.services.ServicesInjector;
import org.apache.isis.core.metamodel.services.ixn.InteractionDtoServiceInternal;
import org.apache.isis.core.metamodel.services.persistsession.PersistenceSessionServiceInternal;
import org.apache.isis.core.metamodel.services.publishing.PublishingServiceInternal;
import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
import org.apache.isis.core.runtime.system.transaction.TransactionalClosure;
import org.apache.isis.schema.ixn.v1.PropertyEditDto;
import java.sql.Timestamp;
public abstract class PropertySetterOrClearFacetForDomainEventAbstract
extends SingleValueFacetAbstract<Class<? extends PropertyDomainEvent<?,?>>> {
private final DomainEventHelper domainEventHelper;
private final PropertyOrCollectionAccessorFacet getterFacet;
private final PropertySetterFacet setterFacet;
private final PropertyClearFacet clearFacet;
private final PropertyDomainEventFacetAbstract propertyDomainEventFacet;
private final ServicesInjector servicesInjector;
private final PersistenceSessionServiceInternal persistenceSessionServiceInternal;
public PropertySetterOrClearFacetForDomainEventAbstract(
final Class<? extends Facet> facetType,
final Class<? extends PropertyDomainEvent<?, ?>> eventType,
final PropertyOrCollectionAccessorFacet getterFacet,
final PropertySetterFacet setterFacet,
final PropertyClearFacet clearFacet,
final PropertyDomainEventFacetAbstract propertyDomainEventFacet,
final ServicesInjector servicesInjector,
final FacetHolder holder) {
super(facetType, eventType, holder);
this.getterFacet = getterFacet;
this.setterFacet = setterFacet;
this.clearFacet = clearFacet;
this.propertyDomainEventFacet = propertyDomainEventFacet;
this.persistenceSessionServiceInternal = servicesInjector.getPersistenceSessionServiceInternal();
this.servicesInjector = servicesInjector;
this.domainEventHelper = new DomainEventHelper(servicesInjector);
}
enum Style {
SET {
@Override
boolean hasCorrespondingFacet(final PropertySetterOrClearFacetForDomainEventAbstract facet) {
return facet.setterFacet != null;
}
@Override
void invoke(
final PropertySetterOrClearFacetForDomainEventAbstract facet,
final OneToOneAssociation owningProperty,
final ObjectAdapter targetAdapter,
final ObjectAdapter valueAdapterOrNull,
final InteractionInitiatedBy interactionInitiatedBy) {
facet.setterFacet.setProperty(
owningProperty, targetAdapter, valueAdapterOrNull, interactionInitiatedBy);
}
},
CLEAR {
@Override
boolean hasCorrespondingFacet(final PropertySetterOrClearFacetForDomainEventAbstract facet) {
return facet.clearFacet != null;
}
@Override
void invoke(
final PropertySetterOrClearFacetForDomainEventAbstract facet,
final OneToOneAssociation owningProperty,
final ObjectAdapter targetAdapter,
final ObjectAdapter valueAdapterOrNull,
final InteractionInitiatedBy interactionInitiatedBy) {
facet.clearFacet.clearProperty(
owningProperty, targetAdapter, interactionInitiatedBy);
}
};
abstract boolean hasCorrespondingFacet(final PropertySetterOrClearFacetForDomainEventAbstract facet);
abstract void invoke(
final PropertySetterOrClearFacetForDomainEventAbstract facet,
final OneToOneAssociation owningProperty,
final ObjectAdapter targetAdapter,
final ObjectAdapter valueAdapterOrNull,
final InteractionInitiatedBy interactionInitiatedBy);
}
public void clearProperty(
final OneToOneAssociation owningProperty,
final ObjectAdapter targetAdapter,
final InteractionInitiatedBy interactionInitiatedBy) {
setOrClearProperty(Style.CLEAR,
owningProperty, targetAdapter, null, interactionInitiatedBy);
}
public void setProperty(
final OneToOneAssociation owningProperty,
final ObjectAdapter targetAdapter,
final ObjectAdapter newValueAdapter,
final InteractionInitiatedBy interactionInitiatedBy) {
setOrClearProperty(Style.SET,
owningProperty, targetAdapter, newValueAdapter, interactionInitiatedBy);
}
private void setOrClearProperty(
final Style style,
final OneToOneAssociation owningProperty,
final ObjectAdapter targetAdapter,
final ObjectAdapter newValueAdapter,
final InteractionInitiatedBy interactionInitiatedBy) {
getPersistenceSessionServiceInternal().executeWithinTransaction(
new TransactionalClosure(){
@Override
public void execute() {
doSetOrClearProperty(style, owningProperty, targetAdapter, newValueAdapter, interactionInitiatedBy);
}
}
);
}
private void doSetOrClearProperty(
final Style style,
final OneToOneAssociation owningProperty,
final ObjectAdapter targetAdapter,
final ObjectAdapter newValueAdapter,
final InteractionInitiatedBy interactionInitiatedBy) {
// similar code in ActionInvocationFacetFDEA
if(!style.hasCorrespondingFacet(this)) {
return;
}
final CommandContext commandContext = getCommandContext();
final Command command = commandContext.getCommand();
final InteractionContext interactionContext = getInteractionContext();
final Interaction interaction = interactionContext.getInteraction();
final String propertyId = owningProperty.getIdentifier().toClassAndNameIdentityString();
if( command.getExecutor() == Command.Executor.USER &&
command.getExecuteIn() == org.apache.isis.applib.annotation.Command.ExecuteIn.BACKGROUND) {
// deal with background commands
// persist command so can it can subsequently be invoked in the 'background'
final CommandService commandService = getCommandService();
if (!commandService.persistIfPossible(command)) {
throw new IsisException(String.format(
"Unable to persist command for property '%s'; CommandService does not support persistent commands ",
propertyId));
}
} else {
final Object target = ObjectAdapter.Util.unwrap(targetAdapter);
final Object argValue = ObjectAdapter.Util.unwrap(newValueAdapter);
final String targetMember = CommandUtil.targetMemberNameFor(owningProperty);
final String targetClass = CommandUtil.targetClassNameFor(targetAdapter);
final Interaction.PropertyEdit execution =
new Interaction.PropertyEdit(interaction, propertyId, target, argValue, targetMember, targetClass);
final Interaction.MemberExecutor<Interaction.PropertyEdit> executor =
new Interaction.MemberExecutor<Interaction.PropertyEdit>() {
@Override
public Object execute(final Interaction.PropertyEdit currentExecution) {
try {
// update the current execution with the DTO (memento)
final PropertyEditDto editDto =
getInteractionDtoServiceInternal().asPropertyEditDto(
owningProperty, targetAdapter, newValueAdapter);
currentExecution.setDto(editDto);
// set the startedAt (and update command if this is the top-most member execution)
// (this isn't done within Interaction#execute(...) because it requires the DTO
// to have been set on the current execution).
final Timestamp startedAt = getClockService().nowAsJavaSqlTimestamp();
execution.setStartedAt(startedAt);
if(command.getStartedAt() == null) {
command.setStartedAt(startedAt);
}
// ... post the executing event
final Object oldValue = getterFacet.getProperty(targetAdapter, interactionInitiatedBy);
final Object newValue = ObjectAdapter.Util.unwrap(newValueAdapter);
final PropertyDomainEvent<?, ?> event =
domainEventHelper.postEventForProperty(
AbstractDomainEvent.Phase.EXECUTING,
eventType(), null,
getIdentified(), targetAdapter,
oldValue, newValue);
// set event onto the execution
currentExecution.setEvent(event);
// invoke method
style.invoke(PropertySetterOrClearFacetForDomainEventAbstract.this, owningProperty,
targetAdapter, newValueAdapter, interactionInitiatedBy);
// reading the actual value from the target object, playing it safe...
final Object actualNewValue = getterFacet.getProperty(targetAdapter, interactionInitiatedBy);
if (!Objects.equal(oldValue, actualNewValue)) {
// ... post the executed event
domainEventHelper.postEventForProperty(
AbstractDomainEvent.Phase.EXECUTED,
eventType(), verify(event),
getIdentified(), targetAdapter,
oldValue, actualNewValue);
}
return null;
//
// REVIEW: the corresponding action has a whole bunch of error handling here.
// we probably should do something similar...
//
} finally {
}
}
};
// sets up startedAt and completedAt on the execution, also manages the execution call graph
interaction.execute(executor, execution);
// handle any exceptions
final Interaction.Execution priorExecution = interaction.getPriorExecution();
// TODO: should also sync DTO's 'threw' attribute here...?
final Exception executionExceptionIfAny = priorExecution.getThrew();
if(executionExceptionIfAny != null) {
throw executionExceptionIfAny instanceof RuntimeException
? ((RuntimeException)executionExceptionIfAny)
: new RuntimeException(executionExceptionIfAny);
}
// publish (if not a contributed association, query-only mixin)
final PublishedPropertyFacet publishedPropertyFacet = getIdentified().getFacet(PublishedPropertyFacet.class);
if (publishedPropertyFacet != null) {
getPublishingServiceInternal().publishProperty(priorExecution);
}
}
}
private Class<? extends PropertyDomainEvent<?, ?>> eventType() {
return value();
}
/**
* Optional hook to allow the facet implementation for the deprecated {@link org.apache.isis.applib.annotation.PostsPropertyChangedEvent} annotation
* to discard the event if of a different type.
*/
protected PropertyDomainEvent<?, ?> verify(PropertyDomainEvent<?, ?> event) {
return event;
}
private InteractionDtoServiceInternal getInteractionDtoServiceInternal() {
return servicesInjector.lookupServiceElseFail(InteractionDtoServiceInternal.class);
}
private CommandContext getCommandContext() {
return servicesInjector.lookupServiceElseFail(CommandContext.class);
}
private InteractionContext getInteractionContext() {
return servicesInjector.lookupServiceElseFail(InteractionContext.class);
}
private CommandService getCommandService() {
return servicesInjector.lookupServiceElseFail(CommandService.class);
}
private ClockService getClockService() {
return servicesInjector.lookupServiceElseFail(ClockService.class);
}
private PublishingServiceInternal getPublishingServiceInternal() {
return servicesInjector.lookupServiceElseFail(PublishingServiceInternal.class);
}
private PersistenceSessionServiceInternal getPersistenceSessionServiceInternal() {
return persistenceSessionServiceInternal;
}
}