/* * 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; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.Collections; import java.util.List; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import org.apache.isis.applib.FatalException; import org.apache.isis.applib.Identifier; import org.apache.isis.applib.services.command.Command; import org.apache.isis.applib.services.command.Command2; import org.apache.isis.applib.services.command.Command3; import org.apache.isis.applib.services.eventbus.AbstractDomainEvent; import org.apache.isis.applib.services.eventbus.AbstractInteractionEvent; import org.apache.isis.applib.services.eventbus.ActionDomainEvent; import org.apache.isis.applib.services.eventbus.ActionInteractionEvent; import org.apache.isis.applib.services.eventbus.CollectionDomainEvent; import org.apache.isis.applib.services.eventbus.EventBusService; import org.apache.isis.applib.services.eventbus.PropertyDomainEvent; import org.apache.isis.core.metamodel.adapter.ObjectAdapter; import org.apache.isis.core.metamodel.facetapi.IdentifiedHolder; import org.apache.isis.core.metamodel.services.ServicesInjector; import org.apache.isis.core.metamodel.spec.feature.ObjectAction; import org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter; public class DomainEventHelper { private final ServicesInjector servicesInjector; public DomainEventHelper(final ServicesInjector servicesInjector) { this.servicesInjector = servicesInjector; } //region > postEventForAction, newActionDomainEvent @SuppressWarnings({ "rawtypes" }) public ActionDomainEvent<?> postEventForAction( final AbstractDomainEvent.Phase phase, final Class eventType, final ActionDomainEvent<?> existingEvent, final ObjectAction objectAction, final IdentifiedHolder identified, final ObjectAdapter targetAdapter, final ObjectAdapter mixedInAdapter, final ObjectAdapter[] argumentAdapters, final Command command, final ObjectAdapter resultAdapter) { try { final ActionDomainEvent<?> event; if (existingEvent != null && phase.isExecuted()) { // reuse existing event from the executing phase event = existingEvent; } else { // all other phases, create a new event final Object source = ObjectAdapter.Util.unwrap(targetAdapter); final Object[] arguments = ObjectAdapter.Util.unwrap(argumentAdapters); final Identifier identifier = identified.getIdentifier(); event = newActionDomainEvent(eventType, identifier, source, arguments); // copy over if have if(mixedInAdapter != null ) { event.setMixedIn(mixedInAdapter.getObject()); } if(objectAction != null) { // should always be the case... event.setActionSemantics(objectAction.getSemantics()); final List<ObjectActionParameter> parameters = objectAction.getParameters(); event.setParameterNames(immutableList(Iterables.transform(parameters, ObjectActionParameter.Functions.GET_NAME))); event.setParameterTypes(immutableList(Iterables.transform(parameters, ObjectActionParameter.Functions.GET_TYPE))); } } event.setEventPhase(phase); event.setPhase(AbstractInteractionEvent.Phase.from(phase)); if(phase.isExecuted()) { event.setReturnValue(ObjectAdapter.Util.unwrap(resultAdapter)); } // associate event with command... if(command != null) { event.setCommand(command); } // ... and associate command with event if(command != null) { if(phase.isExecuting()) { if(command instanceof Command3) { final Command3 command3 = (Command3) command; command3.pushActionDomainEvent(event); } else if(command instanceof Command2 && event instanceof ActionInteractionEvent) { final Command2 command2 = (Command3) command; final ActionInteractionEvent<?> aie = (ActionInteractionEvent<?>) event; command2.pushActionInteractionEvent(aie); } } } getEventBusService().post(event); return event; } catch (Exception e) { throw new FatalException(e); } } private static <T> List<T> immutableList(final Iterable<T> iterable) { return Collections.unmodifiableList(Lists.newArrayList(iterable)); } @SuppressWarnings("unchecked") static <S> ActionDomainEvent<S> newActionDomainEvent( final Class<? extends ActionDomainEvent<S>> type, final Identifier identifier, final S source, final Object... arguments) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { final Constructor<?>[] constructors = type.getConstructors(); // no-arg constructor for (final Constructor<?> constructor : constructors) { final Class<?>[] parameterTypes = constructor.getParameterTypes(); if(parameterTypes.length == 0) { final Object event = constructor.newInstance(); final ActionDomainEvent<S> ade = (ActionDomainEvent<S>) event; ade.setSource(source); ade.setIdentifier(identifier); ade.setArguments(asList(arguments)); return ade; } } for (final Constructor<?> constructor : constructors) { final Class<?>[] parameterTypes = constructor.getParameterTypes(); if(parameterTypes.length != 3) { continue; } if(!parameterTypes[0].isAssignableFrom(source.getClass())) { continue; } if(!parameterTypes[1].isAssignableFrom(Identifier.class)) { continue; } if(!parameterTypes[2].isAssignableFrom(Object[].class)) { continue; } final Object event = constructor.newInstance(source, identifier, arguments); return (ActionDomainEvent<S>) event; } throw new NoSuchMethodException(type.getName()+".<init>(? super " + source.getClass().getName() + ", " + Identifier.class.getName() + ", [Ljava.lang.Object;)"); } // same as in ActionDomainEvent's constructor. private static List<Object> asList(final Object[] arguments) { return arguments != null ? Arrays.asList(arguments) : Collections.emptyList(); } //endregion //region > postEventForProperty, newPropertyInteraction public PropertyDomainEvent<?, ?> postEventForProperty( final AbstractDomainEvent.Phase phase, final Class eventType, final PropertyDomainEvent<?, ?> existingEvent, final IdentifiedHolder identified, final ObjectAdapter targetAdapter, final Object oldValue, final Object newValue) { try { final PropertyDomainEvent<?, ?> event; final Object source = ObjectAdapter.Util.unwrap(targetAdapter); final Identifier identifier = identified.getIdentifier(); if(existingEvent != null && phase.isExecuted()) { // reuse existing event from the executing phase event = existingEvent; } else { // all other phases, create a new event event = newPropertyDomainEvent(eventType, identifier, source, oldValue, newValue); } event.setEventPhase(phase); event.setPhase(AbstractInteractionEvent.Phase.from(phase)); // just in case the actual new value held by the object is different from that applied setEventNewValue(event, newValue); this.getEventBusService().post(event); return event; } catch (Exception e) { throw new FatalException(e); } } private static <S,T> void setEventNewValue(PropertyDomainEvent<S, T> event, Object newValue) { event.setNewValue((T) newValue); } @SuppressWarnings("unchecked") static <S,T> PropertyDomainEvent<S,T> newPropertyDomainEvent( final Class<? extends PropertyDomainEvent<S, T>> type, final Identifier identifier, final S source, final T oldValue, final T newValue) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { final Constructor<?>[] constructors = type.getConstructors(); // no-arg constructor for (final Constructor<?> constructor : constructors) { final Class<?>[] parameterTypes = constructor.getParameterTypes(); if(parameterTypes.length == 0) { final Object event = constructor.newInstance(); final PropertyDomainEvent<S, T> pde = (PropertyDomainEvent<S, T>) event; pde.setSource(source); pde.setIdentifier(identifier); pde.setOldValue(oldValue); pde.setNewValue(newValue); return pde; } } // else for (final Constructor<?> constructor : constructors) { final Class<?>[] parameterTypes = constructor.getParameterTypes(); if(parameterTypes.length != 4) { continue; } if(!parameterTypes[0].isAssignableFrom(source.getClass())) { continue; } if(!parameterTypes[1].isAssignableFrom(Identifier.class)) { continue; } if(oldValue != null && !parameterTypes[2].isAssignableFrom(oldValue.getClass())) { continue; } if(newValue != null && !parameterTypes[3].isAssignableFrom(newValue.getClass())) { continue; } final Object event = constructor.newInstance(source, identifier, oldValue, newValue); return (PropertyDomainEvent<S, T>) event; } throw new NoSuchMethodException(type.getName()+".<init>(? super " + source.getClass().getName() + ", " + Identifier.class.getName() + ", java.lang.Object, java.lang.Object)"); } //endregion //region > postEventForCollection, newCollectionDomainEvent public CollectionDomainEvent<?, ?> postEventForCollection( AbstractDomainEvent.Phase phase, final Class eventType, final CollectionDomainEvent<?, ?> existingEvent, final IdentifiedHolder identified, final ObjectAdapter targetAdapter, final CollectionDomainEvent.Of of, final Object reference) { try { final CollectionDomainEvent<?, ?> event; if (existingEvent != null && phase.isExecuted()) { // reuse existing event from the executing phase event = existingEvent; } else { // all other phases, create a new event final Object source = ObjectAdapter.Util.unwrap(targetAdapter); final Identifier identifier = identified.getIdentifier(); event = newCollectionDomainEvent(eventType, phase, identifier, source, of, reference); } event.setEventPhase(phase); event.setPhase(AbstractInteractionEvent.Phase.from(phase)); getEventBusService().post(event); return event; } catch (Exception e) { throw new FatalException(e); } } @SuppressWarnings("unchecked") <S, T> CollectionDomainEvent<S, T> newCollectionDomainEvent( final Class<? extends CollectionDomainEvent<S, T>> type, final AbstractDomainEvent.Phase phase, final Identifier identifier, final S source, final CollectionDomainEvent.Of of, final T value) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { final Constructor<?>[] constructors = type.getConstructors(); // no-arg constructor for (final Constructor<?> constructor : constructors) { final Class<?>[] parameterTypes = constructor.getParameterTypes(); if(parameterTypes.length ==0) { final Object event = constructor.newInstance(); final CollectionDomainEvent<S, T> cde = (CollectionDomainEvent<S, T>) event; cde.setSource(source); cde.setIdentifier(identifier); cde.setOf(of); cde.setValue(value); return cde; } } // search for constructor accepting source, identifier, type, value for (final Constructor<?> constructor : constructors) { final Class<?>[] parameterTypes = constructor.getParameterTypes(); if(parameterTypes.length != 4) { continue; } if(!parameterTypes[0].isAssignableFrom(source.getClass())) { continue; } if(!parameterTypes[1].isAssignableFrom(Identifier.class)) { continue; } if(!parameterTypes[2].isAssignableFrom(CollectionDomainEvent.Of.class)) { continue; } if(value != null && !parameterTypes[3].isAssignableFrom(value.getClass())) { continue; } final Object event = constructor.newInstance(source, identifier, of, value); return (CollectionDomainEvent<S, T>) event; } if(phase == AbstractDomainEvent.Phase.EXECUTED) { if(of == CollectionDomainEvent.Of.ADD_TO) { // support for @PostsCollectionAddedTo annotation: // search for constructor accepting source, identifier, value for (final Constructor<?> constructor : constructors) { final Class<?>[] parameterTypes = constructor.getParameterTypes(); if(parameterTypes.length != 3) { continue; } if(!parameterTypes[0].isAssignableFrom(source.getClass())) { continue; } if(!parameterTypes[1].isAssignableFrom(Identifier.class)) { continue; } if(value != null && !parameterTypes[2].isAssignableFrom(value.getClass())) { continue; } final Object event = constructor.newInstance(source, identifier, value); return (CollectionDomainEvent<S, T>) event; } } else if(of == CollectionDomainEvent.Of.REMOVE_FROM) { // support for @PostsCollectionRemovedFrom annotation: // search for constructor accepting source, identifier, value for (final Constructor<?> constructor : constructors) { final Class<?>[] parameterTypes = constructor.getParameterTypes(); if(parameterTypes.length != 3) { continue; } if(!parameterTypes[0].isAssignableFrom(source.getClass())) { continue; } if(!parameterTypes[1].isAssignableFrom(Identifier.class)) { continue; } if(value != null && !parameterTypes[2].isAssignableFrom(value.getClass())) { continue; } final Object event = constructor.newInstance( source, identifier, value); return (CollectionDomainEvent<S, T>) event; } } } throw new NoSuchMethodException(type.getName()+".<init>(? super " + source.getClass().getName() + ", " + Identifier.class.getName() + ", java.lang.Object)"); } //endregion //region > eventBusService private EventBusService getEventBusService() { // previously this method used to cache, however it prevents integration tests // from switching out the EventBusService with a mock. return this.servicesInjector.lookupService(EventBusService.class); } //endregion }