/* * 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; } }