You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2018/10/30 17:30:50 UTC
[isis] 01/02: Merge branch 'master' into v2
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch v2
in repository https://gitbox.apache.org/repos/asf/isis.git
commit eb7310a04d1d046cf724ae43a544094f9315e96e
Merge: 3040c3f a3b13aa
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Tue Oct 30 09:59:38 2018 +0000
Merge branch 'master' into v2
ISIS-2027 (health check)
# Conflicts:
# core/applib/src/main/java/org/apache/isis/applib/services/wrapper/WrapperFactory.java
# example/application/simpleapp/webapp/src/main/webapp/WEB-INF/web.xml
.../guides/rgsvc/_rgsvc_application-layer-spi.adoc | 8 ++
...c_application-layer-spi_HealthCheckService.adoc | 53 +++++++++++
...lication-layer-spi_HomePageProviderService.adoc | 4 +-
.../main/asciidoc/guides/ugbtb/_ugbtb_web-xml.adoc | 79 +++++++++-------
.../apache/isis/applib/services/health/Health.java | 39 ++++++++
.../applib/services/health/HealthCheckService.java | 33 +++++++
.../applib/services/wrapper/WrapperFactory.java | 31 ++++++-
.../commons/authentication/HealthAuthSession.java | 44 +++++++++
.../apache/isis/core/webapp/IsisSessionFilter.java | 22 ++++-
.../core/webapp/modules/WebModule_RestEasy.java | 18 ++--
.../isis/core/wrapper/WrapperFactoryDefault.java | 5 +
.../handlers/DomainObjectInvocationHandler.java | 63 +++++++++++--
.../IsisSessionFilter_lookupPassThru_Test.java | 80 ++++++++++++++++
.../restfulobjects/applib/RepresentationType.java | 6 +-
.../restfulobjects/applib/RestfulMediaType.java | 1 +
.../applib/health/HealthRepresentation.java | 40 ++++++++
.../applib/health/HealthResource.java | 44 +++++++++
.../server/RestfulObjectsApplication.java | 2 +
.../server/resources/HealthReprRenderer.java | 54 +++++++++++
.../server/resources/HealthResourceServerside.java | 102 +++++++++++++++++++++
.../application/isis-non-changing.properties | 1 -
.../src/main/webapp/WEB-INF/isis.properties | 5 +-
.../services/health/HealthCheckServiceImpl.java | 29 ++++++
.../manifest/persistor_datanucleus.properties | 1 -
...tion.services.health.HealthCheckServiceImpl.xml | 45 +++++++++
.../modules/simple/dom/impl/SimpleObjects.java | 8 ++
.../webapp/src/main/webapp/WEB-INF/isis.properties | 37 ++++----
27 files changed, 770 insertions(+), 84 deletions(-)
diff --cc core/applib/src/main/java/org/apache/isis/applib/services/wrapper/WrapperFactory.java
index e38d253,2bd8a21..c7f1b26
--- a/core/applib/src/main/java/org/apache/isis/applib/services/wrapper/WrapperFactory.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/wrapper/WrapperFactory.java
@@@ -112,24 -119,12 +121,29 @@@ public interface WrapperFactory
return domainObject;
}
+ @Override public <T> T w(final T domainObject) {
+ return wrap(domainObject);
+ }
+
+ @Inject
+ FactoryService factoryService;
+
+ @Override
+ public <T> T wm(final Class<T> mixinClass, final Object mixedIn) {
+ return wrapMixin(mixinClass, mixedIn);
+ }
+
+ @Override
+ public <T> T wrapMixin(final Class<T> mixinClass, final Object mixedIn) {
+ return wrap(factoryService.m(mixinClass, mixedIn));
+ }
+
@Override
+ public <T> T wrapTry(T domainObject) {
+ return domainObject;
+ }
-
++
+ @Override
public <T> T wrapNoExecute(T domainObject) {
return domainObject;
}
@@@ -185,24 -180,13 +199,31 @@@
<T> T wrap(T domainObject);
/**
+ * Alias for {@link #wrap(Object)}.
+ */
+ @Programmatic
+ <T> T w(T domainObject);
+
+ /**
+ * Alias for {@link #wrapMixin(Class, Object)}
+ */
+ @Programmatic
+ <T> T wm(Class<T> mixinClass, Object mixedIn);
+
+ /**
+ * {@link #wrap(Object) wraps} a {@link FactoryService#mixin(Class, Object) mixin}.
+ */
+ @Programmatic
+ <T> T wrapMixin(Class<T> mixinClass, Object mixedIn);
+
+ /**
+ * Convenience method for {@link #wrap(Object, ExecutionMode)} with {@link ExecutionMode#TRY},
+ * to make this feature more discoverable.
+ */
+ @Programmatic
+ <T> T wrapTry(T domainObject);
+
+ /**
* Convenience method for {@link #wrap(Object, ExecutionMode)} with {@link ExecutionMode#NO_EXECUTE},
* to make this feature more discoverable.
*/
diff --cc core/runtime/src/main/java/org/apache/isis/core/webapp/modules/WebModule_RestEasy.java
index cf22749,0000000..c9465e4
mode 100644,000000..100644
--- a/core/runtime/src/main/java/org/apache/isis/core/webapp/modules/WebModule_RestEasy.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/webapp/modules/WebModule_RestEasy.java
@@@ -1,163 -1,0 +1,163 @@@
+/*
+ * 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.webapp.modules;
+
- import static java.util.Objects.requireNonNull;
- import static org.apache.isis.commons.internal.base._Casts.uncheckedCast;
- import static org.apache.isis.commons.internal.base._Strings.prefix;
- import static org.apache.isis.commons.internal.base._Strings.suffix;
- import static org.apache.isis.commons.internal.context._Context.getDefaultClassLoader;
- import static org.apache.isis.commons.internal.exceptions._Exceptions.unexpectedCodeReach;
- import static org.apache.isis.commons.internal.resources._Resources.putRestfulPath;
-
+import javax.servlet.FilterRegistration.Dynamic;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+
+import org.apache.isis.core.webapp.IsisSessionFilter;
+import org.apache.isis.core.webapp.IsisWebAppConfigProvider;
+
++import static java.util.Objects.requireNonNull;
++import static org.apache.isis.commons.internal.base._Casts.uncheckedCast;
++import static org.apache.isis.commons.internal.base._Strings.prefix;
++import static org.apache.isis.commons.internal.base._Strings.suffix;
++import static org.apache.isis.commons.internal.context._Context.getDefaultClassLoader;
++import static org.apache.isis.commons.internal.exceptions._Exceptions.unexpectedCodeReach;
++import static org.apache.isis.commons.internal.resources._Resources.putRestfulPath;
++
+/**
+ * Package private mixin for WebModule implementing WebModule.
+ *
+ * @since 2.0.0
+ */
+final class WebModule_RestEasy implements WebModule {
+
+ public static final String KEY_RESTFUL_BASE_PATH = "isis.viewer.restfulobjects.basePath";
+ public static final String KEY_RESTFUL_BASE_PATH_DEFAULT = "/restful";
+
+ private final static String RESTEASY_BOOTSTRAPPER =
+ "org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap";
+
+ private final static String RESTEASY_DISPATCHER = "RestfulObjectsRestEasyDispatcher";
+
+ String restfulPathConfigValue;
+
+ @Override
+ public String getName() {
+ return "RestEasy";
+ }
+
+ @Override
+ public void prepare(ServletContext ctx) {
+
+ if(!isApplicable(ctx)) {
+ return;
+ }
+
+ // try to fetch restfulPath from config else fallback to default
+ final String restfulPath =
+ IsisWebAppConfigProvider.getInstance()
+ .peekAtOrDefault(ctx, KEY_RESTFUL_BASE_PATH, KEY_RESTFUL_BASE_PATH_DEFAULT);
+
+ putRestfulPath(restfulPath);
+
+ this.restfulPathConfigValue = restfulPath; // store locally for reuse
+
+ // register this module as a viewer
+ ContextUtil.registerViewer(ctx, "restfulobjects");
+ ContextUtil.registerProtectedPath(ctx, suffix(prefix(restfulPath, "/"), "/") + "*" );
+ }
+
+ @Override
+ public ServletContextListener init(ServletContext ctx) throws ServletException {
+
+ // add IsisSessionFilters
+
+ {
+ final Dynamic filter = ctx.addFilter("IsisSessionFilterForRestfulObjects", IsisSessionFilter.class);
+
+ // this is mapped to the entire application;
+ // however the IsisSessionFilter will
+ // "notice" if the session filter has already been
+ // executed for the request pipeline, and if so will do nothing
+ filter.addMappingForServletNames(null, true, RESTEASY_DISPATCHER);
+
+ filter.setInitParameter(
+ "authenticationSessionStrategy",
+ "org.apache.isis.viewer.restfulobjects.server.authentication.AuthenticationSessionStrategyBasicAuth");
+ filter.setInitParameter(
+ "whenNoSession", // what to do if no session was found ...
+ "auto"); // ... 401 and a basic authentication challenge if request originates from web browser
+ filter.setInitParameter(
+ "passThru",
- getRestfulPath()+"swagger");
++ String.join(",", getRestfulPath()+"swagger", getRestfulPath()+"health"));
+
+ }
+
+ {
+ final Dynamic filter = ctx.addFilter("RestfulObjectsRestEasyDispatcher",
+ "org.apache.isis.viewer.restfulobjects.server.webapp.IsisTransactionFilterForRestfulObjects");
+ filter.addMappingForServletNames(null, true, RESTEASY_DISPATCHER);
+ }
+
+
+
+ // add RestEasy
+
+ // used by RestEasy to determine the JAX-RS resources and other related configuration
+ ctx.setInitParameter(
+ "javax.ws.rs.Application",
+ "org.apache.isis.viewer.restfulobjects.server.RestfulObjectsApplication");
+
+ ctx.setInitParameter("resteasy.servlet.mapping.prefix", getRestfulPath());
+
+ ctx.addServlet(RESTEASY_DISPATCHER,
+ "org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher");
+ ctx.getServletRegistration(RESTEASY_DISPATCHER)
+ .addMapping(getUrlPattern());
+
+ try {
+ final Class<?> listenerClass = getDefaultClassLoader().loadClass(RESTEASY_BOOTSTRAPPER);
+ return ctx.createListener(uncheckedCast(listenerClass));
+ } catch (ClassNotFoundException e) {
+ // guarded against by isAvailable()
+ throw unexpectedCodeReach();
+ }
+
+ }
+
+ @Override
+ public boolean isApplicable(ServletContext ctx) {
+ try {
+ getDefaultClassLoader().loadClass(RESTEASY_BOOTSTRAPPER);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ // -- HELPER
+
+ private String getUrlPattern() {
+ return getRestfulPath() + "*";
+ }
+
+ private String getRestfulPath() {
+ requireNonNull(restfulPathConfigValue, "This web-module needs to be prepared first.");
+ final String restfulPathEnclosedWithSlashes = suffix(prefix(restfulPathConfigValue, "/"), "/");
+ return restfulPathEnclosedWithSlashes;
+ }
+
+
+
+
+}
diff --cc core/runtime/src/main/java/org/apache/isis/core/wrapper/WrapperFactoryDefault.java
index b56e470,0000000..75e15d2
mode 100644,000000..100644
--- a/core/runtime/src/main/java/org/apache/isis/core/wrapper/WrapperFactoryDefault.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/wrapper/WrapperFactoryDefault.java
@@@ -1,333 -1,0 +1,338 @@@
+/*
+ * 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.wrapper;
+
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+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.services.factory.FactoryService;
+import org.apache.isis.applib.services.wrapper.events.ActionArgumentEvent;
+import org.apache.isis.applib.services.wrapper.events.ActionInvocationEvent;
+import org.apache.isis.applib.services.wrapper.events.ActionUsabilityEvent;
+import org.apache.isis.applib.services.wrapper.events.ActionVisibilityEvent;
+import org.apache.isis.applib.services.wrapper.events.CollectionAccessEvent;
+import org.apache.isis.applib.services.wrapper.events.CollectionAddToEvent;
+import org.apache.isis.applib.services.wrapper.events.CollectionMethodEvent;
+import org.apache.isis.applib.services.wrapper.events.CollectionRemoveFromEvent;
+import org.apache.isis.applib.services.wrapper.events.CollectionUsabilityEvent;
+import org.apache.isis.applib.services.wrapper.events.CollectionVisibilityEvent;
+import org.apache.isis.applib.services.wrapper.events.InteractionEvent;
+import org.apache.isis.applib.services.wrapper.events.ObjectTitleEvent;
+import org.apache.isis.applib.services.wrapper.events.ObjectValidityEvent;
+import org.apache.isis.applib.services.wrapper.events.PropertyAccessEvent;
+import org.apache.isis.applib.services.wrapper.events.PropertyModifyEvent;
+import org.apache.isis.applib.services.wrapper.events.PropertyUsabilityEvent;
+import org.apache.isis.applib.services.wrapper.events.PropertyVisibilityEvent;
+import org.apache.isis.applib.services.wrapper.WrapperFactory;
+import org.apache.isis.applib.services.wrapper.WrappingObject;
+import org.apache.isis.applib.services.wrapper.listeners.InteractionListener;
+import org.apache.isis.commons.internal.base._Casts;
+import org.apache.isis.core.commons.authentication.AuthenticationSessionProvider;
+import org.apache.isis.core.metamodel.services.persistsession.PersistenceSessionServiceInternal;
+import org.apache.isis.core.runtime.system.session.IsisSessionFactory;
+import org.apache.isis.core.wrapper.dispatchers.InteractionEventDispatcher;
+import org.apache.isis.core.wrapper.dispatchers.InteractionEventDispatcherTypeSafe;
+import org.apache.isis.core.wrapper.handlers.ProxyContextHandler;
+import org.apache.isis.core.wrapper.proxy.ProxyCreator;
+
+/**
+ * This service provides the ability to "wrap" of a domain object such that it can
+ * be interacted with while enforcing the hide/disable/validate rules implies by
+ * the Isis programming model.
+ *
+ * <p>
+ * This implementation has no UI-visible actions and is the supported implementation, so it is annotated with
+ * {@link org.apache.isis.applib.annotation.DomainService}. This means that by including
+ * <tt>o.a.i.core:isis-core-wrapper</tt> on the classpath, the service is automatically registered; no further
+ * configuration is required.
+ */
+@DomainService(
+ nature = NatureOfService.DOMAIN,
+ menuOrder = "" + Integer.MAX_VALUE
+ )
+public class WrapperFactoryDefault implements WrapperFactory {
+
+ private final List<InteractionListener> listeners = new ArrayList<InteractionListener>();
+ private final Map<Class<? extends InteractionEvent>, InteractionEventDispatcher> dispatchersByEventClass = new HashMap<Class<? extends InteractionEvent>, InteractionEventDispatcher>();
+
+
+ private final ProxyContextHandler proxyContextHandler;
+
+ public WrapperFactoryDefault() {
+ this(new ProxyCreator());
+ }
+ WrapperFactoryDefault(final ProxyCreator proxyCreator) {
+
+ proxyContextHandler = new ProxyContextHandler(proxyCreator);
+
+ dispatchersByEventClass.put(ObjectTitleEvent.class, new InteractionEventDispatcherTypeSafe<ObjectTitleEvent>() {
+ @Override
+ public void dispatchTypeSafe(final ObjectTitleEvent interactionEvent) {
+ for (final InteractionListener l : getListeners()) {
+ l.objectTitleRead(interactionEvent);
+ }
+ }
+ });
+ dispatchersByEventClass.put(PropertyVisibilityEvent.class, new InteractionEventDispatcherTypeSafe<PropertyVisibilityEvent>() {
+ @Override
+ public void dispatchTypeSafe(final PropertyVisibilityEvent interactionEvent) {
+ for (final InteractionListener l : getListeners()) {
+ l.propertyVisible(interactionEvent);
+ }
+ }
+ });
+ dispatchersByEventClass.put(PropertyUsabilityEvent.class, new InteractionEventDispatcherTypeSafe<PropertyUsabilityEvent>() {
+ @Override
+ public void dispatchTypeSafe(final PropertyUsabilityEvent interactionEvent) {
+ for (final InteractionListener l : getListeners()) {
+ l.propertyUsable(interactionEvent);
+ }
+ }
+ });
+ dispatchersByEventClass.put(PropertyAccessEvent.class, new InteractionEventDispatcherTypeSafe<PropertyAccessEvent>() {
+ @Override
+ public void dispatchTypeSafe(final PropertyAccessEvent interactionEvent) {
+ for (final InteractionListener l : getListeners()) {
+ l.propertyAccessed(interactionEvent);
+ }
+ }
+ });
+ dispatchersByEventClass.put(PropertyModifyEvent.class, new InteractionEventDispatcherTypeSafe<PropertyModifyEvent>() {
+ @Override
+ public void dispatchTypeSafe(final PropertyModifyEvent interactionEvent) {
+ for (final InteractionListener l : getListeners()) {
+ l.propertyModified(interactionEvent);
+ }
+ }
+ });
+ dispatchersByEventClass.put(CollectionVisibilityEvent.class, new InteractionEventDispatcherTypeSafe<CollectionVisibilityEvent>() {
+ @Override
+ public void dispatchTypeSafe(final CollectionVisibilityEvent interactionEvent) {
+ for (final InteractionListener l : getListeners()) {
+ l.collectionVisible(interactionEvent);
+ }
+ }
+ });
+ dispatchersByEventClass.put(CollectionUsabilityEvent.class, new InteractionEventDispatcherTypeSafe<CollectionUsabilityEvent>() {
+ @Override
+ public void dispatchTypeSafe(final CollectionUsabilityEvent interactionEvent) {
+ for (final InteractionListener l : getListeners()) {
+ l.collectionUsable(interactionEvent);
+ }
+ }
+ });
+ dispatchersByEventClass.put(CollectionAccessEvent.class, new InteractionEventDispatcherTypeSafe<CollectionAccessEvent>() {
+ @Override
+ public void dispatchTypeSafe(final CollectionAccessEvent interactionEvent) {
+ for (final InteractionListener l : getListeners()) {
+ l.collectionAccessed(interactionEvent);
+ }
+ }
+ });
+ dispatchersByEventClass.put(CollectionAddToEvent.class, new InteractionEventDispatcherTypeSafe<CollectionAddToEvent>() {
+ @Override
+ public void dispatchTypeSafe(final CollectionAddToEvent interactionEvent) {
+ for (final InteractionListener l : getListeners()) {
+ l.collectionAddedTo(interactionEvent);
+ }
+ }
+ });
+ dispatchersByEventClass.put(CollectionRemoveFromEvent.class, new InteractionEventDispatcherTypeSafe<CollectionRemoveFromEvent>() {
+ @Override
+ public void dispatchTypeSafe(final CollectionRemoveFromEvent interactionEvent) {
+ for (final InteractionListener l : getListeners()) {
+ l.collectionRemovedFrom(interactionEvent);
+ }
+ }
+ });
+ dispatchersByEventClass.put(ActionVisibilityEvent.class, new InteractionEventDispatcherTypeSafe<ActionVisibilityEvent>() {
+ @Override
+ public void dispatchTypeSafe(final ActionVisibilityEvent interactionEvent) {
+ for (final InteractionListener l : getListeners()) {
+ l.actionVisible(interactionEvent);
+ }
+ }
+ });
+ dispatchersByEventClass.put(ActionUsabilityEvent.class, new InteractionEventDispatcherTypeSafe<ActionUsabilityEvent>() {
+ @Override
+ public void dispatchTypeSafe(final ActionUsabilityEvent interactionEvent) {
+ for (final InteractionListener l : getListeners()) {
+ l.actionUsable(interactionEvent);
+ }
+ }
+ });
+ dispatchersByEventClass.put(ActionArgumentEvent.class, new InteractionEventDispatcherTypeSafe<ActionArgumentEvent>() {
+ @Override
+ public void dispatchTypeSafe(final ActionArgumentEvent interactionEvent) {
+ for (final InteractionListener l : getListeners()) {
+ l.actionArgument(interactionEvent);
+ }
+ }
+ });
+ dispatchersByEventClass.put(ActionInvocationEvent.class, new InteractionEventDispatcherTypeSafe<ActionInvocationEvent>() {
+ @Override
+ public void dispatchTypeSafe(final ActionInvocationEvent interactionEvent) {
+ for (final InteractionListener l : getListeners()) {
+ l.actionInvoked(interactionEvent);
+ }
+ }
+ });
+ dispatchersByEventClass.put(ObjectValidityEvent.class, new InteractionEventDispatcherTypeSafe<ObjectValidityEvent>() {
+ @Override
+ public void dispatchTypeSafe(final ObjectValidityEvent interactionEvent) {
+ for (final InteractionListener l : getListeners()) {
+ l.objectPersisted(interactionEvent);
+ }
+ }
+ });
+ dispatchersByEventClass.put(CollectionMethodEvent.class, new InteractionEventDispatcherTypeSafe<CollectionMethodEvent>() {
+ @Override
+ public void dispatchTypeSafe(final CollectionMethodEvent interactionEvent) {
+ for (final InteractionListener l : getListeners()) {
+ l.collectionMethodInvoked(interactionEvent);
+ }
+ }
+ });
+ }
+
+ // /////////////////////////////////////////////////////////////
+ // wrap and unwrap
+ // /////////////////////////////////////////////////////////////
+
+ public <T> T wm(final Class<T> mixinClass, final Object mixedIn) {
+ return w(factoryService.m(mixinClass, mixedIn));
+ }
+
+ public <T> T wrapMixin(final Class<T> mixinClass, final Object mixedIn) {
+ return w(factoryService.m(mixinClass, mixedIn));
+ }
+
+ @Override
+ public <T> T w(final T domainObject) {
+ return wrap(domainObject, ExecutionMode.EXECUTE);
+ }
+
+ @Override
+ public <T> T wrap(final T domainObject) {
+ return wrap(domainObject, ExecutionMode.EXECUTE);
+ }
+
+ @Override
++ public <T> T wrapTry(final T domainObject) {
++ return wrap(domainObject, ExecutionMode.TRY);
++ }
++
++ @Override
+ public <T> T wrapNoExecute(final T domainObject) {
+ return wrap(domainObject, ExecutionMode.NO_EXECUTE);
+ }
+
+ @Override
+ public <T> T wrapSkipRules(final T domainObject) {
+ return wrap(domainObject, ExecutionMode.SKIP_RULES);
+ }
+
+ @Override
+ public <T> T wrap(final T domainObject, final ExecutionMode mode) {
+ if (domainObject instanceof WrappingObject) {
+ final WrappingObject wrapperObject = (WrappingObject) domainObject;
+ final ExecutionMode wrapperMode = wrapperObject.__isis_executionMode();
+ if(wrapperMode != mode) {
+ final Object underlyingDomainObject = wrapperObject.__isis_wrapped();
+ return _Casts.uncheckedCast(createProxy(underlyingDomainObject, mode, isisSessionFactory));
+ }
+ return domainObject;
+ }
+ return createProxy(domainObject, mode, isisSessionFactory);
+ }
+
+ protected <T> T createProxy(
+ final T domainObject,
+ final ExecutionMode mode,
+ final IsisSessionFactory isisSessionFactory) {
+ return proxyContextHandler.proxy(domainObject, mode, isisSessionFactory);
+ }
+
+ @Override
+ public boolean isWrapper(final Object possibleWrappedDomainObject) {
+ return possibleWrappedDomainObject instanceof WrappingObject;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ @Programmatic
+ public <T> T unwrap(T possibleWrappedDomainObject) {
+ if(isWrapper(possibleWrappedDomainObject)) {
+ final WrappingObject wrappingObject = (WrappingObject) possibleWrappedDomainObject;
+ return (T) wrappingObject.__isis_wrapped();
+ }
+ return possibleWrappedDomainObject;
+ }
+
+ // /////////////////////////////////////////////////////////////
+ // Listeners
+ // /////////////////////////////////////////////////////////////
+
+ @Override
+ public List<InteractionListener> getListeners() {
+ return listeners;
+ }
+
+ @Override
+ public boolean addInteractionListener(final InteractionListener listener) {
+ return listeners.add(listener);
+ }
+
+ @Override
+ public boolean removeInteractionListener(final InteractionListener listener) {
+ return listeners.remove(listener);
+ }
+
+ @Override
+ public void notifyListeners(final InteractionEvent interactionEvent) {
+ final InteractionEventDispatcher dispatcher = dispatchersByEventClass.get(interactionEvent.getClass());
+ if (dispatcher == null) {
+ throw new RuntimeException("Unknown InteractionEvent - register into dispatchers map");
+ }
+ dispatcher.dispatch(interactionEvent);
+ }
+
+
+ @Inject
+ AuthenticationSessionProvider authenticationSessionProvider;
+
+ @Inject
+ PersistenceSessionServiceInternal persistenceSessionServiceInternal;
+
+ @Inject
+ IsisSessionFactory isisSessionFactory;
+
+ @Inject
+ FactoryService factoryService;
+
+}
diff --cc core/runtime/src/main/java/org/apache/isis/core/wrapper/handlers/DomainObjectInvocationHandler.java
index 8666ded,0000000..5e6e808
mode 100644,000000..100644
--- a/core/runtime/src/main/java/org/apache/isis/core/wrapper/handlers/DomainObjectInvocationHandler.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/wrapper/handlers/DomainObjectInvocationHandler.java
@@@ -1,848 -1,0 +1,897 @@@
+/*
+ * 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.wrapper.handlers;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.apache.isis.commons.internal.collections._Lists;
+import org.apache.isis.commons.internal.collections._Sets;
+
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.applib.services.wrapper.DisabledException;
+import org.apache.isis.applib.services.wrapper.HiddenException;
+import org.apache.isis.applib.services.wrapper.InteractionException;
+import org.apache.isis.applib.services.wrapper.InvalidException;
+import org.apache.isis.applib.services.wrapper.WrapperFactory.ExecutionMode;
+import org.apache.isis.applib.services.wrapper.WrappingObject;
+import org.apache.isis.applib.services.wrapper.events.CollectionAccessEvent;
+import org.apache.isis.applib.services.wrapper.events.InteractionEvent;
+import org.apache.isis.applib.services.wrapper.events.ObjectTitleEvent;
+import org.apache.isis.applib.services.wrapper.events.PropertyAccessEvent;
+import org.apache.isis.applib.services.wrapper.events.UsabilityEvent;
+import org.apache.isis.applib.services.wrapper.events.ValidityEvent;
+import org.apache.isis.applib.services.wrapper.events.VisibilityEvent;
+import org.apache.isis.commons.internal.base._NullSafe;
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
+import org.apache.isis.core.commons.authentication.AuthenticationSessionProvider;
+import org.apache.isis.core.metamodel.IsisJdoMetamodelPlugin;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapterProvider;
+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.facets.ImperativeFacet;
+import org.apache.isis.core.metamodel.facets.ImperativeFacet.Intent;
+import org.apache.isis.core.metamodel.facets.object.mixin.MixinFacet;
+import org.apache.isis.core.metamodel.interactions.ObjectTitleContext;
+import org.apache.isis.core.metamodel.services.ServicesInjector;
+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.Contributed;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
+import org.apache.isis.core.metamodel.specloader.specimpl.ContributeeMember;
+import org.apache.isis.core.metamodel.specloader.specimpl.ObjectActionContributee;
+import org.apache.isis.core.metamodel.specloader.specimpl.ObjectActionMixedIn;
+import org.apache.isis.core.metamodel.specloader.specimpl.dflt.ObjectSpecificationDefault;
+import org.apache.isis.core.runtime.system.session.IsisSessionFactory;
+
+public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandlerDefault<T> {
+
+ private final AuthenticationSessionProvider authenticationSessionProvider;
+ private final PersistenceSessionServiceInternal persistenceSessionServiceInternal;
+
+ private final ProxyContextHandler proxy;
+ private final ExecutionMode executionMode;
+ private final IsisSessionFactory isisSessionFactory;
+
+ /**
+ * The <tt>title()</tt> method; may be <tt>null</tt>.
+ */
+ protected Method titleMethod;
+
+ /**
+ * The <tt>__isis_save()</tt> method from {@link WrappingObject#__isis_save()}.
+ */
+ protected Method __isis_saveMethod;
+
+ /**
+ * The <tt>__isis_wrapped()</tt> method from {@link WrappingObject#__isis_wrapped()}.
+ */
+ protected Method __isis_wrappedMethod;
+
+ /**
+ * The <tt>__isis_executionMode()</tt> method from {@link WrappingObject#__isis_executionMode()}.
+ */
+ protected Method __isis_executionMode;
+
+ protected final Set<String> jdoMethodsProvidedByEnhancement = _Sets.newHashSet();
+
+ public DomainObjectInvocationHandler(
+ final T delegate,
+ final ExecutionMode mode,
+ final ProxyContextHandler proxy,
+ final IsisSessionFactory isisSessionFactory) {
+ super(delegate, mode, isisSessionFactory);
+
+ this.proxy = proxy;
+ this.executionMode = mode;
+ this.isisSessionFactory = isisSessionFactory;
+
+ final ServicesInjector servicesInjector = isisSessionFactory.getServicesInjector();
+
+ this.authenticationSessionProvider = servicesInjector.getAuthenticationSessionProvider();
+ //this.specificationLoader = servicesInjector.getSpecificationLoader();
+ this.persistenceSessionServiceInternal = servicesInjector.getPersistenceSessionServiceInternal();
+
+
+ try {
+ titleMethod = delegate.getClass().getMethod("title", new Class[]{});
+ } catch (final NoSuchMethodException e) {
+ // ignore
+ }
+ try {
+ __isis_saveMethod = WrappingObject.class.getMethod("__isis_save", new Class[]{});
+ __isis_wrappedMethod = WrappingObject.class.getMethod("__isis_wrapped", new Class[]{});
+ __isis_executionMode = WrappingObject.class.getMethod("__isis_executionMode", new Class[]{});
+
+ _NullSafe.stream(IsisJdoMetamodelPlugin.get().getMethodsProvidedByEnhancement())
+ .map(Method::getName)
+ .forEach(jdoMethodsProvidedByEnhancement::add);
+
+ // legacy of ...
+ // dnPersistableMethods.addAll(
+ // _Lists.newArrayList(
+ // Iterables.transform(
+ // Arrays.asList(Persistable.class.getDeclaredMethods()),
+ // new Function<Method, String>() {
+ // @Override
+ // public String apply(final Method input) {
+ // return input.getName();
+ // }
+ // })));
+
+ } catch (final NoSuchMethodException nsme) {
+ throw new IllegalStateException(
+ "Could not locate reserved declared methods in the WrappingObject and WrappedObject interfaces",
+ nsme);
+ }
+ }
+
+ @Override
+ public Object invoke(final Object proxyObject, final Method method, final Object[] args) throws Throwable {
+
+ if (isObjectMethod(method)) {
+ return delegate(method, args);
+ }
+
+ if(isJdoMethod(method)) {
+ return delegate(method, args);
+ }
+
+ if(isInjectMethod(method)) {
+ return delegate(method, args);
+ }
+
+ final ObjectAdapter targetAdapter = adapterFor(getDelegate());
+
+ if (isTitleMethod(method)) {
+ return handleTitleMethod(targetAdapter);
+ }
+
+
+ final ObjectSpecification targetNoSpec = targetAdapter.getSpecification();
+
+ // save method, through the proxy
+ if (isSaveMethod(method)) {
+ return handleSaveMethod(targetAdapter, targetNoSpec);
+ }
+
+ if (isWrappedMethod(method)) {
+ return getDelegate();
+ }
+
+ if (isExecutionModeMethod(method)) {
+ return executionMode;
+ }
+
+ final ObjectMember objectMember = locateAndCheckMember(method);
+ final ContributeeMember contributeeMember = determineIfContributed(args, objectMember);
+
+ final String memberName = objectMember.getName();
+
+ final Intent intent = ImperativeFacet.Util.getIntent(objectMember, method);
+ if(intent == Intent.CHECK_IF_HIDDEN || intent == Intent.CHECK_IF_DISABLED) {
+ throw new UnsupportedOperationException(String.format("Cannot invoke supporting method '%s'", memberName));
+ }
+
+ if (intent == Intent.DEFAULTS || intent == Intent.CHOICES_OR_AUTOCOMPLETE) {
+ return method.invoke(getDelegate(), args);
+ }
+
+ if (objectMember.isOneToOneAssociation()) {
+
+ if (intent == Intent.CHECK_IF_VALID || intent == Intent.MODIFY_PROPERTY_SUPPORTING) {
+ throw new UnsupportedOperationException(String.format("Cannot invoke supporting method for '%s'; use only property accessor/mutator", memberName));
+ }
+
+ final OneToOneAssociation otoa = (OneToOneAssociation) objectMember;
+
+ if (intent == Intent.ACCESSOR) {
+ return handleGetterMethodOnProperty(targetAdapter, args, otoa);
+ }
+
+ if (intent == Intent.MODIFY_PROPERTY || intent == Intent.INITIALIZATION) {
+ return handleSetterMethodOnProperty(targetAdapter, args, otoa);
+ }
+ }
+ if (objectMember.isOneToManyAssociation()) {
+
+ if (intent == Intent.CHECK_IF_VALID) {
+ throw new UnsupportedOperationException(String.format("Cannot invoke supporting method '%s'; use only collection accessor/mutator", memberName));
+ }
+
+ final OneToManyAssociation otma = (OneToManyAssociation) objectMember;
+ if (intent == Intent.ACCESSOR) {
+ return handleGetterMethodOnCollection(targetAdapter, args, otma, method, memberName);
+ }
+ if (intent == Intent.MODIFY_COLLECTION_ADD) {
+ return handleCollectionAddToMethod(targetAdapter, args, otma);
+ }
+ if (intent == Intent.MODIFY_COLLECTION_REMOVE) {
+ return handleCollectionRemoveFromMethod(targetAdapter, args, otma);
+ }
+ }
+
+ if (objectMember instanceof ObjectAction) {
+
+ if (intent == Intent.CHECK_IF_VALID) {
+ throw new UnsupportedOperationException(String.format("Cannot invoke supporting method '%s'; use only the 'invoke' method", memberName));
+ }
+
+ final ObjectAction objectAction = (ObjectAction) objectMember;
+
+ ObjectAction actualObjectAction;
+ ObjectAdapter actualTargetAdapter;
+
+ final MixinFacet mixinFacet = targetAdapter.getSpecification().getFacet(MixinFacet.class);
+ if(mixinFacet != null) {
+
+ // rather than invoke on a (transient) mixin, instead try to
+ // figure out the corresponding ObjectActionMixedIn
+ actualTargetAdapter = mixinFacet.mixedIn(targetAdapter, MixinFacet.Policy.IGNORE_FAILURES);
+ actualObjectAction = determineMixinAction(actualTargetAdapter, objectAction);
+
+ if(actualTargetAdapter == null || actualObjectAction == null) {
+ // revert to original behaviour
+ actualTargetAdapter = targetAdapter;
+ actualObjectAction = objectAction;
+ }
+ } else {
+ actualTargetAdapter = targetAdapter;
+ actualObjectAction = objectAction;
+ }
+
+ return handleActionMethod(actualTargetAdapter, args, actualObjectAction, contributeeMember);
+ }
+
+ throw new UnsupportedOperationException(String.format("Unknown member type '%s'", objectMember));
+ }
+
+ private static ObjectAction determineMixinAction(
+ final ObjectAdapter domainObjectAdapter,
+ final ObjectAction objectAction) {
+ if(domainObjectAdapter == null) {
+ return null;
+ }
+ final ObjectSpecification specification = domainObjectAdapter.getSpecification();
+ final Stream<ObjectAction> objectActions = specification.streamObjectActions(Contributed.INCLUDED);
+
+ return objectActions
+ .filter(action->action instanceof ObjectActionMixedIn)
+ .map(action->(ObjectActionMixedIn) action)
+ .filter(mixedInAction->mixedInAction.hasMixinAction(objectAction))
+ .findFirst()
+ .orElse(null);
+
+ // throw new RuntimeException("Unable to find the mixed-in action corresponding to " + objectAction.getIdentifier().toFullIdentityString());
+ }
+
+ public InteractionInitiatedBy getInteractionInitiatedBy() {
+ return getExecutionMode().shouldEnforceRules()? InteractionInitiatedBy.USER: InteractionInitiatedBy.FRAMEWORK;
+ }
+
+ // see if this is a contributed property/collection/action
+ private ContributeeMember determineIfContributed(
+ final Object[] args,
+ final ObjectMember objectMember) {
+
+ if (!(objectMember instanceof ObjectAction)) {
+ return null;
+ }
+
+ final ObjectAction objectAction = (ObjectAction) objectMember;
+
+ for (final Object arg : args) {
+ if (arg == null) {
+ continue;
+ }
+ final ObjectSpecificationDefault objectSpec = getJavaSpecification(arg.getClass());
+
+ if (args.length == 1) {
+ // is this a contributed property/collection?
+ final Stream<ObjectAssociation> associations =
+ objectSpec.streamAssociations(Contributed.INCLUDED);
+
+
+ final Optional<ContributeeMember> contributeeMember = associations
+ .filter(association->association instanceof ContributeeMember)
+ .map(association->(ContributeeMember) association)
+ .filter(contributeeMember1->contributeeMember1.isContributedBy(objectAction))
+ .findAny();
+
+ if(contributeeMember.isPresent()) {
+ return contributeeMember.get();
+ }
+ }
+
+ // is this a contributed action?
+ {
+ final Stream<ObjectAction> actions =
+ objectSpec.streamObjectActions(Contributed.INCLUDED);
+
+ final Optional<ContributeeMember> contributeeMember = actions
+ .filter(action->action instanceof ContributeeMember)
+ .map(action->(ContributeeMember) action)
+ .filter(contributeeMember1->contributeeMember1.isContributedBy(objectAction))
+ .findAny();
+
+ if(contributeeMember.isPresent()) {
+ return contributeeMember.get();
+ }
+ }
+
+ }
+
+ return null;
+ }
+
+ private boolean isJdoMethod(final Method method) {
+ return methodStartsWith(method, "jdo") || jdoMethodsProvidedByEnhancement.contains(method.getName());
+ }
+
+ private static boolean isInjectMethod(final Method method) {
+ return methodStartsWith(method, "inject");
+ }
+
+ private static boolean methodStartsWith(final Method method, final String prefix) {
+ return method.getName().startsWith(prefix);
+ }
+
+ // /////////////////////////////////////////////////////////////////
+ // title
+ // /////////////////////////////////////////////////////////////////
+
+ private Object handleTitleMethod(final ObjectAdapter targetAdapter)
+ throws IllegalAccessException, InvocationTargetException {
+
+ resolveIfRequired(targetAdapter);
+
+ final ObjectSpecification targetNoSpec = targetAdapter.getSpecification();
+ final ObjectTitleContext titleContext = targetNoSpec.createTitleInteractionContext(getAuthenticationSession(), InteractionInitiatedBy.FRAMEWORK, targetAdapter);
+ final ObjectTitleEvent titleEvent = titleContext.createInteractionEvent();
+ notifyListeners(titleEvent);
+ return titleEvent.getTitle();
+ }
+
+ // /////////////////////////////////////////////////////////////////
+ // save
+ // /////////////////////////////////////////////////////////////////
+
+ private Object handleSaveMethod(
+ final ObjectAdapter targetAdapter, final ObjectSpecification targetNoSpec) {
+
+ if(getExecutionMode().shouldEnforceRules()) {
+ final InteractionResult interactionResult =
+ targetNoSpec.isValidResult(targetAdapter, getInteractionInitiatedBy());
+ notifyListenersAndVetoIfRequired(interactionResult);
+ }
+
+ if (getExecutionMode().shouldExecute()) {
+ if (targetAdapter.isTransient()) {
- getPersistenceSessionService().makePersistent(targetAdapter);
++ if(getExecutionMode().shouldFailFast()) {
++ getPersistenceSessionService().makePersistent(targetAdapter);
++ } else {
++ try {
++ getPersistenceSessionService().makePersistent(targetAdapter);
++ } catch(Exception ignore) {
++ // ignore
++ }
++ }
+ }
+ }
+ return null;
+ }
+
+ // /////////////////////////////////////////////////////////////////
+ // property - access
+ // /////////////////////////////////////////////////////////////////
+
+ private Object handleGetterMethodOnProperty(
+ final ObjectAdapter targetAdapter,
+ final Object[] args,
+ final OneToOneAssociation property) {
+
+ if (args.length != 0) {
+ throw new IllegalArgumentException("Invoking a 'get' should have no arguments");
+ }
+
+ if(getExecutionMode().shouldEnforceRules()) {
+ checkVisibility(targetAdapter, property);
+ }
+
+ resolveIfRequired(targetAdapter);
+
+ final InteractionInitiatedBy interactionInitiatedBy = getInteractionInitiatedBy();
+ final ObjectAdapter currentReferencedAdapter = property.get(targetAdapter, interactionInitiatedBy);
+
+ final Object currentReferencedObj = ObjectAdapter.Util.unwrapPojo(currentReferencedAdapter);
+
+ final PropertyAccessEvent ev = new PropertyAccessEvent(getDelegate(), property.getIdentifier(), currentReferencedObj);
+ notifyListeners(ev);
+ return currentReferencedObj;
+ }
+
+
+ // /////////////////////////////////////////////////////////////////
+ // property - modify
+ // /////////////////////////////////////////////////////////////////
+
+ private Object handleSetterMethodOnProperty(
+ final ObjectAdapter targetAdapter, final Object[] args,
+ final OneToOneAssociation property) {
+ if (args.length != 1) {
+ throw new IllegalArgumentException("Invoking a setter should only have a single argument");
+ }
+
+ final Object argumentObj = underlying(args[0]);
+
+ if(getExecutionMode().shouldEnforceRules()) {
+ checkVisibility(targetAdapter, property);
+ checkUsability(targetAdapter, property);
+ }
+
+ final ObjectAdapter argumentAdapter = argumentObj != null ? adapterFor(argumentObj) : null;
+
+ resolveIfRequired(targetAdapter);
+
+
+ if(getExecutionMode().shouldEnforceRules()) {
+ final InteractionResult interactionResult = property.isAssociationValid(targetAdapter, argumentAdapter, getInteractionInitiatedBy()).getInteractionResult();
+ notifyListenersAndVetoIfRequired(interactionResult);
+ }
+
+ if (getExecutionMode().shouldExecute()) {
- property.set(targetAdapter, argumentAdapter, getInteractionInitiatedBy());
++ if(getExecutionMode().shouldFailFast()) {
++ property.set(targetAdapter, argumentAdapter, getInteractionInitiatedBy());
++ } else {
++ try {
++ property.set(targetAdapter, argumentAdapter, getInteractionInitiatedBy());
++ } catch(Exception ignore) {
++ // ignore
++ }
++ }
++
+ }
+
+ return null;
+ }
+
+
+ // /////////////////////////////////////////////////////////////////
+ // collection - access
+ // /////////////////////////////////////////////////////////////////
+
+ private Object handleGetterMethodOnCollection(
+ final ObjectAdapter targetAdapter,
+ final Object[] args,
+ final OneToManyAssociation collection,
+ final Method method,
+ final String memberName) {
+
+ if (args.length != 0) {
+ throw new IllegalArgumentException("Invoking a 'get' should have no arguments");
+ }
+
+ if(getExecutionMode().shouldEnforceRules()) {
+ checkVisibility(targetAdapter, collection);
+ }
+
+ resolveIfRequired(targetAdapter);
+
+ final InteractionInitiatedBy interactionInitiatedBy = getInteractionInitiatedBy();
+ final ObjectAdapter currentReferencedAdapter = collection.get(targetAdapter, interactionInitiatedBy);
+
+ final Object currentReferencedObj = ObjectAdapter.Util.unwrapPojo(currentReferencedAdapter);
+
+ final CollectionAccessEvent ev = new CollectionAccessEvent(getDelegate(), collection.getIdentifier());
+
+ if (currentReferencedObj instanceof Collection) {
+ final Collection<?> collectionViewObject = lookupWrappingObject(method, memberName,
+ (Collection<?>) currentReferencedObj, collection);
+ notifyListeners(ev);
+ return collectionViewObject;
+ } else if (currentReferencedObj instanceof Map) {
+ final Map<?, ?> mapViewObject = lookupWrappingObject(memberName, (Map<?, ?>) currentReferencedObj,
+ collection);
+ notifyListeners(ev);
+ return mapViewObject;
+ }
+ throw new IllegalArgumentException(String.format("Collection type '%s' not supported by framework", currentReferencedObj.getClass().getName()));
+ }
+
+ private Collection<?> lookupWrappingObject(
+ final Method method,
+ final String memberName,
+ final Collection<?> collectionToLookup,
+ final OneToManyAssociation otma) {
+ return collectionToLookup instanceof WrappingObject
+ ? collectionToLookup
+ : proxy.proxy(collectionToLookup, memberName, this, otma);
+ }
+
+ private Map<?, ?> lookupWrappingObject(
+ final String memberName,
+ final Map<?, ?> mapToLookup,
+ final OneToManyAssociation otma) {
+ return mapToLookup instanceof WrappingObject
+ ? mapToLookup
+ : proxy.proxy(mapToLookup, memberName, this, otma);
+ }
+
+ // /////////////////////////////////////////////////////////////////
+ // collection - add to
+ // /////////////////////////////////////////////////////////////////
+
+ private Object handleCollectionAddToMethod(
+ final ObjectAdapter targetAdapter,
+ final Object[] args,
+ final OneToManyAssociation otma) {
+
+ if (args.length != 1) {
+ throw new IllegalArgumentException("Invoking a addTo should only have a single argument");
+ }
+
+ if(getExecutionMode().shouldEnforceRules()) {
+ checkVisibility(targetAdapter, otma);
+ checkUsability(targetAdapter, otma);
+ }
+
+ resolveIfRequired(targetAdapter);
+
+ final Object argumentObj = underlying(args[0]);
+ if (argumentObj == null) {
+ throw new IllegalArgumentException("Must provide a non-null object to add");
+ }
+ final ObjectAdapter argumentNO = adapterFor(argumentObj);
+
+ if(getExecutionMode().shouldEnforceRules()) {
+ final InteractionResult interactionResult = otma.isValidToAdd(targetAdapter, argumentNO,
+ getInteractionInitiatedBy()).getInteractionResult();
+ notifyListenersAndVetoIfRequired(interactionResult);
+ }
+
+ if (getExecutionMode().shouldExecute()) {
- otma.addElement(targetAdapter, argumentNO, getInteractionInitiatedBy());
++ if(getExecutionMode().shouldFailFast()) {
++ otma.addElement(targetAdapter, argumentNO, getInteractionInitiatedBy());
++ } else {
++ try {
++ otma.addElement(targetAdapter, argumentNO, getInteractionInitiatedBy());
++ } catch(Exception ignore) {
++ // ignore
++ }
++ }
+ }
+
+ return null;
+ }
+
+
+ // /////////////////////////////////////////////////////////////////
+ // collection - remove from
+ // /////////////////////////////////////////////////////////////////
+
+ private Object handleCollectionRemoveFromMethod(
+ final ObjectAdapter targetAdapter,
+ final Object[] args,
+ final OneToManyAssociation collection) {
+ if (args.length != 1) {
+ throw new IllegalArgumentException("Invoking a removeFrom should only have a single argument");
+ }
+
+ if(getExecutionMode().shouldEnforceRules()) {
+ checkVisibility(targetAdapter, collection);
+ checkUsability(targetAdapter, collection);
+ }
+
+
+ resolveIfRequired(targetAdapter);
+
+ final Object argumentObj = underlying(args[0]);
+ if (argumentObj == null) {
+ throw new IllegalArgumentException("Must provide a non-null object to remove");
+ }
+ final ObjectAdapter argumentAdapter = adapterFor(argumentObj);
+
+ if(getExecutionMode().shouldEnforceRules()) {
+ final InteractionResult interactionResult = collection.isValidToRemove(targetAdapter, argumentAdapter,
+ getInteractionInitiatedBy()).getInteractionResult();
+ notifyListenersAndVetoIfRequired(interactionResult);
+ }
+
+ if (getExecutionMode().shouldExecute()) {
- collection.removeElement(targetAdapter, argumentAdapter, getInteractionInitiatedBy());
++ if(getExecutionMode().shouldFailFast()) {
++ collection.removeElement(targetAdapter, argumentAdapter, getInteractionInitiatedBy());
++ } else {
++ try {
++ collection.removeElement(targetAdapter, argumentAdapter, getInteractionInitiatedBy());
++ } catch(Exception ignore) {
++ // ignore
++ }
++ }
+ }
+
+ return null;
+ }
+
+ // /////////////////////////////////////////////////////////////////
+ // action
+ // /////////////////////////////////////////////////////////////////
+
+ private Object handleActionMethod(
+ final ObjectAdapter targetAdapter, final Object[] args,
+ final ObjectAction objectAction,
+ final ContributeeMember contributeeMember) {
+
+ final ObjectAdapter contributeeAdapter;
+ final Object[] contributeeArgs;
+ if(contributeeMember != null) {
+ final int contributeeParamPosition = contributeeMember.getContributeeParamPosition();
+ final Object contributee = args[contributeeParamPosition];
+ contributeeAdapter = adapterFor(contributee);
+
+ final List<Object> argCopy = _Lists.of(args);
+ argCopy.remove(contributeeParamPosition);
+ contributeeArgs = argCopy.toArray();
+ } else {
+ contributeeAdapter = null;
+ contributeeArgs = null;
+ }
+
+ if(getExecutionMode().shouldEnforceRules()) {
+ if(contributeeMember != null) {
+ checkVisibility(contributeeAdapter, contributeeMember);
+ checkUsability(contributeeAdapter, contributeeMember);
+ } else {
+ checkVisibility(targetAdapter, objectAction);
+ checkUsability(targetAdapter, objectAction);
+ }
+ }
+
+ final ObjectAdapter[] argAdapters = asObjectAdaptersUnderlying(args);
+
+ if(getExecutionMode().shouldEnforceRules()) {
+ if(contributeeMember != null) {
+ if(contributeeMember instanceof ObjectActionContributee) {
+ final ObjectActionContributee objectActionContributee = (ObjectActionContributee) contributeeMember;
+ final ObjectAdapter[] contributeeArgAdapters = asObjectAdaptersUnderlying(contributeeArgs);
+
+ checkValidity(contributeeAdapter, objectActionContributee, contributeeArgAdapters);
+ }
+ // nothing to do for contributed properties or collections
+ } else {
+ checkValidity(targetAdapter, objectAction, argAdapters);
+ }
+ }
+
+ if (getExecutionMode().shouldExecute()) {
+ final InteractionInitiatedBy interactionInitiatedBy = getInteractionInitiatedBy();
+
+ final ObjectAdapter mixedInAdapter = null; // if a mixin action, then it will automatically fill in.
+
- final ObjectAdapter returnedAdapter = objectAction.execute(
- targetAdapter, mixedInAdapter, argAdapters,
- interactionInitiatedBy);
++
++ ObjectAdapter returnedAdapter;
++
++ if(getExecutionMode().shouldFailFast()) {
++ returnedAdapter = objectAction.execute(
++ targetAdapter, mixedInAdapter, argAdapters,
++ interactionInitiatedBy);
++ } else {
++ try {
++ returnedAdapter = objectAction.execute(
++ targetAdapter, mixedInAdapter, argAdapters,
++ interactionInitiatedBy);
++ } catch(Exception ignore) {
++ // ignore
++ returnedAdapter = null;
++ }
++
++ }
++
+
+ return ObjectAdapter.Util.unwrapPojo(returnedAdapter);
+ }
+
+ return null;
+ }
+
+ private void checkValidity(final ObjectAdapter targetAdapter, final ObjectAction objectAction, final ObjectAdapter[] argAdapters) {
+ final InteractionResult interactionResult = objectAction.isProposedArgumentSetValid(targetAdapter, argAdapters,
+ getInteractionInitiatedBy()).getInteractionResult();
+ notifyListenersAndVetoIfRequired(interactionResult);
+ }
+
+ private ObjectAdapter[] asObjectAdaptersUnderlying(final Object[] args) {
+
+ final ObjectAdapter[] argAdapters = new ObjectAdapter[args.length];
+ int i = 0;
+ for (final Object arg : args) {
+ argAdapters[i++] = adapterFor(underlying(arg));
+ }
+
+ return argAdapters;
+ }
+
+ private ObjectAdapter adapterFor(final Object obj) {
+ return obj != null ? getObjectAdapterProvider().adapterFor(obj) : null;
+ }
+
+ private Object underlying(final Object arg) {
+ if (arg instanceof WrappingObject) {
+ final WrappingObject argViewObject = (WrappingObject) arg;
+ return argViewObject.__isis_wrapped();
+ } else {
+ return arg;
+ }
+ }
+
+ // /////////////////////////////////////////////////////////////////
+ // visibility and usability checks (common to all members)
+ // /////////////////////////////////////////////////////////////////
+
+ /**
+ * REVIEW: ideally should provide some way to allow to caller to indicate the 'where' context. Having
+ * a hard-coded value like this is an approximation.
+ */
+ private final Where where = Where.ANYWHERE;
+
+ private void checkVisibility(
+ final ObjectAdapter targetObjectAdapter,
+ final ObjectMember objectMember) {
+ final Consent visibleConsent = objectMember.isVisible(targetObjectAdapter, getInteractionInitiatedBy(), where);
+ final InteractionResult interactionResult = visibleConsent.getInteractionResult();
+ notifyListenersAndVetoIfRequired(interactionResult);
+ }
+
+ private void checkUsability(
+ final ObjectAdapter targetObjectAdapter,
+ final ObjectMember objectMember) {
+ final InteractionResult interactionResult = objectMember.isUsable(targetObjectAdapter,
+ getInteractionInitiatedBy(), where
+ ).getInteractionResult();
+ notifyListenersAndVetoIfRequired(interactionResult);
+ }
+
+ // /////////////////////////////////////////////////////////////////
+ // notify listeners
+ // /////////////////////////////////////////////////////////////////
+
+ private void notifyListenersAndVetoIfRequired(final InteractionResult interactionResult) {
+ final InteractionEvent interactionEvent = interactionResult.getInteractionEvent();
+ notifyListeners(interactionEvent);
+ if (interactionEvent.isVeto()) {
+ throw toException(interactionEvent);
+ }
+ }
+
+ /**
+ * Wraps a {@link InteractionEvent#isVeto() vetoing}
+ * {@link InteractionEvent} in a corresponding {@link InteractionException},
+ * and returns it.
+ */
+ private InteractionException toException(final InteractionEvent interactionEvent) {
+ if (!interactionEvent.isVeto()) {
+ throw new IllegalArgumentException("Provided interactionEvent must be a veto");
+ }
+ if (interactionEvent instanceof ValidityEvent) {
+ final ValidityEvent validityEvent = (ValidityEvent) interactionEvent;
+ return new InvalidException(validityEvent);
+ }
+ if (interactionEvent instanceof VisibilityEvent) {
+ final VisibilityEvent visibilityEvent = (VisibilityEvent) interactionEvent;
+ return new HiddenException(visibilityEvent);
+ }
+ if (interactionEvent instanceof UsabilityEvent) {
+ final UsabilityEvent usabilityEvent = (UsabilityEvent) interactionEvent;
+ return new DisabledException(usabilityEvent);
+ }
+ throw new IllegalArgumentException("Provided interactionEvent must be a VisibilityEvent, UsabilityEvent or a ValidityEvent");
+ }
+
+ // /////////////////////////////////////////////////////////////////
+ // switching
+ // /////////////////////////////////////////////////////////////////
+
+ private ObjectMember locateAndCheckMember(final Method method) {
+ final ObjectSpecificationDefault objectSpecificationDefault = getJavaSpecificationOfOwningClass(method);
+ final ObjectMember member = objectSpecificationDefault.getMember(method);
+
+ if (member == null) {
+ final String methodName = method.getName();
+ throw new UnsupportedOperationException("Method '" + methodName + "' being invoked does not correspond to any of the object's fields or actions.");
+ }
+ return member;
+ }
+
+ protected boolean isTitleMethod(final Method method) {
+ return method.equals(titleMethod);
+ }
+
+ protected boolean isSaveMethod(final Method method) {
+ return method.equals(__isis_saveMethod);
+ }
+
+ protected boolean isWrappedMethod(final Method method) {
+ return method.equals(__isis_wrappedMethod);
+ }
+
+ protected boolean isExecutionModeMethod(final Method method) {
+ return method.equals(__isis_executionMode);
+ }
+
+ // /////////////////////////////////////////////////////////////////
+ // Specification lookup
+ // /////////////////////////////////////////////////////////////////
+
+ private ObjectSpecificationDefault getJavaSpecificationOfOwningClass(final Method method) {
+ return getJavaSpecification(method.getDeclaringClass());
+ }
+
+ private ObjectSpecificationDefault getJavaSpecification(final Class<?> clazz) {
+ final ObjectSpecification objectSpec = getSpecification(clazz);
+ 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 getSpecificationLoader().loadSpecification(type);
+ }
+
+ // /////////////////////////////////////////////////////////////////
+ // Dependencies
+ // /////////////////////////////////////////////////////////////////
+
+ protected SpecificationLoader getSpecificationLoader() {
+ return isisSessionFactory.getSpecificationLoader();
+ }
+
+ public AuthenticationSessionProvider getAuthenticationSessionProvider() {
+ return authenticationSessionProvider;
+ }
+
+ protected AuthenticationSession getAuthenticationSession() {
+ return getAuthenticationSessionProvider().getAuthenticationSession();
+ }
+
+ protected ObjectAdapterProvider getObjectAdapterProvider() {
+ return persistenceSessionServiceInternal;
+ }
+
+ protected PersistenceSessionServiceInternal getPersistenceSessionService() {
+ return persistenceSessionServiceInternal;
+ }
+
+ public IsisSessionFactory getIsisSessionFactory() {
+ return isisSessionFactory;
+ }
+}
diff --cc core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/RepresentationType.java
index 059630f,70c2794..4075ffe
--- a/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/RepresentationType.java
+++ b/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/RepresentationType.java
@@@ -18,9 -18,7 +18,8 @@@
*/
package org.apache.isis.viewer.restfulobjects.applib;
-
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@@ -41,9 -41,9 +40,10 @@@ import org.apache.isis.viewer.restfulob
import org.apache.isis.viewer.restfulobjects.applib.domaintypes.TypeActionResultRepresentation;
import org.apache.isis.viewer.restfulobjects.applib.domaintypes.TypeListRepresentation;
import org.apache.isis.viewer.restfulobjects.applib.errors.ErrorRepresentation;
+ import org.apache.isis.viewer.restfulobjects.applib.health.HealthRepresentation;
import org.apache.isis.viewer.restfulobjects.applib.homepage.HomePageRepresentation;
import org.apache.isis.viewer.restfulobjects.applib.user.UserRepresentation;
+import org.apache.isis.viewer.restfulobjects.applib.util.MediaTypes;
import org.apache.isis.viewer.restfulobjects.applib.util.Parser;
import org.apache.isis.viewer.restfulobjects.applib.version.VersionRepresentation;
diff --cc core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/health/HealthResource.java
index 0000000,c00aeca..f624932
mode 000000,100644..100644
--- a/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/health/HealthResource.java
+++ b/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/health/HealthResource.java
@@@ -1,0 -1,51 +1,44 @@@
+ /*
+ * 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.viewer.restfulobjects.applib.health;
+
-import javax.ws.rs.DELETE;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.PUT;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
++import javax.ws.rs.*;
+ import javax.ws.rs.core.MediaType;
+ import javax.ws.rs.core.Response;
+
-import org.jboss.resteasy.annotations.ClientResponseType;
-
+ import org.apache.isis.viewer.restfulobjects.applib.RestfulMediaType;
+
+ @Path("/health")
+ public interface HealthResource {
+
+ @GET
+ @Produces({ MediaType.APPLICATION_JSON, RestfulMediaType.APPLICATION_JSON_HEALTH })
- @ClientResponseType(entityType = String.class)
++ //TODO @ClientResponseType(entityType = String.class)
+ public Response health();
+
+ @DELETE
+ public Response deleteHealthNotAllowed();
+
+ @PUT
+ public Response putHealthNotAllowed();
+
+ @POST
+ public Response postHealthNotAllowed();
+
+ }
diff --cc example/application/simpleapp/application/src/test/java/domainapp/application/integtests/mml/approved/domainapp.application.services.health.HealthCheckServiceImpl.xml
index 0000000,a2ed203..b2eaa9d
mode 000000,100644..100644
--- a/example/application/simpleapp/application/src/test/java/domainapp/application/integtests/mml/approved/domainapp.application.services.health.HealthCheckServiceImpl.xml
+++ b/example/application/simpleapp/application/src/test/java/domainapp/application/integtests/mml/approved/domainapp.application.services.health.HealthCheckServiceImpl.xml
@@@ -1,0 -1,50 +1,45 @@@
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+ <mml:domainClassDto id="domainapp.application.services.health.HealthCheckServiceImpl" service="true" xmlns:mml="http://isis.apache.org/schema/metamodel">
+ <mml:facets>
+ <mml:facet id="org.apache.isis.core.metamodel.facets.all.named.NamedFacet" fqcn="org.apache.isis.core.metamodel.facets.all.named.NamedFacetInferred">
+ <mml:attr name="escaped">true</mml:attr>
+ <mml:attr name="value">Health Check Service Impl</mml:attr>
+ </mml:facet>
++ <mml:facet id="org.apache.isis.core.metamodel.facets.object.audit.AuditableFacet" fqcn="org.apache.isis.core.metamodel.facets.object.domainobject.auditing.AuditableFacetFromConfiguration">
++ <mml:attr name="enablement">ENABLED</mml:attr>
++ </mml:facet>
+ <mml:facet id="org.apache.isis.core.metamodel.facets.object.bookmarkpolicy.BookmarkPolicyFacet" fqcn="org.apache.isis.core.metamodel.facets.object.bookmarkpolicy.BookmarkPolicyFacetFallback">
+ <mml:attr name="bookmarkPolicy">NEVER</mml:attr>
+ </mml:facet>
+ <mml:facet id="org.apache.isis.core.metamodel.facets.object.domainservice.DomainServiceFacet" fqcn="org.apache.isis.core.metamodel.facets.object.domainservice.annotation.DomainServiceFacetAnnotation">
+ <mml:attr name="natureOfService">DOMAIN</mml:attr>
+ <mml:attr name="repositoryFor">java.lang.Object</mml:attr>
+ </mml:facet>
+ <mml:facet id="org.apache.isis.core.metamodel.facets.object.domainservicelayout.DomainServiceLayoutFacet" fqcn="org.apache.isis.core.metamodel.facets.object.domainservicelayout.annotation.DomainServiceLayoutFacetAnnotation">
+ <mml:attr name="menuBar">PRIMARY</mml:attr>
- <mml:attr name="menuOrder">2147483547</mml:attr>
+ </mml:facet>
+ <mml:facet id="org.apache.isis.core.metamodel.facets.object.grid.GridFacet" fqcn="org.apache.isis.core.metamodel.facets.object.grid.GridFacetDefault"/>
+ <mml:facet id="org.apache.isis.core.metamodel.facets.object.icon.IconFacet" fqcn="org.apache.isis.core.metamodel.facets.object.domainservice.annotation.IconFacetDerivedFromDomainServiceAnnotation">
+ <mml:attr name="repositoryFor">java.lang.Object</mml:attr>
+ </mml:facet>
- <mml:facet id="org.apache.isis.core.metamodel.facets.object.membergroups.MemberGroupLayoutFacet" fqcn="org.apache.isis.core.metamodel.facets.object.membergroups.annotprop.MemberGroupLayoutFacetFallback">
- <mml:attr name="columns">[4,0,0,8]</mml:attr>
- <mml:attr name="left">General</mml:attr>
- </mml:facet>
- <mml:facet id="org.apache.isis.core.metamodel.facets.object.notpersistable.NotPersistableFacet" fqcn="org.apache.isis.core.metamodel.facets.fallback.NotPersistableFacetNull">
- <mml:attr name="disabling">true</mml:attr>
- </mml:facet>
+ <mml:facet id="org.apache.isis.core.metamodel.facets.object.objectspecid.ObjectSpecIdFacet" fqcn="org.apache.isis.core.metamodel.facets.object.objectspecid.classname.ObjectSpecIdFacetDerivedFromClassName">
+ <mml:attr name="derived">true</mml:attr>
+ <mml:attr name="value">domainapp.application.services.health.HealthCheckServiceImpl</mml:attr>
+ </mml:facet>
+ <mml:facet id="org.apache.isis.core.metamodel.facets.object.objectvalidprops.ObjectValidPropertiesFacet" fqcn="org.apache.isis.core.metamodel.facets.object.objectvalidprops.impl.ObjectValidPropertiesFacetImpl">
+ <mml:attr name="validating">true</mml:attr>
+ </mml:facet>
+ <mml:facet id="org.apache.isis.core.metamodel.facets.object.paged.PagedFacet" fqcn="org.apache.isis.core.metamodel.facets.fallback.PagedFacetFromConfiguration">
+ <mml:attr name="derived">true</mml:attr>
+ <mml:attr name="value">25</mml:attr>
+ </mml:facet>
+ <mml:facet id="org.apache.isis.core.metamodel.facets.object.plural.PluralFacet" fqcn="org.apache.isis.core.metamodel.facets.object.plural.inferred.PluralFacetInferred">
+ <mml:attr name="derived">true</mml:attr>
+ <mml:attr name="value">Health Check Service Impls</mml:attr>
+ </mml:facet>
+ <mml:facet id="org.apache.isis.core.runtime.authorization.standard.AuthorizationFacet" fqcn="org.apache.isis.core.runtime.authorization.standard.AuthorizationFacetImpl">
+ <mml:attr name="disabling">true</mml:attr>
+ <mml:attr name="hiding">true</mml:attr>
+ </mml:facet>
+ </mml:facets>
+ </mml:domainClassDto>
diff --cc example/application/simpleapp/module-simple/src/main/java/domainapp/modules/simple/dom/impl/SimpleObjects.java
index d62b3e6,5aa77cf..30771d1
--- a/example/application/simpleapp/module-simple/src/main/java/domainapp/modules/simple/dom/impl/SimpleObjects.java
+++ b/example/application/simpleapp/module-simple/src/main/java/domainapp/modules/simple/dom/impl/SimpleObjects.java
@@@ -82,6 -82,14 +82,14 @@@ public class SimpleObjects
.executeUnique();
}
+ @Programmatic
+ public void ping() {
- TypesafeQuery<SimpleObject> q = isisJdoSupport.newTypesafeQuery(SimpleObject.class);
++ JDOQLTypedQuery<SimpleObject> q = isisJdoSupport.newTypesafeQuery(SimpleObject.class);
+ final QSimpleObject candidate = QSimpleObject.candidate();
+ q.range(0,2);
+ q.orderBy(candidate.name.asc());
+ q.executeList();
+ }
public static class CreateDomainEvent extends ActionDomainEvent<SimpleObjects> {}
@Action(domainEvent = CreateDomainEvent.class)