You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2019/01/29 12:17:53 UTC

[isis] branch 2033-IoC updated: ISIS-2033: adds and enables spring demo module

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch 2033-IoC
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/2033-IoC by this push:
     new 5270b93  ISIS-2033: adds and enables spring demo module
5270b93 is described below

commit 5270b9394f995a74b6de18ff0548aa08d3f9fbb8
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Jan 29 13:17:46 2019 +0100

    ISIS-2033: adds and enables spring demo module
    
    Task-Url: https://issues.apache.org/jira/browse/ISIS-2033
---
 .../java/org/apache/isis/applib/AppManifest.java   | 23 ++++++--
 .../classdiscovery/ClassDiscoveryPlugin.java       |  4 +-
 .../org/apache/isis/config/AppConfigLocator.java   | 12 ++--
 .../config/builder/IsisConfigurationDefault.java   | 16 +++---
 .../isis/config/builder/ModulePackageHelper.java   | 65 +++++++++++++++-------
 .../ClassDiscoveryPluginUsingReflections.java      |  9 ++-
 .../isis/core/plugins/ioc/weld/WeldFactory.java    |  6 --
 .../application/manifest/DomainAppAppManifest.java |  7 +++
 .../modules/spring/SpringContextListener.java      | 26 +++++++++
 .../modules/spring/SpringModuleCDIBridge.java      | 17 ++++++
 .../modules/spring/dom/customer/Customer.java      | 32 +++++++++++
 .../modules/spring/dom/customer/CustomerMenu.java  | 30 ++++++++++
 .../spring/dom/customer/CustomerRepository.java    |  9 +++
 13 files changed, 206 insertions(+), 50 deletions(-)

diff --git a/core/applib/src/main/java/org/apache/isis/applib/AppManifest.java b/core/applib/src/main/java/org/apache/isis/applib/AppManifest.java
index 52a8bc4..bb490c3 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/AppManifest.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/AppManifest.java
@@ -19,9 +19,7 @@
 
 package org.apache.isis.applib;
 
-import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -160,7 +158,7 @@ public interface AppManifest {
      */
     public final static class Registry {
 
-        public final static List<String> FRAMEWORK_PROVIDED_SERVICE_PACKAGES = Collections.unmodifiableList(Arrays.asList(
+        public final static List<String> FRAMEWORK_PROVIDED_SERVICE_PACKAGES = _Lists.of(
                 "org.apache.isis.applib",
                 "org.apache.isis.core.wrapper" ,
                 "org.apache.isis.core.metamodel.services" ,
@@ -171,7 +169,24 @@ public interface AppManifest {
                 "org.apache.isis.objectstore.jdo.datanucleus.service.support" ,
                 "org.apache.isis.objectstore.jdo.datanucleus.service.eventbus" ,
                 "org.apache.isis.viewer.wicket.viewer.services", 
-                "org.apache.isis.core.integtestsupport.components"));
+                "org.apache.isis.core.integtestsupport.components");
+
+        public final static List<String> FRAMEWORK_PROVIDED_TYPES_FOR_SCANNING = _Lists.of(
+        		"org.apache.isis.config.AppConfig",
+                "org.apache.isis.applib.IsisApplibModule",
+                "org.apache.isis.core.wrapper.WrapperFactoryDefault",
+                "org.apache.isis.core.metamodel.JdoMetamodelUtil",
+                "org.apache.isis.core.runtime.RuntimeModule",
+                "org.apache.isis.viewer.restfulobjects.rendering.RendererContext"
+                
+//                "org.apache.isis.objectstore.jdo.applib.service" ,
+//                "org.apache.isis.objectstore.jdo.datanucleus.service.support" ,
+//                "org.apache.isis.objectstore.jdo.datanucleus.service.eventbus" ,
+//                "org.apache.isis.viewer.wicket.viewer.services", 
+//                "org.apache.isis.core.integtestsupport.components"
+                
+        		
+        		);
 
         private static Registry instance = new Registry();
         public static Registry instance() {
diff --git a/core/commons/src/main/java/org/apache/isis/core/plugins/classdiscovery/ClassDiscoveryPlugin.java b/core/commons/src/main/java/org/apache/isis/core/plugins/classdiscovery/ClassDiscoveryPlugin.java
index 0ca1f6d..edf39c2 100644
--- a/core/commons/src/main/java/org/apache/isis/core/plugins/classdiscovery/ClassDiscoveryPlugin.java
+++ b/core/commons/src/main/java/org/apache/isis/core/plugins/classdiscovery/ClassDiscoveryPlugin.java
@@ -19,7 +19,7 @@
 
 package org.apache.isis.core.plugins.classdiscovery;
 
-import java.util.List;
+import java.util.Collection;
 
 import org.apache.isis.commons.internal.context._Plugin;
 
@@ -29,7 +29,7 @@ public interface ClassDiscoveryPlugin {
     public ClassDiscovery discover(String packageNamePrefix);
 
     //TODO missing java-doc
-    public ClassDiscovery discover(List<String> packageNamePrefixes);
+    public ClassDiscovery discover(Collection<String> packageNamePrefixes);
 
     //TODO missing java-doc
     //TODO [ahuber] REVIEW how is this different from discover(String)
diff --git a/core/config/src/main/java/org/apache/isis/config/AppConfigLocator.java b/core/config/src/main/java/org/apache/isis/config/AppConfigLocator.java
index c7a6ca9..1ecdfee 100644
--- a/core/config/src/main/java/org/apache/isis/config/AppConfigLocator.java
+++ b/core/config/src/main/java/org/apache/isis/config/AppConfigLocator.java
@@ -52,6 +52,9 @@ public final class AppConfigLocator {
     
     // for sanity check
     private final static Set<String> criticalServices() {
+    	
+    	//TODO [2033] use classes from AppManifest.Registry instead
+    	
     	return _Sets.newLinkedHashSet(_Lists.of(
     		"org.apache.isis.applib.services.registry.ServiceRegistry", 
     		"org.apache.isis.config.IsisConfiguration", 
@@ -82,8 +85,6 @@ public final class AppConfigLocator {
     		
     		"org.apache.isis.applib.services.homepage.HomePageProviderService"
     		
-    		//"org.jboss.seam.conversation.spi.SeamConversationContext"
-    		
     		
     		));
     }
@@ -166,9 +167,10 @@ public final class AppConfigLocator {
         	
             LOG.info(String.format("Located AppConfig '%s' via ServiceLoader.", appConfigClass.getName()));
             
-            Supplier<Stream<Class<?>>> onDiscover = () -> Stream.concat(
-            		Stream.of(appConfigClass), 
-            		appConfigImpl.isisConfiguration().streamClassesToDiscover());
+            Supplier<Stream<Class<?>>> onDiscover = appConfigImpl.isisConfiguration()::streamClassesToDiscover;
+            
+            onDiscover.get()
+            .forEach(type->probe.println("on discover include '%s'", type));
             
             // as we are in a non-managed environment, we need to bootstrap CDI ourself
             _CDI.init(onDiscover);
diff --git a/core/config/src/main/java/org/apache/isis/config/builder/IsisConfigurationDefault.java b/core/config/src/main/java/org/apache/isis/config/builder/IsisConfigurationDefault.java
index a45ecfe..99c6494 100644
--- a/core/config/src/main/java/org/apache/isis/config/builder/IsisConfigurationDefault.java
+++ b/core/config/src/main/java/org/apache/isis/config/builder/IsisConfigurationDefault.java
@@ -19,6 +19,8 @@
 
 package org.apache.isis.config.builder;
 
+import static org.apache.isis.commons.internal.base._With.requires;
+
 import java.awt.Color;
 import java.awt.Font;
 import java.util.Enumeration;
@@ -26,14 +28,12 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
+import java.util.Set;
 import java.util.StringTokenizer;
 import java.util.stream.Stream;
 
 import javax.enterprise.inject.Vetoed;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.apache.isis.applib.AppManifest;
 import org.apache.isis.commons.internal.base._Lazy;
 import org.apache.isis.commons.internal.base._Strings;
@@ -44,8 +44,8 @@ import org.apache.isis.config.IsisConfiguration;
 import org.apache.isis.config.IsisConfigurationException;
 import org.apache.isis.config.resource.ResourceStreamSource;
 import org.apache.isis.core.commons.exceptions.IsisException;
-
-import static org.apache.isis.commons.internal.base._With.requires;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 
 /**
@@ -98,7 +98,8 @@ class IsisConfigurationDefault implements IsisConfiguration {
     // Type Discovery
     // ////////////////////////////////////////////////
     
-    _Lazy<Integer> typeDiscovery = _Lazy.threadSafe(()->ModulePackageHelper.runTypeDiscovery(this.getAppManifest()));
+    _Lazy<Set<Class<?>>> typeDiscovery = _Lazy.threadSafe(()->
+    	ModulePackageHelper.runTypeDiscovery(this.getAppManifest()));
     
     public void triggerTypeDiscovery() {
         typeDiscovery.get();
@@ -106,8 +107,7 @@ class IsisConfigurationDefault implements IsisConfiguration {
     
     @Override
     public Stream<Class<?>> streamClassesToDiscover() {
-        triggerTypeDiscovery();
-        return AppManifest.Registry.instance().streamAllTypes();
+        return typeDiscovery.get().stream();
     }
     
     
diff --git a/core/config/src/main/java/org/apache/isis/config/builder/ModulePackageHelper.java b/core/config/src/main/java/org/apache/isis/config/builder/ModulePackageHelper.java
index 4fb5d5c..75294dd 100644
--- a/core/config/src/main/java/org/apache/isis/config/builder/ModulePackageHelper.java
+++ b/core/config/src/main/java/org/apache/isis/config/builder/ModulePackageHelper.java
@@ -19,7 +19,10 @@
 
 package org.apache.isis.config.builder;
 
+import static org.apache.isis.commons.internal.base._With.requires;
+
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -27,9 +30,6 @@ import java.util.stream.Stream;
 
 import javax.xml.bind.annotation.XmlElement;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.apache.isis.applib.AppManifest;
 import org.apache.isis.applib.annotation.DomainObject;
 import org.apache.isis.applib.annotation.DomainObjectLayout;
@@ -42,33 +42,34 @@ import org.apache.isis.applib.annotation.ViewModel;
 import org.apache.isis.applib.annotation.ViewModelLayout;
 import org.apache.isis.applib.fixturescripts.DiscoverableFixtureScript;
 import org.apache.isis.applib.fixturescripts.FixtureScript;
-import org.apache.isis.commons.internal.collections._Lists;
 import org.apache.isis.commons.internal.collections._Sets;
+import org.apache.isis.commons.internal.context._Context;
+import org.apache.isis.commons.internal.exceptions._Exceptions;
 import org.apache.isis.commons.internal.reflection._Reflect;
 import org.apache.isis.core.plugins.classdiscovery.ClassDiscovery;
 import org.apache.isis.core.plugins.classdiscovery.ClassDiscoveryPlugin;
 
-import static org.apache.isis.commons.internal.base._With.requires;
+import lombok.val;
+import lombok.extern.slf4j.Slf4j;
 
 /**
  * @since 2.0.0-M2
  */
+@Slf4j
 class ModulePackageHelper {
     
-    private static final Logger LOG = LoggerFactory.getLogger(ModulePackageHelper.class);
-
-    public static int runTypeDiscovery(final AppManifest appManifest) {
+    public static Set<Class<?>> runTypeDiscovery(final AppManifest appManifest) {
         
-        final List<String> moduleAndFrameworkPackages = 
+        final Set<Class<?>> moduleAndFrameworkTypesForScanning = 
                 findAndRegisterTypes(appManifest);
         
-        return moduleAndFrameworkPackages.size();
+        return moduleAndFrameworkTypesForScanning;
         
     }
     
     // -- HELPER
-    
-    private static Stream<String> modulePackageNamesFrom(final AppManifest appManifest) {
+
+    private static Stream<Class<?>> modulesFrom(final AppManifest appManifest) {
         
         final List<Class<?>> modules = appManifest.getModules();
         
@@ -77,27 +78,51 @@ class ModulePackageHelper {
                     "If an appManifest is provided then it must return a non-empty set of modules");
         }
 
-        return modules.stream()
+        return modules.stream();
+    }
+    
+    private static Stream<String> modulePackageNamesFrom(final AppManifest appManifest) {
+        return modulesFrom(appManifest)
                 .map(Class::getPackage)
                 .map(Package::getName);
     }
     
-    private static List<String> findAndRegisterTypes(final AppManifest appManifest) {
+    private static Set<Class<?>> findAndRegisterTypes(final AppManifest appManifest) {
         
         requires(appManifest, "appManifest");
         
-        LOG.info(String.format(
+        log.info(String.format(
                 "Discover the application's domain and register all types using manifest '%s' ...",
                 appManifest.getClass().getName()) );
         
+        
+        val typesForScanning = new HashSet<Class<?>>();
+        AppManifest.Registry.FRAMEWORK_PROVIDED_TYPES_FOR_SCANNING.stream()
+        .map(name -> {
+			try {
+				return _Context.loadClass(name);
+			} catch (ClassNotFoundException e) {
+				throw _Exceptions.unrecoverable(e);
+			}
+		})
+        .forEach(typesForScanning::add);
+        
+        modulesFrom(appManifest)
+        .forEach(typesForScanning::add);
+        
+        typesForScanning.add(appManifest.getClass());
+        
+        //FIXME [2033] at this point we should have all we need, let CDI take over
+        // and let then CDI Bean intercepter make entries into the registry 
+        
         final AppManifest.Registry registry = AppManifest.Registry.instance();
 
-        final List<String> moduleAndFrameworkPackages = _Lists.newArrayList();
+        final Set<String> moduleAndFrameworkPackages = new HashSet<>();
         moduleAndFrameworkPackages.addAll(AppManifest.Registry.FRAMEWORK_PROVIDED_SERVICE_PACKAGES);
-        
         modulePackageNamesFrom(appManifest)
             .forEach(moduleAndFrameworkPackages::add);
-
+        moduleAndFrameworkPackages.add(appManifest.getClass().getPackage().getName());
+        
         final ClassDiscovery discovery = ClassDiscoveryPlugin.get().discover(moduleAndFrameworkPackages);
 
         final Set<Class<?>> domainServiceTypes = _Sets.newLinkedHashSet();
@@ -162,7 +187,7 @@ class ModulePackageHelper {
         registry.setViewModelTypes(withinPackageAndNotAnonymous(packagesWithDotSuffix, viewModelTypes));
         registry.setXmlElementTypes(withinPackageAndNotAnonymous(packagesWithDotSuffix, xmlElementTypes));
         
-        return moduleAndFrameworkPackages;
+        return typesForScanning;
     }
     
     static <T> Set<Class<? extends T>> withinPackageAndNotAnonymous(
@@ -186,7 +211,7 @@ class ModulePackageHelper {
             }
         }
         //TODO [2039] we may need to re-think this policy, there should not be surprising use-cases
-        LOG.warn("Skipping a service for registration because due to not being part of the packagess to include: " + className);
+        log.warn("Skipping a service for registration because due to not being part of the packagess to include: " + className);
         return false;
     }
 
diff --git a/core/plugins/discovery-reflections/src/main/java/org/apache/isis/core/plugins/classdiscovery/reflections/ClassDiscoveryPluginUsingReflections.java b/core/plugins/discovery-reflections/src/main/java/org/apache/isis/core/plugins/classdiscovery/reflections/ClassDiscoveryPluginUsingReflections.java
index 43726da..3b226e4 100644
--- a/core/plugins/discovery-reflections/src/main/java/org/apache/isis/core/plugins/classdiscovery/reflections/ClassDiscoveryPluginUsingReflections.java
+++ b/core/plugins/discovery-reflections/src/main/java/org/apache/isis/core/plugins/classdiscovery/reflections/ClassDiscoveryPluginUsingReflections.java
@@ -16,14 +16,13 @@
  */
 package org.apache.isis.core.plugins.classdiscovery.reflections;
 
-import java.util.List;
-
-import org.reflections.scanners.SubTypesScanner;
-import org.reflections.util.ClasspathHelper;
+import java.util.Collection;
 
 import org.apache.isis.commons.internal.context._Context;
 import org.apache.isis.core.plugins.classdiscovery.ClassDiscovery;
 import org.apache.isis.core.plugins.classdiscovery.ClassDiscoveryPlugin;
+import org.reflections.scanners.SubTypesScanner;
+import org.reflections.util.ClasspathHelper;
 
 public class ClassDiscoveryPluginUsingReflections implements ClassDiscoveryPlugin {
 
@@ -34,7 +33,7 @@ public class ClassDiscoveryPluginUsingReflections implements ClassDiscoveryPlugi
     }
 
     @Override
-    public ClassDiscovery discover(List<String> packageNamePrefixes) {
+    public ClassDiscovery discover(Collection<String> packageNamePrefixes) {
         ReflectManifest.prepareDiscovery();	//TODO [ahuber] REVIEW why is this required?
         return ReflectDiscovery.of(packageNamePrefixes);
     }
diff --git a/core/plugins/ioc-weld/src/main/java/org/apache/isis/core/plugins/ioc/weld/WeldFactory.java b/core/plugins/ioc-weld/src/main/java/org/apache/isis/core/plugins/ioc/weld/WeldFactory.java
index 336dff8..4627805 100644
--- a/core/plugins/ioc-weld/src/main/java/org/apache/isis/core/plugins/ioc/weld/WeldFactory.java
+++ b/core/plugins/ioc-weld/src/main/java/org/apache/isis/core/plugins/ioc/weld/WeldFactory.java
@@ -59,12 +59,6 @@ public class WeldFactory {
 	            		
 	            		"org.jboss.weld.module.web.WeldWebModule",
 	                    
-	                    //"domainapp.application.HelloWorldAppManifest", // specific to the app
-	                    
-	                    "org.apache.isis.config.AppConfig",
-	                    "org.apache.isis.applib.AppManifest",
-	                    "org.apache.isis.core.runtime.RuntimeModule",
-	                    "org.apache.isis.core.metamodel.JdoMetamodelUtil",
 	                    "org.apache.isis.core.wrapper.WrapperFactoryDefault",
 	                    "org.apache.isis.viewer.wicket.viewer.IsisWicketModule",
 	                    "org.apache.isis.applib.services.jdosupport.IsisJdoSupportDN5",
diff --git a/example/application/simpleapp/application/src/main/java/domainapp/application/manifest/DomainAppAppManifest.java b/example/application/simpleapp/application/src/main/java/domainapp/application/manifest/DomainAppAppManifest.java
index a85bb2d..1e6f50f 100644
--- a/example/application/simpleapp/application/src/main/java/domainapp/application/manifest/DomainAppAppManifest.java
+++ b/example/application/simpleapp/application/src/main/java/domainapp/application/manifest/DomainAppAppManifest.java
@@ -55,6 +55,13 @@ public class DomainAppAppManifest extends AppManifestAbstract2 implements AppCon
         
         ThreadPoolSupport.HIGHEST_CONCURRENCY_EXECUTION_MODE_ALLOWED = 
         		ThreadPoolExecutionMode.SEQUENTIAL_WITHIN_CALLING_THREAD;
+        
+        //FIXME [2033] since introducing spring, debug logging seems enabled by default, we overrule here
+        ch.qos.logback.classic.Level level = ch.qos.logback.classic.Level.WARN;
+        ch.qos.logback.classic.Logger root = 
+        		(ch.qos.logback.classic.Logger) org.slf4j.LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
+        root.setLevel(level);
+        
     }
 
 	// Implementing AppConfig, to tell the framework how to bootstrap the configuration.
diff --git a/example/application/simpleapp/module-spring/src/main/java/domainapp/modules/spring/SpringContextListener.java b/example/application/simpleapp/module-spring/src/main/java/domainapp/modules/spring/SpringContextListener.java
new file mode 100644
index 0000000..7d266aa
--- /dev/null
+++ b/example/application/simpleapp/module-spring/src/main/java/domainapp/modules/spring/SpringContextListener.java
@@ -0,0 +1,26 @@
+package domainapp.modules.spring;
+
+import java.util.NoSuchElementException;
+
+import javax.enterprise.inject.Vetoed;
+
+import org.apache.isis.commons.internal.context._Context;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+@Vetoed // must not be managed by CDI
+public class SpringContextListener implements ApplicationContextAware {
+
+	@Override
+	public void setApplicationContext(ApplicationContext springContext) throws BeansException {
+		_Context.putSingleton(ApplicationContext.class, springContext);
+	}
+	
+	public static ApplicationContext currentContext() {
+		return _Context.getOrThrow(ApplicationContext.class, 
+				()-> new NoSuchElementException(
+						"There is no Spring ApplicationContext stored on framework's _Context."));
+	}
+	
+}
diff --git a/example/application/simpleapp/module-spring/src/main/java/domainapp/modules/spring/SpringModuleCDIBridge.java b/example/application/simpleapp/module-spring/src/main/java/domainapp/modules/spring/SpringModuleCDIBridge.java
new file mode 100644
index 0000000..f0b4ba4
--- /dev/null
+++ b/example/application/simpleapp/module-spring/src/main/java/domainapp/modules/spring/SpringModuleCDIBridge.java
@@ -0,0 +1,17 @@
+package domainapp.modules.spring;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Produces;
+import javax.inject.Singleton;
+
+import domainapp.modules.spring.dom.customer.CustomerRepository;
+
+@ApplicationScoped // not to be managed by Spring
+public class SpringModuleCDIBridge {
+
+	@Produces @Singleton
+	public CustomerRepository getCustomerRepository() {
+		return SpringContextListener.currentContext().getBean(CustomerRepository.class);
+	}
+	
+}
diff --git a/example/application/simpleapp/module-spring/src/main/java/domainapp/modules/spring/dom/customer/Customer.java b/example/application/simpleapp/module-spring/src/main/java/domainapp/modules/spring/dom/customer/Customer.java
new file mode 100644
index 0000000..24e4043
--- /dev/null
+++ b/example/application/simpleapp/module-spring/src/main/java/domainapp/modules/spring/dom/customer/Customer.java
@@ -0,0 +1,32 @@
+package domainapp.modules.spring.dom.customer;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+
+import org.apache.isis.applib.annotation.DomainObject;
+import org.apache.isis.applib.annotation.Nature;
+
+import lombok.Getter;
+import lombok.ToString;
+
+@DomainObject(nature=Nature.EXTERNAL_ENTITY)
+@Entity @ToString
+public class Customer {
+
+    @Id
+    @GeneratedValue(strategy=GenerationType.AUTO)
+    @Getter private Long id;
+    
+    @Getter private String firstName;
+    @Getter private String lastName;
+
+    protected Customer() {}
+
+    public Customer(String firstName, String lastName) {
+        this.firstName = firstName;
+        this.lastName = lastName;
+    }
+
+}
diff --git a/example/application/simpleapp/module-spring/src/main/java/domainapp/modules/spring/dom/customer/CustomerMenu.java b/example/application/simpleapp/module-spring/src/main/java/domainapp/modules/spring/dom/customer/CustomerMenu.java
new file mode 100644
index 0000000..1af74d8
--- /dev/null
+++ b/example/application/simpleapp/module-spring/src/main/java/domainapp/modules/spring/dom/customer/CustomerMenu.java
@@ -0,0 +1,30 @@
+package domainapp.modules.spring.dom.customer;
+
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.apache.isis.applib.annotation.Action;
+import org.apache.isis.applib.annotation.ActionLayout;
+import org.apache.isis.applib.annotation.DomainObjectLayout;
+import org.apache.isis.applib.annotation.DomainService;
+import org.apache.isis.applib.annotation.NatureOfService;
+
+@DomainService(
+        nature = NatureOfService.VIEW_MENU_ONLY,
+        objectType = "simple.CustomerMenu"
+)
+@DomainObjectLayout(named="Customer (Spring Demo)")
+@Singleton
+public class CustomerMenu {
+
+	@Inject private CustomerRepository customerRepository;
+	
+	@Action
+	@ActionLayout(cssClassFa="fa-leaf")
+	public List<Customer> findByLastName(String lastName) {
+		return customerRepository.findByLastName(lastName);
+	}
+	
+}
diff --git a/example/application/simpleapp/module-spring/src/main/java/domainapp/modules/spring/dom/customer/CustomerRepository.java b/example/application/simpleapp/module-spring/src/main/java/domainapp/modules/spring/dom/customer/CustomerRepository.java
new file mode 100644
index 0000000..d4c5555
--- /dev/null
+++ b/example/application/simpleapp/module-spring/src/main/java/domainapp/modules/spring/dom/customer/CustomerRepository.java
@@ -0,0 +1,9 @@
+package domainapp.modules.spring.dom.customer;
+
+import java.util.List;
+
+import org.springframework.data.repository.CrudRepository;
+
+public interface CustomerRepository extends CrudRepository<Customer, Long> {
+    List<Customer> findByLastName(String lastName);
+}