This is an automated email from the ASF dual-hosted git repository. ahuber pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/isis.git
The following commit(s) were added to refs/heads/master by this push: new 5bf49c6 ISIS-1960: Introduces a ForkingInvocationHandler that executes actions in the background 5bf49c6 is described below commit 5bf49c608b8ddae9d3d5e38559debf070b3637cd Author: Andi Huber <ahu...@apache.org> AuthorDate: Sun Jun 3 18:24:12 2018 +0200 ISIS-1960: Introduces a ForkingInvocationHandler that executes actions in the background Task-Url: https://issues.apache.org/jira/browse/ISIS-1960 --- .../background/BackgroundServiceDefault.java | 145 +++-------------- .../background/CommandInvocationHandler.java | 172 +++++++++++++++++++++ .../background/ForkingInvocationHandler.java | 71 +++++++++ .../PersistenceSessionServiceInternalDefault.java | 13 +- .../runtime/system/session/IsisSessionFactory.java | 16 +- 5 files changed, 287 insertions(+), 130 deletions(-) diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundServiceDefault.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundServiceDefault.java index 3f11b89..14cf750 100644 --- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundServiceDefault.java +++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundServiceDefault.java @@ -1,4 +1,4 @@ -/** +/* * 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. @@ -17,11 +17,9 @@ package org.apache.isis.core.runtime.services.background; import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.util.Collections; -import java.util.List; import java.util.Map; -import java.util.Objects; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -32,27 +30,18 @@ import org.apache.isis.applib.annotation.Programmatic; import org.apache.isis.applib.services.background.BackgroundCommandService; import org.apache.isis.applib.services.background.BackgroundCommandService2; import org.apache.isis.applib.services.background.BackgroundService2; -import org.apache.isis.applib.services.command.Command; import org.apache.isis.applib.services.command.CommandContext; import org.apache.isis.applib.services.factory.FactoryService; import org.apache.isis.commons.internal._Constants; import org.apache.isis.commons.internal.base._Casts; import org.apache.isis.core.commons.lang.ArrayExtensions; -import org.apache.isis.core.metamodel.adapter.ObjectAdapter; import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager; -import org.apache.isis.core.metamodel.facets.actions.action.invocation.CommandUtil; import org.apache.isis.core.metamodel.services.command.CommandDtoServiceInternal; import org.apache.isis.core.metamodel.spec.ObjectSpecification; -import org.apache.isis.core.metamodel.spec.feature.Contributed; -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.core.metamodel.specloader.classsubstitutor.ProxyEnhanced; -import org.apache.isis.core.metamodel.specloader.specimpl.ObjectActionMixedIn; -import org.apache.isis.core.metamodel.specloader.specimpl.dflt.ObjectSpecificationDefault; import org.apache.isis.core.plugins.codegen.ProxyFactory; import org.apache.isis.core.runtime.system.session.IsisSessionFactory; -import org.apache.isis.schema.cmd.v1.CommandDto; /** * Depends on an implementation of {@link org.apache.isis.applib.services.background.BackgroundCommandService} to @@ -64,7 +53,11 @@ import org.apache.isis.schema.cmd.v1.CommandDto; ) public class BackgroundServiceDefault implements BackgroundService2 { - + private final int threadCount = Runtime.getRuntime().availableProcessors(); + + private final ExecutorService backgroundExecutorService = + Executors.newFixedThreadPool(threadCount); + @Programmatic @PostConstruct public void init(Map<String,String> props) { @@ -73,31 +66,13 @@ public class BackgroundServiceDefault implements BackgroundService2 { @Programmatic @PreDestroy public void shutdown() { - + backgroundExecutorService.shutdownNow(); } - - // ////////////////////////////////////// - - - private ObjectSpecificationDefault getJavaSpecificationOfOwningClass(final Method method) { - return getJavaSpecification(method.getDeclaringClass()); - } - - private ObjectSpecificationDefault getJavaSpecification(final Class<?> cls) { - final ObjectSpecification objectSpec = getSpecification(cls); - if (!(objectSpec instanceof ObjectSpecificationDefault)) { - throw new UnsupportedOperationException( - "Only Java is supported " - + "(specification is '" + objectSpec.getClass().getCanonicalName() + "')"); - } - return (ObjectSpecificationDefault) objectSpec; - } - - private ObjectSpecification getSpecification(final Class<?> type) { + + ObjectSpecification getSpecification(final Class<?> type) { return specificationLoader.loadSpecification(type); } - // ////////////////////////////////////// @Programmatic @@ -147,90 +122,20 @@ public class BackgroundServiceDefault implements BackgroundService2 { * @param mixedInIfAny - if target is a mixin, then this is the domain object that is mixed-in to. */ private <T> InvocationHandler newMethodHandler(final T target, final Object mixedInIfAny) { - return new InvocationHandler() { - @Override - public Object invoke( - final Object proxied, - final Method proxyMethod, - final Object[] args) throws Throwable { - - final boolean inheritedFromObject = proxyMethod.getDeclaringClass().equals(Object.class); - if(inheritedFromObject) { - return proxyMethod.invoke(target, args); - } - - final ObjectSpecificationDefault targetObjSpec = getJavaSpecificationOfOwningClass(proxyMethod); - final ObjectMember member = targetObjSpec.getMember(proxyMethod); - - if(member == null) { - return proxyMethod.invoke(target, args); - } - - if(!(member instanceof ObjectAction)) { - throw new UnsupportedOperationException( - "Only actions can be executed in the background " - + "(method " + proxyMethod.getName() + " represents a " + member.getFeatureType().name() + "')"); - } - - ObjectAction action = (ObjectAction) member; - - final Object domainObject; - if (mixedInIfAny == null) { - domainObject = target; - } else { - domainObject = mixedInIfAny; - // replace action with the mixedIn action of the domain object itself - action = findMixedInAction(action, mixedInIfAny); - } - - final ObjectAdapter domainObjectAdapter = getAdapterManager().adapterFor(domainObject); - final String domainObjectClassName = CommandUtil.targetClassNameFor(domainObjectAdapter); - - final String targetActionName = CommandUtil.targetMemberNameFor(action); - - final ObjectAdapter[] argAdapters = adaptersFor(args); - final String targetArgs = CommandUtil.argDescriptionFor(action, argAdapters); - - final Command command = commandContext.getCommand(); - - final BackgroundCommandService2 bcs2 = (BackgroundCommandService2) backgroundCommandService; - Objects.requireNonNull(bcs2, - ()->String.format("You need to provide a domain-service implementing '%s'!", - BackgroundCommandService2.class.getName())); - - final List<ObjectAdapter> targetList = Collections.singletonList(domainObjectAdapter); - final CommandDto dto = - commandDtoServiceInternal.asCommandDto(targetList, action, argAdapters); - - bcs2.schedule(dto, command, domainObjectClassName, targetActionName, targetArgs); - - - return null; - } - - private ObjectAction findMixedInAction(final ObjectAction action, final Object domainObject) { - final String actionId = action.getId(); - final ObjectSpecification domainSpec = getAdapterManager().adapterFor(domainObject).getSpecification(); - List<ObjectAction> objectActions = domainSpec.getObjectActions(Contributed.INCLUDED); - for (ObjectAction objectAction : objectActions) { - if(objectAction instanceof ObjectActionMixedIn) { - ObjectActionMixedIn objectActionMixedIn = (ObjectActionMixedIn) objectAction; - if(objectActionMixedIn.hasMixinAction(action)) { - return objectActionMixedIn; - } - } - } - - throw new IllegalArgumentException(String.format( - "Unable to find mixin action '%s' for %s", actionId, domainSpec.getFullIdentifier())); - } - - ObjectAdapter[] adaptersFor(final Object[] args) { - final AdapterManager adapterManager = getAdapterManager(); - return CommandUtil.adaptersFor(args, adapterManager); - } - - }; + + if(backgroundCommandService==null) { + return new ForkingInvocationHandler<T>(target, mixedInIfAny, backgroundExecutorService); + } + + return new CommandInvocationHandler<T>( + (BackgroundCommandService2) backgroundCommandService, + target, + mixedInIfAny, + specificationLoader, + commandDtoServiceInternal, + commandContext, + this::getAdapterManager); + } diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/CommandInvocationHandler.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/CommandInvocationHandler.java new file mode 100644 index 0000000..0c1b090 --- /dev/null +++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/CommandInvocationHandler.java @@ -0,0 +1,172 @@ +/* + * 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.runtime.services.background; + +import static org.apache.isis.commons.internal.base._With.requires; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +import org.apache.isis.applib.services.background.BackgroundCommandService2; +import org.apache.isis.applib.services.command.Command; +import org.apache.isis.applib.services.command.CommandContext; +import org.apache.isis.core.metamodel.adapter.ObjectAdapter; +import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager; +import org.apache.isis.core.metamodel.facets.actions.action.invocation.CommandUtil; +import org.apache.isis.core.metamodel.services.command.CommandDtoServiceInternal; +import org.apache.isis.core.metamodel.spec.ObjectSpecification; +import org.apache.isis.core.metamodel.spec.feature.Contributed; +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.core.metamodel.specloader.specimpl.ObjectActionMixedIn; +import org.apache.isis.core.metamodel.specloader.specimpl.dflt.ObjectSpecificationDefault; +import org.apache.isis.schema.cmd.v1.CommandDto; + +class CommandInvocationHandler<T> implements InvocationHandler { + + private final BackgroundCommandService2 backgroundCommandService; + private final T target; + private final Object mixedInIfAny; + private final SpecificationLoader specificationLoader; + private final CommandDtoServiceInternal commandDtoServiceInternal; + private final CommandContext commandContext; + private final Supplier<AdapterManager> adapterManagerSupplier; + + CommandInvocationHandler( + BackgroundCommandService2 backgroundCommandService, + T target, + Object mixedInIfAny, + SpecificationLoader specificationLoader, + CommandDtoServiceInternal commandDtoServiceInternal, + CommandContext commandContext, + Supplier<AdapterManager> adapterManagerSupplier) { + this.backgroundCommandService = requires(backgroundCommandService, "backgroundCommandService"); + this.target = requires(target, "target"); + this.mixedInIfAny = mixedInIfAny; + this.specificationLoader = requires(specificationLoader, "specificationLoader"); + this.commandDtoServiceInternal = requires(commandDtoServiceInternal, "commandDtoServiceInternal"); + this.commandContext = requires(commandContext, "commandContext"); + this.adapterManagerSupplier = requires(adapterManagerSupplier, "adapterManagerSupplier"); + } + + @Override + public Object invoke( + final Object proxied, + final Method proxyMethod, + final Object[] args) throws Throwable { + + final boolean inheritedFromObject = proxyMethod.getDeclaringClass().equals(Object.class); + if(inheritedFromObject) { + return proxyMethod.invoke(target, args); + } + + final ObjectSpecificationDefault targetObjSpec = getJavaSpecificationOfOwningClass(proxyMethod); + final ObjectMember member = targetObjSpec.getMember(proxyMethod); + + if(member == null) { + return proxyMethod.invoke(target, args); + } + + if(!(member instanceof ObjectAction)) { + throw new UnsupportedOperationException( + "Only actions can be executed in the background " + + "(method " + proxyMethod.getName() + " represents a " + member.getFeatureType().name() + "')"); + } + + ObjectAction action = (ObjectAction) member; + + final Object domainObject; + if (mixedInIfAny == null) { + domainObject = target; + } else { + domainObject = mixedInIfAny; + // replace action with the mixedIn action of the domain object itself + action = findMixedInAction(action, mixedInIfAny); + } + + final ObjectAdapter domainObjectAdapter = getAdapterManager().adapterFor(domainObject); + final String domainObjectClassName = CommandUtil.targetClassNameFor(domainObjectAdapter); + + final String targetActionName = CommandUtil.targetMemberNameFor(action); + + final ObjectAdapter[] argAdapters = adaptersFor(args); + final String targetArgs = CommandUtil.argDescriptionFor(action, argAdapters); + + final Command command = commandContext.getCommand(); + + final List<ObjectAdapter> targetList = Collections.singletonList(domainObjectAdapter); + final CommandDto dto = + commandDtoServiceInternal.asCommandDto(targetList, action, argAdapters); + + backgroundCommandService + .schedule(dto, command, domainObjectClassName, targetActionName, targetArgs); + + return null; + } + + // -- HELPER + + private AdapterManager getAdapterManager() { + return adapterManagerSupplier.get(); + } + + private ObjectAction findMixedInAction(final ObjectAction action, final Object domainObject) { + final String actionId = action.getId(); + final ObjectSpecification domainSpec = getAdapterManager().adapterFor(domainObject).getSpecification(); + List<ObjectAction> objectActions = domainSpec.getObjectActions(Contributed.INCLUDED); + for (ObjectAction objectAction : objectActions) { + if(objectAction instanceof ObjectActionMixedIn) { + ObjectActionMixedIn objectActionMixedIn = (ObjectActionMixedIn) objectAction; + if(objectActionMixedIn.hasMixinAction(action)) { + return objectActionMixedIn; + } + } + } + + throw new IllegalArgumentException(String.format( + "Unable to find mixin action '%s' for %s", actionId, domainSpec.getFullIdentifier())); + } + + private ObjectAdapter[] adaptersFor(final Object[] args) { + final AdapterManager adapterManager = getAdapterManager(); + return CommandUtil.adaptersFor(args, adapterManager); + } + + private ObjectSpecificationDefault getJavaSpecificationOfOwningClass(final Method method) { + return getJavaSpecification(method.getDeclaringClass()); + } + + private ObjectSpecificationDefault getJavaSpecification(final Class<?> cls) { + final ObjectSpecification objectSpec = getSpecification(cls); + if (!(objectSpec instanceof ObjectSpecificationDefault)) { + throw new UnsupportedOperationException( + "Only Java is supported " + + "(specification is '" + objectSpec.getClass().getCanonicalName() + "')"); + } + return (ObjectSpecificationDefault) objectSpec; + } + + private ObjectSpecification getSpecification(final Class<?> type) { + return specificationLoader.loadSpecification(type); + } + + +} diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/ForkingInvocationHandler.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/ForkingInvocationHandler.java new file mode 100644 index 0000000..9ac2593 --- /dev/null +++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/ForkingInvocationHandler.java @@ -0,0 +1,71 @@ +/* + * 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.runtime.services.background; + +import static org.apache.isis.commons.internal.base._With.requires; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.concurrent.ExecutorService; + +import org.apache.isis.core.runtime.system.context.IsisContext; + +/** + * Package private invocation handler that executes actions in the background using a ExecutorService + * @since 2.0.0 + */ +class ForkingInvocationHandler<T> implements InvocationHandler { + + private final T target; + private final Object mixedInIfAny; + private final ExecutorService backgroundExecutorService; + + ForkingInvocationHandler( + T target, + Object mixedInIfAny, + ExecutorService backgroundExecutorService ) { + this.target = requires(target, "target"); + this.mixedInIfAny = mixedInIfAny; + this.backgroundExecutorService = requires(backgroundExecutorService, "backgroundExecutorService"); + } + + @Override + public Object invoke( + final Object proxied, + final Method proxyMethod, + final Object[] args) throws Throwable { + + final boolean inheritedFromObject = proxyMethod.getDeclaringClass().equals(Object.class); + if(inheritedFromObject) { + return proxyMethod.invoke(target, args); + } + + final Object domainObject; + if (mixedInIfAny == null) { + domainObject = target; + } else { + domainObject = mixedInIfAny; + } + + backgroundExecutorService.submit(()->{ + IsisContext.getSessionFactory().doInSession(()->proxyMethod.invoke(domainObject, args)); + }); + + return null; + } + +} diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/persistsession/PersistenceSessionServiceInternalDefault.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/persistsession/PersistenceSessionServiceInternalDefault.java index 5a8fc46..480f440 100644 --- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/persistsession/PersistenceSessionServiceInternalDefault.java +++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/persistsession/PersistenceSessionServiceInternalDefault.java @@ -18,16 +18,20 @@ */ package org.apache.isis.core.runtime.services.persistsession; +import static java.util.Objects.requireNonNull; +import static java.util.Optional.ofNullable; + import java.util.List; +import org.apache.isis.applib.NonRecoverableException; import org.apache.isis.applib.annotation.DomainService; import org.apache.isis.applib.annotation.NatureOfService; import org.apache.isis.applib.annotation.Programmatic; import org.apache.isis.applib.query.Query; import org.apache.isis.applib.services.bookmark.Bookmark; import org.apache.isis.applib.services.bookmark.BookmarkService; -import org.apache.isis.applib.services.xactn.Transaction; import org.apache.isis.applib.services.command.Command; +import org.apache.isis.applib.services.xactn.Transaction; import org.apache.isis.applib.services.xactn.TransactionState; import org.apache.isis.core.metamodel.adapter.ObjectAdapter; import org.apache.isis.core.metamodel.adapter.oid.Oid; @@ -37,6 +41,7 @@ import org.apache.isis.core.metamodel.spec.ObjectSpecification; import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation; import org.apache.isis.core.metamodel.specloader.SpecificationLoader; import org.apache.isis.core.runtime.system.persistence.PersistenceSession; +import org.apache.isis.core.runtime.system.session.IsisSession; import org.apache.isis.core.runtime.system.session.IsisSessionFactory; import org.apache.isis.core.runtime.system.transaction.IsisTransaction; import org.apache.isis.core.runtime.system.transaction.IsisTransactionManager; @@ -203,11 +208,13 @@ public class PersistenceSessionServiceInternalDefault implements PersistenceSess } protected PersistenceSession getPersistenceSession() { - return getIsisSessionFactory().getCurrentSession().getPersistenceSession(); + return ofNullable(getIsisSessionFactory().getCurrentSession()) + .map(IsisSession::getPersistenceSession) + .orElseThrow(()->new NonRecoverableException("No IsisSession on current thread.")); } private IsisSessionFactory getIsisSessionFactory() { - return isisSessionFactory; + return requireNonNull(isisSessionFactory, "IsisSessionFactory was not injected."); } @Programmatic diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSessionFactory.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSessionFactory.java index 54d76a2..c9fece3 100644 --- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSessionFactory.java +++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSessionFactory.java @@ -23,10 +23,6 @@ import java.util.List; import java.util.concurrent.Callable; import javax.inject.Inject; -import com.google.common.collect.Lists; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.apache.isis.applib.AppManifest; import org.apache.isis.applib.annotation.Programmatic; @@ -53,6 +49,10 @@ import org.apache.isis.core.runtime.system.persistence.PersistenceSession; import org.apache.isis.core.runtime.system.persistence.PersistenceSessionFactory; import org.apache.isis.core.runtime.system.transaction.IsisTransactionManager; import org.apache.isis.core.runtime.system.transaction.IsisTransactionManagerException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Lists; /** * Is the factory of {@link IsisSession}s, also holding a reference to the current session using @@ -160,7 +160,8 @@ public class IsisSessionFactory final List<Object> copyOfServices = Lists.newArrayList(services); final TitleService titleService = servicesInjector.lookupServiceElseFail(TitleService.class); for (Object service : copyOfServices) { - final String unused = titleService.titleOf(service); + @SuppressWarnings("unused") + final String unused = titleService.titleOf(service); } // (previously we took a protective copy to avoid a concurrent modification exception, @@ -170,7 +171,8 @@ public class IsisSessionFactory if(correspondingClass.isEnum()) { final Object[] enumConstants = correspondingClass.getEnumConstants(); for (Object enumConstant : enumConstants) { - final String unused = titleService.titleOf(enumConstant); + @SuppressWarnings("unused") + final String unused = titleService.titleOf(enumConstant); } } } @@ -283,7 +285,7 @@ public class IsisSessionFactory @Programmatic public IsisSession getCurrentSession() { - return currentSession.get(); + return currentSession.get(); } private IsisTransactionManager getCurrentSessionTransactionManager() { -- To stop receiving notification emails like this one, please contact ahu...@apache.org.