You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2016/12/11 17:59:55 UTC

[01/10] cayenne git commit: CAY-2166 Auto-loading of Cayenne modules

Repository: cayenne
Updated Branches:
  refs/heads/master e2624ed77 -> 2bc717d28


CAY-2166 Auto-loading of Cayenne modules


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/2fd200b0
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/2fd200b0
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/2fd200b0

Branch: refs/heads/master
Commit: 2fd200b03196cf490c94acfddf3792e8d466c52e
Parents: e2624ed
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Sun Dec 11 17:05:33 2016 +0300
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Sun Dec 11 17:39:01 2016 +0300

----------------------------------------------------------------------
 .../java/org/apache/cayenne/di/spi/DIGraph.java |  21 ++-
 .../org/apache/cayenne/di/spi/ListProvider.java |   3 -
 .../org/apache/cayenne/di/spi/ModuleLoader.java |  87 +++++++++++
 .../apache/cayenne/di/spi/ModuleProvider.java   |  41 +++++
 .../org/apache/cayenne/di/spi/DIGraphTest.java  |  58 +++++++
 .../apache/cayenne/di/spi/ModuleLoaderTest.java | 152 +++++++++++++++++++
 .../org.apache.cayenne.di.spi.ModuleProvider    |   4 +
 7 files changed, 351 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/2fd200b0/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DIGraph.java
----------------------------------------------------------------------
diff --git a/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DIGraph.java b/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DIGraph.java
index 5de1cd8..6e8592b 100644
--- a/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DIGraph.java
+++ b/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DIGraph.java
@@ -19,14 +19,9 @@
 
 package org.apache.cayenne.di.spi;
 
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Deque;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
+import org.apache.cayenne.di.DIRuntimeException;
+
+import java.util.*;
 
 /**
  * The implementation here is basically an adjacency list, but a {@link Map} is
@@ -39,7 +34,7 @@ import java.util.Map;
 class DIGraph<V> {
 
 	/**
-	 * Note: {@link LinkedHashMap} is used for supporting insertion order.
+	 * {@link LinkedHashMap} is used for supporting insertion order.
 	 */
 	private Map<V, List<V>> neighbors = new LinkedHashMap<>();
 
@@ -129,8 +124,7 @@ class DIGraph<V> {
 	}
 
 	/**
-	 * Return (as a List) the topological sort of the vertices; null for no such
-	 * sort.
+	 * Return (as a List) the topological sort of the vertices. Throws an exception if cycles are detected.
 	 */
 	public List<V> topSort() {
 		Map<V, Integer> degree = inDegree();
@@ -157,7 +151,7 @@ class DIGraph<V> {
 
 		// Check that we have used the entire graph (if not, there was a cycle)
 		if (result.size() != neighbors.size()) {
-			return null;
+			throw new DIRuntimeException("Dependency cycle detected in DI container");
 		}
 
 		return result;
@@ -176,4 +170,7 @@ class DIGraph<V> {
 		return s.toString();
 	}
 
+	public int size() {
+		return neighbors.size();
+	}
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2fd200b0/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ListProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ListProvider.java b/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ListProvider.java
index 2178635..9fee307 100644
--- a/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ListProvider.java
+++ b/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ListProvider.java
@@ -49,9 +49,6 @@ class ListProvider implements Provider<List<?>> {
     public List<?> get() throws DIRuntimeException {
         List<Key<?>> insertOrder = graph.topSort();
 
-        if (insertOrder == null)
-            throw new DIRuntimeException("Dependency cycle detected in DI container");
-
         if (insertOrder.size() != providers.size()) {
             List<Key<?>> emptyKeys = new ArrayList<>();
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2fd200b0/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ModuleLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ModuleLoader.java b/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ModuleLoader.java
new file mode 100644
index 0000000..8ea771e
--- /dev/null
+++ b/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ModuleLoader.java
@@ -0,0 +1,87 @@
+/*
+ *    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.cayenne.di.spi;
+
+import org.apache.cayenne.di.DIRuntimeException;
+import org.apache.cayenne.di.Module;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+/**
+ * Auto-loads DI modules using ServiceLoader. To make a module auto-loadable, you will need to ship the jar with a file
+ * "META-INF/services/org.apache.cayenne.di.spi.ModuleProvider" that contains provider implementation for the module in
+ * question.
+ *
+ * @since 4.0
+ */
+public class ModuleLoader {
+
+    /**
+     * Auto-loads all modules declared on classpath. Modules are loaded from the SPI declarations stored in
+     * "META-INF/services/org.apache.cayenne.di.spi.ModuleProvider", and then sorted in the order of override dependency.
+     *
+     * @return a sorted collection of auto-loadable modules.
+     * @throws DIRuntimeException if auto-loaded modules have circular override dependencies.
+     */
+    public List<Module> load() {
+
+        // map providers by class
+
+        Map<Class<? extends Module>, ModuleProvider> providers = new HashMap<>();
+
+        for (ModuleProvider provider : ServiceLoader.load(ModuleProvider.class)) {
+
+            ModuleProvider existing = providers.put(provider.moduleType(), provider);
+            if (existing != null && !existing.getClass().equals(provider.getClass())) {
+                throw new DIRuntimeException("More than one provider for module type '%s': %s and %s",
+                        provider.moduleType().getName(),
+                        existing.getClass().getName(),
+                        provider.getClass().getName());
+            }
+        }
+
+        if (providers.size() == 0) {
+            return Collections.emptyList();
+        }
+
+        // do override dependency sort...
+        DIGraph<Class<? extends Module>> overrideGraph = new DIGraph<>();
+        for (Map.Entry<Class<? extends Module>, ModuleProvider> e : providers.entrySet()) {
+
+            overrideGraph.add(e.getKey());
+            for (Class<? extends Module> overridden : e.getValue().overrides()) {
+                overrideGraph.add(e.getKey(), overridden);
+            }
+        }
+
+        Collection<Class<? extends Module>> moduleTypes = overrideGraph.topSort();
+        List<Module> modules = new ArrayList<>(moduleTypes.size());
+        for (Class<? extends Module> type : moduleTypes) {
+            modules.add(providers.get(type).module());
+        }
+
+        return modules;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2fd200b0/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ModuleProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ModuleProvider.java b/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ModuleProvider.java
new file mode 100644
index 0000000..e96322e
--- /dev/null
+++ b/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ModuleProvider.java
@@ -0,0 +1,41 @@
+/*
+ *    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.cayenne.di.spi;
+
+import org.apache.cayenne.di.Module;
+
+/**
+ * Provider of modules used by module auto-loading mechanism to identify and load modules.
+ *
+ * @since 4.0
+ */
+public interface ModuleProvider {
+
+    Module module();
+
+    Class<? extends Module> moduleType();
+
+    /**
+     * Returns an array of module types this module overrides. Module auto-loading mechanism will ensure module
+     * load order that respects overriding preferences.
+     *
+     * @return a collection of module types this module overrides.
+     */
+    Class<? extends Module>[] overrides();
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2fd200b0/cayenne-di/src/test/java/org/apache/cayenne/di/spi/DIGraphTest.java
----------------------------------------------------------------------
diff --git a/cayenne-di/src/test/java/org/apache/cayenne/di/spi/DIGraphTest.java b/cayenne-di/src/test/java/org/apache/cayenne/di/spi/DIGraphTest.java
new file mode 100644
index 0000000..dcfe1e8
--- /dev/null
+++ b/cayenne-di/src/test/java/org/apache/cayenne/di/spi/DIGraphTest.java
@@ -0,0 +1,58 @@
+/*
+ *    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.cayenne.di.spi;
+
+import org.apache.cayenne.di.DIRuntimeException;
+import org.junit.Test;
+
+import java.util.List;
+
+import static java.util.Arrays.asList;
+import static org.junit.Assert.assertEquals;
+
+public class DIGraphTest {
+
+    @Test
+    public void testTopSortNoCycles() {
+        DIGraph<String> graph = new DIGraph<>();
+        graph.add("x", "y");
+        graph.add("x", "z");
+        graph.add("z", "a");
+
+        List<String> sorted = graph.topSort();
+        assertEquals(asList("y", "a", "z", "x"), sorted);
+    }
+
+    @Test(expected = DIRuntimeException.class)
+    public void testTopSortDirectCycle() {
+        DIGraph<String> graph = new DIGraph<>();
+        graph.add("x", "y");
+        graph.add("y", "x");
+        graph.topSort();
+    }
+
+    @Test(expected = DIRuntimeException.class)
+    public void testTopSortInDirectCycle() {
+        DIGraph<String> graph = new DIGraph<>();
+        graph.add("x", "y");
+        graph.add("y", "z");
+        graph.add("z", "x");
+        graph.topSort();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2fd200b0/cayenne-di/src/test/java/org/apache/cayenne/di/spi/ModuleLoaderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-di/src/test/java/org/apache/cayenne/di/spi/ModuleLoaderTest.java b/cayenne-di/src/test/java/org/apache/cayenne/di/spi/ModuleLoaderTest.java
new file mode 100644
index 0000000..3456b5b
--- /dev/null
+++ b/cayenne-di/src/test/java/org/apache/cayenne/di/spi/ModuleLoaderTest.java
@@ -0,0 +1,152 @@
+/*
+ *    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.cayenne.di.spi;
+
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.DIBootstrap;
+import org.apache.cayenne.di.Injector;
+import org.apache.cayenne.di.Module;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class ModuleLoaderTest {
+
+    @Test
+    public void testLoad() {
+
+        List<Module> modules = new ModuleLoader().load();
+        assertEquals(4, modules.size());
+        assertTrue(modules.get(0) instanceof Module2);
+        assertTrue(modules.get(1) instanceof Module1);
+        assertTrue(modules.get(2) instanceof Module3);
+        assertTrue(modules.get(3) instanceof Module4);
+
+        Injector i = DIBootstrap.createInjector(modules);
+        assertEquals("a", i.getInstance(String.class));
+        assertEquals(Integer.valueOf(56), i.getInstance(Integer.class));
+    }
+
+    public static class Module1 implements Module {
+
+        @Override
+        public void configure(Binder binder) {
+            binder.bind(String.class).toInstance("a");
+        }
+    }
+
+    public static class Module2 implements Module {
+
+        @Override
+        public void configure(Binder binder) {
+            binder.bind(String.class).toInstance("b");
+        }
+    }
+
+    public static class Module3 implements Module {
+
+        @Override
+        public void configure(Binder binder) {
+            binder.bind(Integer.class).toInstance(66);
+        }
+    }
+
+    public static class Module4 implements Module {
+
+        @Override
+        public void configure(Binder binder) {
+            binder.bind(Integer.class).toInstance(56);
+        }
+    }
+
+    public static class ModuleProvider1 implements ModuleProvider {
+
+        @Override
+        public Module module() {
+            return new Module1();
+        }
+
+        @Override
+        public Class<? extends Module> moduleType() {
+            return Module1.class;
+        }
+
+        @Override
+        public Class<? extends Module>[] overrides() {
+            return new Class[]{Module2.class};
+        }
+    }
+
+    public static class ModuleProvider2 implements ModuleProvider {
+
+        @Override
+        public Module module() {
+            return new Module2();
+        }
+
+        @Override
+        public Class<? extends Module> moduleType() {
+            return Module2.class;
+        }
+
+        @Override
+        public Class<? extends Module>[] overrides() {
+            return new Class[0];
+        }
+    }
+
+    public static class ModuleProvider3 implements ModuleProvider {
+
+        @Override
+        public Module module() {
+            return new Module3();
+        }
+
+        @Override
+        public Class<? extends Module> moduleType() {
+            return Module3.class;
+        }
+
+        @Override
+        public Class<? extends Module>[] overrides() {
+            return new Class[0];
+        }
+    }
+
+    public static class ModuleProvider4 implements ModuleProvider {
+
+        @Override
+        public Module module() {
+            return new Module4();
+        }
+
+        @Override
+        public Class<? extends Module> moduleType() {
+            return Module4.class;
+        }
+
+        @Override
+        public Class<? extends Module>[] overrides() {
+            return new Class[]{Module3.class};
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2fd200b0/cayenne-di/src/test/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider
----------------------------------------------------------------------
diff --git a/cayenne-di/src/test/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider b/cayenne-di/src/test/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider
new file mode 100644
index 0000000..ff2d444
--- /dev/null
+++ b/cayenne-di/src/test/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider
@@ -0,0 +1,4 @@
+org.apache.cayenne.di.spi.ModuleLoaderTest$ModuleProvider1
+org.apache.cayenne.di.spi.ModuleLoaderTest$ModuleProvider2
+org.apache.cayenne.di.spi.ModuleLoaderTest$ModuleProvider3
+org.apache.cayenne.di.spi.ModuleLoaderTest$ModuleProvider4
\ No newline at end of file


[05/10] cayenne git commit: CAY-2166 Auto-loading of Cayenne modules

Posted by aa...@apache.org.
CAY-2166 Auto-loading of Cayenne modules

* auto-loading of Java8Module
* renamed CayenneJava8Module to Java8Module


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/c0c1c5af
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/c0c1c5af
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/c0c1c5af

Branch: refs/heads/master
Commit: c0c1c5af79ad04df192da2b3232c6b0c384ef01f
Parents: 0fbc622
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Sun Dec 11 17:05:33 2016 +0300
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Sun Dec 11 19:54:42 2016 +0300

----------------------------------------------------------------------
 cayenne-java8/pom.xml                           | 22 ++++++++++
 .../cayenne/java8/CayenneJava8Module.java       | 42 -------------------
 .../org/apache/cayenne/java8/Java8Module.java   | 40 ++++++++++++++++++
 .../cayenne/java8/Java8ModuleProvider.java      | 43 ++++++++++++++++++++
 .../org.apache.cayenne.di.spi.ModuleProvider    |  1 +
 .../cayenne/java8/Java8ModuleProviderTest.java  | 30 ++++++++++++++
 .../org/apache/cayenne/java8/RuntimeBase.java   |  5 +--
 .../cayenne/rop/protostuff/RuntimeBase.java     | 12 +++---
 .../apache/cayenne/tutorial/Http2Client.java    |  4 +-
 .../cayenne/tutorial/Http2ROPServlet.java       | 11 ++---
 10 files changed, 152 insertions(+), 58 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/c0c1c5af/cayenne-java8/pom.xml
----------------------------------------------------------------------
diff --git a/cayenne-java8/pom.xml b/cayenne-java8/pom.xml
index 978601a..525f1b3 100644
--- a/cayenne-java8/pom.xml
+++ b/cayenne-java8/pom.xml
@@ -46,6 +46,28 @@
 			<artifactId>hsqldb</artifactId>
 			<scope>test</scope>
 		</dependency>
+		<dependency>
+			<groupId>org.apache.cayenne</groupId>
+			<artifactId>cayenne-server</artifactId>
+			<version>${project.version}</version>
+			<type>test-jar</type>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>jcl-over-slf4j</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-simple</artifactId>
+			<scope>test</scope>
+		</dependency>
 	</dependencies>
 	<build>
 		<plugins>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c0c1c5af/cayenne-java8/src/main/java/org/apache/cayenne/java8/CayenneJava8Module.java
----------------------------------------------------------------------
diff --git a/cayenne-java8/src/main/java/org/apache/cayenne/java8/CayenneJava8Module.java b/cayenne-java8/src/main/java/org/apache/cayenne/java8/CayenneJava8Module.java
deleted file mode 100644
index 251718e..0000000
--- a/cayenne-java8/src/main/java/org/apache/cayenne/java8/CayenneJava8Module.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*****************************************************************
- *   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.cayenne.java8;
-
-import org.apache.cayenne.configuration.server.ServerModule;
-import org.apache.cayenne.di.Binder;
-import org.apache.cayenne.di.Module;
-import org.apache.cayenne.java8.access.types.LocalDateTimeType;
-import org.apache.cayenne.java8.access.types.LocalDateType;
-import org.apache.cayenne.java8.access.types.LocalTimeType;
-
-public class CayenneJava8Module implements Module {
-
-    public CayenneJava8Module() {
-    }
-
-    @Override
-    public void configure(Binder binder) {
-        ServerModule.contributeDefaultTypes(binder)
-                .add(new LocalDateType())
-                .add(new LocalTimeType())
-                .add(new LocalDateTimeType());
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c0c1c5af/cayenne-java8/src/main/java/org/apache/cayenne/java8/Java8Module.java
----------------------------------------------------------------------
diff --git a/cayenne-java8/src/main/java/org/apache/cayenne/java8/Java8Module.java b/cayenne-java8/src/main/java/org/apache/cayenne/java8/Java8Module.java
new file mode 100644
index 0000000..2de3749
--- /dev/null
+++ b/cayenne-java8/src/main/java/org/apache/cayenne/java8/Java8Module.java
@@ -0,0 +1,40 @@
+/*
+ *    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.cayenne.java8;
+
+import org.apache.cayenne.configuration.server.ServerModule;
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.java8.access.types.LocalDateTimeType;
+import org.apache.cayenne.java8.access.types.LocalDateType;
+import org.apache.cayenne.java8.access.types.LocalTimeType;
+
+/**
+ * @since 4.0
+ */
+public class Java8Module implements Module {
+
+    @Override
+    public void configure(Binder binder) {
+        ServerModule.contributeDefaultTypes(binder)
+                .add(new LocalDateType())
+                .add(new LocalTimeType())
+                .add(new LocalDateTimeType());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c0c1c5af/cayenne-java8/src/main/java/org/apache/cayenne/java8/Java8ModuleProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-java8/src/main/java/org/apache/cayenne/java8/Java8ModuleProvider.java b/cayenne-java8/src/main/java/org/apache/cayenne/java8/Java8ModuleProvider.java
new file mode 100644
index 0000000..243dc73
--- /dev/null
+++ b/cayenne-java8/src/main/java/org/apache/cayenne/java8/Java8ModuleProvider.java
@@ -0,0 +1,43 @@
+/*
+ *    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.cayenne.java8;
+
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.di.spi.ModuleProvider;
+
+/**
+ * @since 4.0
+ */
+public class Java8ModuleProvider implements ModuleProvider {
+
+    @Override
+    public Module module() {
+        return new Java8Module();
+    }
+
+    @Override
+    public Class<? extends Module> moduleType() {
+        return Java8Module.class;
+    }
+
+    @Override
+    public Class<? extends Module>[] overrides() {
+        return new Class[0];
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c0c1c5af/cayenne-java8/src/main/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider
----------------------------------------------------------------------
diff --git a/cayenne-java8/src/main/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider b/cayenne-java8/src/main/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider
new file mode 100644
index 0000000..d24953f
--- /dev/null
+++ b/cayenne-java8/src/main/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider
@@ -0,0 +1 @@
+org.apache.cayenne.java8.Java8ModuleProvider
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c0c1c5af/cayenne-java8/src/test/java/org/apache/cayenne/java8/Java8ModuleProviderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-java8/src/test/java/org/apache/cayenne/java8/Java8ModuleProviderTest.java b/cayenne-java8/src/test/java/org/apache/cayenne/java8/Java8ModuleProviderTest.java
new file mode 100644
index 0000000..4c4df57
--- /dev/null
+++ b/cayenne-java8/src/test/java/org/apache/cayenne/java8/Java8ModuleProviderTest.java
@@ -0,0 +1,30 @@
+/*
+ *    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.cayenne.java8;
+
+import org.apache.cayenne.unit.util.ModuleProviderChecker;
+import org.junit.Test;
+
+public class Java8ModuleProviderTest {
+
+    @Test
+    public void testProviderPresent() {
+        ModuleProviderChecker.testProviderPresent(Java8ModuleProvider.class);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c0c1c5af/cayenne-java8/src/test/java/org/apache/cayenne/java8/RuntimeBase.java
----------------------------------------------------------------------
diff --git a/cayenne-java8/src/test/java/org/apache/cayenne/java8/RuntimeBase.java b/cayenne-java8/src/test/java/org/apache/cayenne/java8/RuntimeBase.java
index f150818..8b75880 100644
--- a/cayenne-java8/src/test/java/org/apache/cayenne/java8/RuntimeBase.java
+++ b/cayenne-java8/src/test/java/org/apache/cayenne/java8/RuntimeBase.java
@@ -20,7 +20,6 @@
 package org.apache.cayenne.java8;
 
 import org.apache.cayenne.configuration.server.ServerRuntime;
-import org.apache.cayenne.di.Module;
 import org.junit.Before;
 
 public class RuntimeBase {
@@ -29,8 +28,6 @@ public class RuntimeBase {
 
     @Before
     public void setUpRuntime() throws Exception {
-        Module java8Module = new CayenneJava8Module();
-        this.runtime = new ServerRuntime("cayenne-java8.xml", java8Module);
+        this.runtime = ServerRuntime.builder().addConfig("cayenne-java8.xml").build();
     }
-
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c0c1c5af/cayenne-protostuff/src/test/java/org/apache/cayenne/rop/protostuff/RuntimeBase.java
----------------------------------------------------------------------
diff --git a/cayenne-protostuff/src/test/java/org/apache/cayenne/rop/protostuff/RuntimeBase.java b/cayenne-protostuff/src/test/java/org/apache/cayenne/rop/protostuff/RuntimeBase.java
index 91bd033..3867862 100644
--- a/cayenne-protostuff/src/test/java/org/apache/cayenne/rop/protostuff/RuntimeBase.java
+++ b/cayenne-protostuff/src/test/java/org/apache/cayenne/rop/protostuff/RuntimeBase.java
@@ -26,7 +26,7 @@ import org.apache.cayenne.configuration.rop.client.ClientRuntime;
 import org.apache.cayenne.configuration.rop.client.ProtostuffModule;
 import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.apache.cayenne.di.Module;
-import org.apache.cayenne.java8.CayenneJava8Module;
+import org.apache.cayenne.java8.Java8Module;
 import org.apache.cayenne.remote.ClientConnection;
 import org.apache.cayenne.remote.service.ProtostuffLocalConnectionProvider;
 import org.junit.Before;
@@ -42,9 +42,11 @@ public class RuntimeBase extends ProtostuffProperties {
 
     @Before
     public void setUpRuntimes() throws Exception {
-        this.serverRuntime = new ServerRuntime("cayenne-protostuff.xml",
-                new ProtostuffModule(),
-                new CayenneJava8Module());
+        this.serverRuntime = ServerRuntime
+                .builder()
+                .addConfig("cayenne-protostuff.xml")
+                .addModule(new ProtostuffModule())
+                .build();
 
         Map<String, String> properties = new HashMap<>();
         properties.put(ClientConstants.ROP_CHANNEL_EVENTS_PROPERTY, Boolean.TRUE.toString());
@@ -56,7 +58,7 @@ public class RuntimeBase extends ProtostuffProperties {
                 serverRuntime.getInjector(),
                 properties,
                 new ProtostuffModule(),
-                new CayenneJava8Module(),
+                new Java8Module(),
                 module);
 
         this.context = clientRuntime.newContext();

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c0c1c5af/tutorials/tutorial-rop-client-http2/src/main/java/org/apache/cayenne/tutorial/Http2Client.java
----------------------------------------------------------------------
diff --git a/tutorials/tutorial-rop-client-http2/src/main/java/org/apache/cayenne/tutorial/Http2Client.java b/tutorials/tutorial-rop-client-http2/src/main/java/org/apache/cayenne/tutorial/Http2Client.java
index 6fb819d..33f7d93 100644
--- a/tutorials/tutorial-rop-client-http2/src/main/java/org/apache/cayenne/tutorial/Http2Client.java
+++ b/tutorials/tutorial-rop-client-http2/src/main/java/org/apache/cayenne/tutorial/Http2Client.java
@@ -24,7 +24,7 @@ import org.apache.cayenne.configuration.rop.client.ClientConstants;
 import org.apache.cayenne.configuration.rop.client.ClientJettyHttp2Module;
 import org.apache.cayenne.configuration.rop.client.ClientRuntime;
 import org.apache.cayenne.configuration.rop.client.ProtostuffModule;
-import org.apache.cayenne.java8.CayenneJava8Module;
+import org.apache.cayenne.java8.Java8Module;
 import org.apache.cayenne.query.ObjectSelect;
 import org.apache.cayenne.rop.JettyHttp2ClientConnectionProvider;
 import org.apache.cayenne.rop.http.JettyHttpROPConnector;
@@ -69,7 +69,7 @@ public class Http2Client {
         ClientRuntime runtime = new ClientRuntime(properties,
                 new ClientJettyHttp2Module(),
                 new ProtostuffModule(),
-                new CayenneJava8Module());
+                new Java8Module());
 
         ObjectContext context = runtime.newContext();
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c0c1c5af/tutorials/tutorial-rop-server-http2/src/main/java/org/apache/cayenne/tutorial/Http2ROPServlet.java
----------------------------------------------------------------------
diff --git a/tutorials/tutorial-rop-server-http2/src/main/java/org/apache/cayenne/tutorial/Http2ROPServlet.java b/tutorials/tutorial-rop-server-http2/src/main/java/org/apache/cayenne/tutorial/Http2ROPServlet.java
index 1ae9865..4c4b4df 100644
--- a/tutorials/tutorial-rop-server-http2/src/main/java/org/apache/cayenne/tutorial/Http2ROPServlet.java
+++ b/tutorials/tutorial-rop-server-http2/src/main/java/org/apache/cayenne/tutorial/Http2ROPServlet.java
@@ -25,7 +25,6 @@ import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.apache.cayenne.configuration.web.WebConfiguration;
 import org.apache.cayenne.configuration.web.WebUtil;
 import org.apache.cayenne.di.Module;
-import org.apache.cayenne.java8.CayenneJava8Module;
 import org.apache.cayenne.remote.RemoteService;
 import org.apache.cayenne.rop.ROPSerializationService;
 import org.apache.cayenne.rop.ROPServlet;
@@ -51,11 +50,13 @@ public class Http2ROPServlet extends ROPServlet {
 
         Collection<Module> modules = configAdapter.createModules(
                 new ROPServerModule(eventBridgeParameters),
-                new ProtostuffModule(),
-                new CayenneJava8Module());
+                new ProtostuffModule());
 
-        ServerRuntime runtime = new ServerRuntime(configurationLocation, modules
-                .toArray(new Module[modules.size()]));
+        ServerRuntime runtime = ServerRuntime
+                .builder()
+                .addConfig(configurationLocation)
+                .addModules(modules)
+                .build();
 
         this.remoteService = runtime.getInjector().getInstance(RemoteService.class);
         this.serializationService = runtime.getInjector().getInstance(ROPSerializationService.class);


[04/10] cayenne git commit: shorten pom naming

Posted by aa...@apache.org.
shorten pom naming


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/0fbc6228
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/0fbc6228
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/0fbc6228

Branch: refs/heads/master
Commit: 0fbc62280cedae6f5ef27fc7443fc5b29f308640
Parents: ba59e95
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Sun Dec 11 15:13:30 2016 +0300
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Sun Dec 11 19:41:39 2016 +0300

----------------------------------------------------------------------
 itests/cayenne-tools-itest/pom.xml | 2 +-
 itests/pom.xml                     | 2 +-
 modeler/cayenne-wocompat/pom.xml   | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/0fbc6228/itests/cayenne-tools-itest/pom.xml
----------------------------------------------------------------------
diff --git a/itests/cayenne-tools-itest/pom.xml b/itests/cayenne-tools-itest/pom.xml
index 423d629..b37523e 100644
--- a/itests/cayenne-tools-itest/pom.xml
+++ b/itests/cayenne-tools-itest/pom.xml
@@ -26,7 +26,7 @@
 
 	<description>Integration Tests - Cayenne Tools</description>
 	<artifactId>cayenne-tools-itest</artifactId>
-	<name>cayenne-tools-itest: Integration Tests - Cayenne Tools</name>
+	<name>cayenne-tools-itest: Tools integration tests</name>
 	<packaging>jar</packaging>
 	
 	<properties>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/0fbc6228/itests/pom.xml
----------------------------------------------------------------------
diff --git a/itests/pom.xml b/itests/pom.xml
index 8c73f5d..92413cf 100644
--- a/itests/pom.xml
+++ b/itests/pom.xml
@@ -30,7 +30,7 @@
 
     <groupId>org.apache.cayenne.itests</groupId>
     <artifactId>cayenne-itests-parent</artifactId>
-    <name>cayenne-itests-parent: Cayenne Integration Tests Parent</name>
+    <name>cayenne-itests-parent: Integration Tests Parent</name>
     <packaging>pom</packaging>
     
     <modules>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/0fbc6228/modeler/cayenne-wocompat/pom.xml
----------------------------------------------------------------------
diff --git a/modeler/cayenne-wocompat/pom.xml b/modeler/cayenne-wocompat/pom.xml
index 9c62f2d..02192ed 100644
--- a/modeler/cayenne-wocompat/pom.xml
+++ b/modeler/cayenne-wocompat/pom.xml
@@ -25,7 +25,7 @@
 	</parent>
 
 	<artifactId>cayenne-wocompat</artifactId>
-	<name>cayenne-wocompat: Cayenne WebObjects Compatibility Library</name>
+	<name>cayenne-wocompat: WebObjects Compatibility Library</name>
 	<packaging>jar</packaging>
 
 	<dependencies>


[06/10] cayenne git commit: dependencies scope cleanup, bridged logging to SLF

Posted by aa...@apache.org.
dependencies scope cleanup, bridged logging to SLF


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/29c640e8
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/29c640e8
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/29c640e8

Branch: refs/heads/master
Commit: 29c640e8c027373a77cebb068fbf8414104de116
Parents: c0c1c5a
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Sun Dec 11 20:02:00 2016 +0300
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Sun Dec 11 20:03:20 2016 +0300

----------------------------------------------------------------------
 cayenne-joda/pom.xml  |  18 +++++++-
 cayenne-tools/pom.xml | 111 +++++++++++++++++++++++++--------------------
 2 files changed, 80 insertions(+), 49 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/29c640e8/cayenne-joda/pom.xml
----------------------------------------------------------------------
diff --git a/cayenne-joda/pom.xml b/cayenne-joda/pom.xml
index 009ea2d..3f2fbe8 100644
--- a/cayenne-joda/pom.xml
+++ b/cayenne-joda/pom.xml
@@ -45,11 +45,12 @@
 			<groupId>org.apache.cayenne</groupId>
 			<artifactId>cayenne-server</artifactId>
 			<version>${project.version}</version>
+			<scope>compile</scope>
 		</dependency>
-
 		<dependency>
 			<groupId>joda-time</groupId>
 			<artifactId>joda-time</artifactId>
+			<scope>compile</scope>
 		</dependency>
 
 		<!-- Test dependencies -->
@@ -74,6 +75,21 @@
 			<artifactId>hsqldb</artifactId>
 			<scope>test</scope>
 		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>jcl-over-slf4j</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-simple</artifactId>
+			<scope>test</scope>
+		</dependency>
 	</dependencies>
 	<build>
 		<plugins>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/29c640e8/cayenne-tools/pom.xml
----------------------------------------------------------------------
diff --git a/cayenne-tools/pom.xml b/cayenne-tools/pom.xml
index eb9a6e7..fb48cb3 100644
--- a/cayenne-tools/pom.xml
+++ b/cayenne-tools/pom.xml
@@ -31,88 +31,103 @@
 	<packaging>jar</packaging>
 	<name>cayenne-tools: Cayenne Tools</name>
 	<dependencies>
-		<dependency>
-			<groupId>junit</groupId>
-			<artifactId>junit</artifactId>
-		</dependency>
-        <dependency>
-            <groupId>org.apache.cayenne</groupId>
-            <artifactId>cayenne-server</artifactId>
-            <version>${project.version}</version>
-            <scope>test</scope>
-            <type>test-jar</type>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.cayenne</groupId>
-            <artifactId>cayenne-dbsync</artifactId>
-            <version>${project.version}</version>
-            <scope>test</scope>
-            <type>test-jar</type>
-        </dependency>
-
-        <dependency>
-			<groupId>org.apache.cayenne.build-tools</groupId>
-			<artifactId>cayenne-test-utilities</artifactId>
-			<version>${project.version}</version>
-			<scope>test</scope>
-		</dependency>
-
-		<dependency>
-			<groupId>org.mockito</groupId>
-			<artifactId>mockito-all</artifactId>
-			<scope>test</scope>
-		</dependency>
-
-		<dependency>
-			<groupId>org.apache.ant</groupId>
-			<artifactId>ant</artifactId>
-		</dependency>
+        <!-- Compile Dependencies -->
         <dependency>
             <groupId>org.apache.ant</groupId>
-            <artifactId>ant-testutil</artifactId>
-            <version>1.9.4</version>
-            <scope>test</scope>
+            <artifactId>ant</artifactId>
+            <scope>compile</scope>
         </dependency>
-
 		<dependency>
 			<groupId>commons-collections</groupId>
 			<artifactId>commons-collections</artifactId>
+            <scope>compile</scope>
 		</dependency>
-
 		<dependency>
 			<groupId>commons-logging</groupId>
 			<artifactId>commons-logging</artifactId>
+            <scope>compile</scope>
 		</dependency>
-
 		<dependency>
 			<groupId>org.apache.velocity</groupId>
 			<artifactId>velocity</artifactId>
-		</dependency>		
-
+            <scope>compile</scope>
+		</dependency>
 		<dependency>
 			<groupId>org.apache.cayenne</groupId>
 			<artifactId>cayenne-server</artifactId>
 			<version>${project.version}</version>
+            <scope>compile</scope>
 		</dependency>
-
 		<dependency>
 			<groupId>org.apache.cayenne</groupId>
 			<artifactId>cayenne-project</artifactId>
 			<version>${project.version}</version>
+            <scope>compile</scope>
 		</dependency>
-
         <dependency>
             <groupId>org.apache.cayenne</groupId>
             <artifactId>cayenne-dbsync</artifactId>
             <version>${project.version}</version>
             <scope>compile</scope>
         </dependency>
-
 		<dependency>
 			<groupId>foundrylogic.vpp</groupId>
 			<artifactId>vpp</artifactId>
+            <scope>compile</scope>
 		</dependency>
 
+        <!-- Test Dependencies -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cayenne</groupId>
+            <artifactId>cayenne-server</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+            <type>test-jar</type>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cayenne</groupId>
+            <artifactId>cayenne-dbsync</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+            <type>test-jar</type>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cayenne.build-tools</groupId>
+            <artifactId>cayenne-test-utilities</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.ant</groupId>
+            <artifactId>ant-testutil</artifactId>
+            <version>1.9.4</version>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.apache.derby</groupId>
             <artifactId>derby</artifactId>
@@ -128,7 +143,7 @@
 	
 	<build>
 		<plugins>
-			<!-- This ensures LICESNE and NOTICE inclusion in all jars -->
+			<!-- This ensures LICENSE and NOTICE inclusion in all jars -->
             <plugin>
                 <artifactId>maven-remote-resources-plugin</artifactId>
                 <executions>


[08/10] cayenne git commit: CAY-2166 Auto-loading of Cayenne modules

Posted by aa...@apache.org.
CAY-2166 Auto-loading of Cayenne modules

* auto-loading CryptoModule
* refactoring CryptoModuleBuilder to be a provider of extensions, with defaults coming from CryptoModule


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/374aab3d
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/374aab3d
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/374aab3d

Branch: refs/heads/master
Commit: 374aab3d9c68f9f5fc5b07dcc485f8a9573bb5ab
Parents: 08fc9f4
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Sun Dec 11 17:05:33 2016 +0300
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Sun Dec 11 20:39:52 2016 +0300

----------------------------------------------------------------------
 .../org/apache/cayenne/crypto/CryptoModule.java | 177 ++++++++++++
 .../cayenne/crypto/CryptoModuleBuilder.java     | 275 ++++++-------------
 2 files changed, 254 insertions(+), 198 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/374aab3d/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModule.java
----------------------------------------------------------------------
diff --git a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModule.java b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModule.java
new file mode 100644
index 0000000..382c9aa
--- /dev/null
+++ b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModule.java
@@ -0,0 +1,177 @@
+/*
+ *    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.cayenne.crypto;
+
+import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
+import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
+import org.apache.cayenne.crypto.batch.CryptoBatchTranslatorFactoryDecorator;
+import org.apache.cayenne.crypto.cipher.CipherFactory;
+import org.apache.cayenne.crypto.cipher.DefaultCipherFactory;
+import org.apache.cayenne.crypto.key.JceksKeySource;
+import org.apache.cayenne.crypto.key.KeySource;
+import org.apache.cayenne.crypto.map.ColumnMapper;
+import org.apache.cayenne.crypto.map.PatternColumnMapper;
+import org.apache.cayenne.crypto.reader.CryptoRowReaderFactoryDecorator;
+import org.apache.cayenne.crypto.transformer.DefaultTransformerFactory;
+import org.apache.cayenne.crypto.transformer.TransformerFactory;
+import org.apache.cayenne.crypto.transformer.bytes.BytesTransformerFactory;
+import org.apache.cayenne.crypto.transformer.bytes.DefaultBytesTransformerFactory;
+import org.apache.cayenne.crypto.transformer.bytes.LazyBytesTransformerFactory;
+import org.apache.cayenne.crypto.transformer.value.Base64StringConverter;
+import org.apache.cayenne.crypto.transformer.value.BigDecimalConverter;
+import org.apache.cayenne.crypto.transformer.value.BigIntegerConverter;
+import org.apache.cayenne.crypto.transformer.value.BooleanConverter;
+import org.apache.cayenne.crypto.transformer.value.ByteConverter;
+import org.apache.cayenne.crypto.transformer.value.BytesConverter;
+import org.apache.cayenne.crypto.transformer.value.BytesToBytesConverter;
+import org.apache.cayenne.crypto.transformer.value.DefaultValueTransformerFactory;
+import org.apache.cayenne.crypto.transformer.value.DoubleConverter;
+import org.apache.cayenne.crypto.transformer.value.FloatConverter;
+import org.apache.cayenne.crypto.transformer.value.IntegerConverter;
+import org.apache.cayenne.crypto.transformer.value.LazyValueTransformerFactory;
+import org.apache.cayenne.crypto.transformer.value.LongConverter;
+import org.apache.cayenne.crypto.transformer.value.ShortConverter;
+import org.apache.cayenne.crypto.transformer.value.Utf8StringConverter;
+import org.apache.cayenne.crypto.transformer.value.UtilDateConverter;
+import org.apache.cayenne.crypto.transformer.value.ValueTransformerFactory;
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.MapBuilder;
+import org.apache.cayenne.di.Module;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.sql.Types;
+import java.util.Date;
+
+/**
+ * Contains cryptography extensions for Cayenne.
+ *
+ * @since 4.0
+ */
+public class CryptoModule implements Module {
+
+    private static final String DEFAULT_CIPHER_ALGORITHM = "AES";
+    private static final String DEFAULT_CIPHER_MODE = "CBC";
+    private static final String DEFAULT_CIPHER_PADDING = "PKCS5Padding";
+    // same as default keystore password in java...
+    private static final char[] DEFAULT_KEY_PASSWORD = "changeit".toCharArray();
+    private static final String DEFAULT_COLUMN_MAPPER_PATTERN = "^CRYPTO_";
+
+    public static CryptoModuleBuilder builder() {
+        return new CryptoModuleBuilder();
+    }
+
+    public static MapBuilder<String> contributeProperties(Binder binder) {
+        return binder.bindMap(CryptoConstants.PROPERTIES_MAP);
+    }
+
+    public static MapBuilder<char[]> contributeCredentials(Binder binder) {
+        return binder.bindMap(CryptoConstants.CREDENTIALS_MAP);
+    }
+
+    public static MapBuilder<BytesConverter<?>> contributeDbToByteConverters(Binder binder) {
+        return binder.bindMap(DefaultValueTransformerFactory.DB_TO_BYTE_CONVERTERS_KEY);
+    }
+
+    public static MapBuilder<BytesConverter<?>> contributeObjectToByteConverters(Binder binder) {
+        return binder.bindMap(DefaultValueTransformerFactory.OBJECT_TO_BYTE_CONVERTERS_KEY);
+    }
+
+    @Override
+    public void configure(Binder binder) {
+
+        MapBuilder<String> props = contributeProperties(binder)
+                .put(CryptoConstants.CIPHER_ALGORITHM, DEFAULT_CIPHER_ALGORITHM)
+                .put(CryptoConstants.CIPHER_MODE, DEFAULT_CIPHER_MODE)
+                .put(CryptoConstants.CIPHER_PADDING, DEFAULT_CIPHER_PADDING);
+
+        // credentials are stored as char[] to potentially allow wiping them clean in memory...
+        contributeCredentials(binder).put(CryptoConstants.KEY_PASSWORD, DEFAULT_KEY_PASSWORD);
+
+        binder.bind(CipherFactory.class).to(DefaultCipherFactory.class);
+        binder.bind(TransformerFactory.class).to(DefaultTransformerFactory.class);
+        binder.bind(ValueTransformerFactory.class).to(DefaultValueTransformerFactory.class);
+
+        MapBuilder<BytesConverter<?>> dbToBytesBinder = contributeDbToByteConverters(binder);
+        contributeDefaultDbConverters(dbToBytesBinder);
+
+        MapBuilder<BytesConverter<?>> objectToBytesBinder = contributeObjectToByteConverters(binder);
+        contributeDefaultObjectConverters(objectToBytesBinder);
+
+        binder.bind(BytesTransformerFactory.class).to(DefaultBytesTransformerFactory.class);
+        binder.bind(KeySource.class).to(JceksKeySource.class);
+        binder.bind(ColumnMapper.class).toInstance(new PatternColumnMapper(DEFAULT_COLUMN_MAPPER_PATTERN));
+
+        binder.decorate(BatchTranslatorFactory.class).before(CryptoBatchTranslatorFactoryDecorator.class);
+        binder.decorate(RowReaderFactory.class).before(CryptoRowReaderFactoryDecorator.class);
+
+        // decorate Crypto's own services to allow Cayenne to operate over plaintext entities even if crypto keys are
+        // not available.
+        binder.decorate(ValueTransformerFactory.class).after(LazyValueTransformerFactory.class);
+        binder.decorate(BytesTransformerFactory.class).after(LazyBytesTransformerFactory.class);
+    }
+
+    private static void contributeDefaultDbConverters(MapBuilder<BytesConverter<?>> mapBuilder) {
+
+        mapBuilder.put(String.valueOf(Types.BINARY), BytesToBytesConverter.INSTANCE);
+        mapBuilder.put(String.valueOf(Types.BLOB), BytesToBytesConverter.INSTANCE);
+        mapBuilder.put(String.valueOf(Types.VARBINARY), BytesToBytesConverter.INSTANCE);
+        mapBuilder.put(String.valueOf(Types.LONGVARBINARY), BytesToBytesConverter.INSTANCE);
+
+        mapBuilder.put(String.valueOf(Types.CHAR), Base64StringConverter.INSTANCE);
+        mapBuilder.put(String.valueOf(Types.NCHAR), Base64StringConverter.INSTANCE);
+        mapBuilder.put(String.valueOf(Types.CLOB), Base64StringConverter.INSTANCE);
+        mapBuilder.put(String.valueOf(Types.NCLOB), Base64StringConverter.INSTANCE);
+        mapBuilder.put(String.valueOf(Types.LONGVARCHAR), Base64StringConverter.INSTANCE);
+        mapBuilder.put(String.valueOf(Types.LONGNVARCHAR), Base64StringConverter.INSTANCE);
+        mapBuilder.put(String.valueOf(Types.VARCHAR), Base64StringConverter.INSTANCE);
+        mapBuilder.put(String.valueOf(Types.NVARCHAR), Base64StringConverter.INSTANCE);
+    }
+
+    private static void contributeDefaultObjectConverters(MapBuilder<BytesConverter<?>> mapBuilder) {
+
+        mapBuilder.put("byte[]", BytesToBytesConverter.INSTANCE);
+        mapBuilder.put(String.class.getName(), Utf8StringConverter.INSTANCE);
+
+        mapBuilder.put(Double.class.getName(), DoubleConverter.INSTANCE);
+        mapBuilder.put(Double.TYPE.getName(), DoubleConverter.INSTANCE);
+
+        mapBuilder.put(Float.class.getName(), FloatConverter.INSTANCE);
+        mapBuilder.put(Float.TYPE.getName(), FloatConverter.INSTANCE);
+
+        mapBuilder.put(Long.class.getName(), LongConverter.INSTANCE);
+        mapBuilder.put(Long.TYPE.getName(), LongConverter.INSTANCE);
+
+        mapBuilder.put(Integer.class.getName(), IntegerConverter.INSTANCE);
+        mapBuilder.put(Integer.TYPE.getName(), IntegerConverter.INSTANCE);
+
+        mapBuilder.put(Short.class.getName(), ShortConverter.INSTANCE);
+        mapBuilder.put(Short.TYPE.getName(), ShortConverter.INSTANCE);
+
+        mapBuilder.put(Byte.class.getName(), ByteConverter.INSTANCE);
+        mapBuilder.put(Byte.TYPE.getName(), ByteConverter.INSTANCE);
+
+        mapBuilder.put(Boolean.class.getName(), BooleanConverter.INSTANCE);
+        mapBuilder.put(Boolean.TYPE.getName(), BooleanConverter.INSTANCE);
+
+        mapBuilder.put(Date.class.getName(), UtilDateConverter.INSTANCE);
+        mapBuilder.put(BigInteger.class.getName(), BigIntegerConverter.INSTANCE);
+        mapBuilder.put(BigDecimal.class.getName(), BigDecimalConverter.INSTANCE);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/374aab3d/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModuleBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModuleBuilder.java b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModuleBuilder.java
index 4162409..63adfe8 100644
--- a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModuleBuilder.java
+++ b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModuleBuilder.java
@@ -18,49 +18,20 @@
  ****************************************************************/
 package org.apache.cayenne.crypto;
 
-import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
-import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
-import org.apache.cayenne.crypto.batch.CryptoBatchTranslatorFactoryDecorator;
 import org.apache.cayenne.crypto.cipher.CipherFactory;
-import org.apache.cayenne.crypto.cipher.DefaultCipherFactory;
-import org.apache.cayenne.crypto.key.JceksKeySource;
 import org.apache.cayenne.crypto.key.KeySource;
 import org.apache.cayenne.crypto.map.ColumnMapper;
 import org.apache.cayenne.crypto.map.PatternColumnMapper;
-import org.apache.cayenne.crypto.reader.CryptoRowReaderFactoryDecorator;
-import org.apache.cayenne.crypto.transformer.DefaultTransformerFactory;
-import org.apache.cayenne.crypto.transformer.TransformerFactory;
 import org.apache.cayenne.crypto.transformer.bytes.BytesTransformerFactory;
-import org.apache.cayenne.crypto.transformer.bytes.DefaultBytesTransformerFactory;
-import org.apache.cayenne.crypto.transformer.bytes.LazyBytesTransformerFactory;
-import org.apache.cayenne.crypto.transformer.value.Base64StringConverter;
-import org.apache.cayenne.crypto.transformer.value.BigDecimalConverter;
-import org.apache.cayenne.crypto.transformer.value.BigIntegerConverter;
-import org.apache.cayenne.crypto.transformer.value.BooleanConverter;
-import org.apache.cayenne.crypto.transformer.value.ByteConverter;
 import org.apache.cayenne.crypto.transformer.value.BytesConverter;
-import org.apache.cayenne.crypto.transformer.value.BytesToBytesConverter;
-import org.apache.cayenne.crypto.transformer.value.DefaultValueTransformerFactory;
-import org.apache.cayenne.crypto.transformer.value.DoubleConverter;
-import org.apache.cayenne.crypto.transformer.value.FloatConverter;
-import org.apache.cayenne.crypto.transformer.value.IntegerConverter;
-import org.apache.cayenne.crypto.transformer.value.LazyValueTransformerFactory;
-import org.apache.cayenne.crypto.transformer.value.LongConverter;
-import org.apache.cayenne.crypto.transformer.value.ShortConverter;
-import org.apache.cayenne.crypto.transformer.value.Utf8StringConverter;
-import org.apache.cayenne.crypto.transformer.value.UtilDateConverter;
 import org.apache.cayenne.crypto.transformer.value.ValueTransformerFactory;
 import org.apache.cayenne.di.Binder;
 import org.apache.cayenne.di.MapBuilder;
 import org.apache.cayenne.di.Module;
 
 import java.io.File;
-import java.math.BigDecimal;
-import java.math.BigInteger;
 import java.net.MalformedURLException;
 import java.net.URL;
-import java.sql.Types;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
@@ -70,15 +41,11 @@ import java.util.Objects;
  * runtime needed to enable encryption of certain data columns. Builder allows
  * to specify custom ciphers, as well as a strategy for discovering which
  * columns are encrypted.
- * 
+ *
  * @since 4.0
  */
 public class CryptoModuleBuilder {
 
-    private static final String DEFAULT_CIPHER_ALGORITHM = "AES";
-    private static final String DEFAULT_CIPHER_MODE = "CBC";
-    private static final String DEFAULT_CIPHER_PADDING = "PKCS5Padding";
-
     private Class<? extends ValueTransformerFactory> valueTransformerFactoryType;
     private Class<? extends BytesTransformerFactory> bytesTransformerFactoryType;
 
@@ -91,7 +58,6 @@ public class CryptoModuleBuilder {
 
     private String cipherAlgoritm;
     private String cipherMode;
-    private String cipherPadding;
     private Class<? extends CipherFactory> cipherFactoryType;
 
     private URL keyStoreUrl;
@@ -105,71 +71,50 @@ public class CryptoModuleBuilder {
 
     private boolean compress;
 
-    public static CryptoModuleBuilder builder() {
-        return new CryptoModuleBuilder();
-    }
-
-    public CryptoModuleBuilder() {
-
-        // init some sensible defaults that work in JVM without extra
-        // packages...
-        this.cipherAlgoritm = DEFAULT_CIPHER_ALGORITHM;
-        this.cipherMode = DEFAULT_CIPHER_MODE;
-        this.cipherPadding = DEFAULT_CIPHER_PADDING;
-
-        this.cipherFactoryType = DefaultCipherFactory.class;
-        this.keySourceType = JceksKeySource.class;
-
-        this.columnMapperPattern = "^CRYPTO_";
-
-        this.valueTransformerFactoryType = DefaultValueTransformerFactory.class;
-        this.bytesTransformerFactoryType = DefaultBytesTransformerFactory.class;
+    // use CryptoModule.builder() to create the builder...
+    protected CryptoModuleBuilder() {
+        this.extraDbToBytes = new HashMap<>();
+        this.extraObjectToBytes = new HashMap<>();
     }
 
     public CryptoModuleBuilder cipherAlgorithm(String algorithm) {
-        this.cipherAlgoritm = algorithm;
+        this.cipherAlgoritm = Objects.requireNonNull(algorithm);
         return this;
     }
 
     public CryptoModuleBuilder cipherMode(String mode) {
-        this.cipherMode = mode;
+        this.cipherMode = Objects.requireNonNull(mode);
         return this;
     }
 
     public CryptoModuleBuilder cipherFactory(Class<? extends CipherFactory> factoryType) {
-        this.cipherFactoryType = factoryType;
+        this.cipherFactoryType = Objects.requireNonNull(factoryType);
         return this;
     }
 
     public CryptoModuleBuilder valueTransformer(Class<? extends ValueTransformerFactory> factoryType) {
-        this.valueTransformerFactoryType = factoryType;
+        this.valueTransformerFactoryType = Objects.requireNonNull(factoryType);
         return this;
     }
 
     public <T> CryptoModuleBuilder objectToBytesConverter(Class<T> objectType, BytesConverter<T> converter) {
-        if (extraObjectToBytes == null) {
-            extraObjectToBytes = new HashMap<>();
-        }
         extraObjectToBytes.put(objectType.getName(), Objects.requireNonNull(converter));
         return this;
     }
 
     public CryptoModuleBuilder dbToBytesConverter(int sqlType, BytesConverter<?> converter) {
-        if (extraDbToBytes == null) {
-            extraDbToBytes = new HashMap<>();
-        }
         extraDbToBytes.put(sqlType, Objects.requireNonNull(converter));
         return this;
     }
 
     public CryptoModuleBuilder bytesTransformer(Class<? extends BytesTransformerFactory> factoryType) {
-        this.bytesTransformerFactoryType = factoryType;
+        this.bytesTransformerFactoryType = Objects.requireNonNull(factoryType);
         return this;
     }
 
     public CryptoModuleBuilder columnMapper(Class<? extends ColumnMapper> columnMapperType) {
         this.columnMapperPattern = null;
-        this.columnMapperType = columnMapperType;
+        this.columnMapperType = Objects.requireNonNull(columnMapperType);
         this.columnMapper = null;
         return this;
     }
@@ -177,24 +122,23 @@ public class CryptoModuleBuilder {
     public CryptoModuleBuilder columnMapper(ColumnMapper columnMapper) {
         this.columnMapperPattern = null;
         this.columnMapperType = null;
-        this.columnMapper = columnMapper;
+        this.columnMapper = Objects.requireNonNull(columnMapper);
         return this;
     }
 
     public CryptoModuleBuilder columnMapper(String pattern) {
-        this.columnMapperPattern = pattern;
+        this.columnMapperPattern = Objects.requireNonNull(pattern);
         this.columnMapperType = null;
         this.columnMapper = null;
         return this;
     }
 
     /**
-     * @param encryptionKeyAlias
-     *            The name of the key in the keystore that should be used for
-     *            encryption by default.
+     * @param encryptionKeyAlias The name of the key in the keystore that should be used for
+     *                           encryption by default.
      */
     public CryptoModuleBuilder encryptionKeyAlias(String encryptionKeyAlias) {
-        this.encryptionKeyAlias = encryptionKeyAlias;
+        this.encryptionKeyAlias = Objects.requireNonNull(encryptionKeyAlias);
         return this;
     }
 
@@ -202,21 +146,18 @@ public class CryptoModuleBuilder {
      * Configures keystore parameters. The KeyStore must be of "jceks" type and
      * contain all needed secret keys for the target database. Currently all
      * keys must be protected with the same password.
-     * 
-     * @param file
-     *            A file to load keystore from.
-     * @param passwordForAllKeys
-     *            A password that unlocks all keys in the keystore.
-     * @param encryptionKeyAlias
-     *            The name of the key in the keystore that should be used for
-     *            encryption by default.
+     *
+     * @param file               A file to load keystore from.
+     * @param passwordForAllKeys A password that unlocks all keys in the keystore.
+     * @param encryptionKeyAlias The name of the key in the keystore that should be used for
+     *                           encryption by default.
      */
     public CryptoModuleBuilder keyStore(File file, char[] passwordForAllKeys, String encryptionKeyAlias) {
         this.encryptionKeyAlias = encryptionKeyAlias;
         this.keyPassword = passwordForAllKeys;
         this.keyStoreUrl = null;
         this.keyStoreUrlString = null;
-        this.keyStoreFile = file;
+        this.keyStoreFile = Objects.requireNonNull(file);
         return this;
     }
 
@@ -224,20 +165,17 @@ public class CryptoModuleBuilder {
      * Configures keystore parameters. The KeyStore must be of "jceks" type and
      * contain all needed secret keys for the target database. Currently all
      * keys must be protected with the same password.
-     * 
-     * @param url
-     *            A URL to load keystore from.
-     * @param passwordForAllKeys
-     *            A password that unlocks all keys in the keystore.
-     * @param encryptionKeyAlias
-     *            The name of the key in the keystore that should be used for
-     *            encryption by default.
+     *
+     * @param url                A URL to load keystore from.
+     * @param passwordForAllKeys A password that unlocks all keys in the keystore.
+     * @param encryptionKeyAlias The name of the key in the keystore that should be used for
+     *                           encryption by default.
      */
     public CryptoModuleBuilder keyStore(String url, char[] passwordForAllKeys, String encryptionKeyAlias) {
         this.encryptionKeyAlias = encryptionKeyAlias;
         this.keyPassword = passwordForAllKeys;
         this.keyStoreUrl = null;
-        this.keyStoreUrlString = url;
+        this.keyStoreUrlString = Objects.requireNonNull(url);
         this.keyStoreFile = null;
         return this;
     }
@@ -246,33 +184,30 @@ public class CryptoModuleBuilder {
      * Configures keystore parameters. The KeyStore must be of "jceks" type and
      * contain all needed secret keys for the target database. Currently all
      * keys must be protected with the same password.
-     * 
-     * @param url
-     *            A URL to load keystore from.
-     * @param passwordForAllKeys
-     *            A password that unlocks all keys in the keystore.
-     * @param encryptionKeyAlias
-     *            The name of the key in the keystore that should be used for
-     *            encryption by default.
+     *
+     * @param url                A URL to load keystore from.
+     * @param passwordForAllKeys A password that unlocks all keys in the keystore.
+     * @param encryptionKeyAlias The name of the key in the keystore that should be used for
+     *                           encryption by default.
      */
     public CryptoModuleBuilder keyStore(URL url, char[] passwordForAllKeys, String encryptionKeyAlias) {
         this.encryptionKeyAlias = encryptionKeyAlias;
         this.keyPassword = passwordForAllKeys;
-        this.keyStoreUrl = url;
+        this.keyStoreUrl = Objects.requireNonNull(url);
         this.keyStoreUrlString = null;
         this.keyStoreFile = null;
         return this;
     }
 
     public CryptoModuleBuilder keySource(Class<? extends KeySource> type) {
-        this.keySourceType = type;
+        this.keySourceType = Objects.requireNonNull(type);
         this.keySource = null;
         return this;
     }
 
     public CryptoModuleBuilder keySource(KeySource keySource) {
         this.keySourceType = null;
-        this.keySource = keySource;
+        this.keySource = Objects.requireNonNull(keySource);
         return this;
     }
 
@@ -286,42 +221,22 @@ public class CryptoModuleBuilder {
      */
     public Module build() {
 
-        if (valueTransformerFactoryType == null) {
-            throw new IllegalStateException("'ValueTransformerFactory' is not initialized");
-        }
-
-        if (columnMapperType == null && columnMapper == null && columnMapperPattern == null) {
-            throw new IllegalStateException("'ColumnMapper' is not initialized");
-        }
-
-        if (cipherFactoryType == null) {
-            throw new IllegalStateException("'CipherFactory' is not initialized");
-        }
-
         return new Module() {
 
             @Override
             public void configure(Binder binder) {
 
-                String keyStoreUrl = null;
-                if (CryptoModuleBuilder.this.keyStoreUrl != null) {
-                    keyStoreUrl = CryptoModuleBuilder.this.keyStoreUrl.toExternalForm();
-                } else if (CryptoModuleBuilder.this.keyStoreUrlString != null) {
-                    keyStoreUrl = CryptoModuleBuilder.this.keyStoreUrlString;
-                } else if (keyStoreFile != null) {
-                    try {
-                        keyStoreUrl = keyStoreFile.toURI().toURL().toExternalForm();
-                    } catch (MalformedURLException e) {
-                        throw new IllegalStateException("Invalid keyStore file", e);
-                    }
+                MapBuilder<String> props = CryptoModule.contributeProperties(binder);
+
+                if (cipherAlgoritm != null) {
+                    props.put(CryptoConstants.CIPHER_ALGORITHM, cipherAlgoritm);
                 }
 
-                // String properties
-                MapBuilder<String> props = binder.<String> bindMap(CryptoConstants.PROPERTIES_MAP)
-                        .put(CryptoConstants.CIPHER_ALGORITHM, cipherAlgoritm)
-                        .put(CryptoConstants.CIPHER_MODE, cipherMode)
-                        .put(CryptoConstants.CIPHER_PADDING, cipherPadding);
+                if (cipherMode != null) {
+                    props.put(CryptoConstants.CIPHER_MODE, cipherMode);
+                }
 
+                String keyStoreUrl = keyStoreUrl();
                 if (keyStoreUrl != null) {
                     props.put(CryptoConstants.KEYSTORE_URL, keyStoreUrl);
                 }
@@ -334,41 +249,39 @@ public class CryptoModuleBuilder {
                     props.put(CryptoConstants.COMPRESSION, "true");
                 }
 
-                // char[] credentials... stored as char[] to potentially allow
-                // wiping them clean in memory...
-                MapBuilder<char[]> creds = binder.<char[]> bindMap(CryptoConstants.CREDENTIALS_MAP);
-
                 if (keyPassword != null) {
-                    creds.put(CryptoConstants.KEY_PASSWORD, keyPassword);
+                    CryptoModule.contributeCredentials(binder).put(CryptoConstants.KEY_PASSWORD, keyPassword);
                 }
 
-                binder.bind(CipherFactory.class).to(cipherFactoryType);
-                binder.bind(TransformerFactory.class).to(DefaultTransformerFactory.class);
-                binder.bind(ValueTransformerFactory.class).to(valueTransformerFactoryType);
+                if (cipherFactoryType != null) {
+                    binder.bind(CipherFactory.class).to(cipherFactoryType);
+                }
 
-                MapBuilder<BytesConverter<?>> dbToBytesBinder =
-                        binder.bindMap(DefaultValueTransformerFactory.DB_TO_BYTE_CONVERTERS_KEY);
-                contributeDefaultDbConverters(dbToBytesBinder);
-                if (extraDbToBytes != null) {
+                if (valueTransformerFactoryType != null) {
+                    binder.bind(ValueTransformerFactory.class).to(valueTransformerFactoryType);
+                }
+
+                if (!extraDbToBytes.isEmpty()) {
+                    MapBuilder<BytesConverter<?>> dbToBytesBinder = CryptoModule.contributeDbToByteConverters(binder);
                     for (Map.Entry<Integer, BytesConverter<?>> extraConverter : extraDbToBytes.entrySet()) {
                         dbToBytesBinder.put(extraConverter.getKey().toString(), extraConverter.getValue());
                     }
                 }
 
-                MapBuilder<BytesConverter<?>> objectToBytesBinder =
-                        binder.bindMap(DefaultValueTransformerFactory.OBJECT_TO_BYTE_CONVERTERS_KEY);
-                contributeDefaultObjectConverters(objectToBytesBinder);
-                if (extraObjectToBytes != null) {
+                if (!extraObjectToBytes.isEmpty()) {
+                    MapBuilder<BytesConverter<?>> objectToBytesBinder = CryptoModule.contributeObjectToByteConverters(binder);
                     for (Map.Entry<String, BytesConverter<?>> extraConverter : extraObjectToBytes.entrySet()) {
                         objectToBytesBinder.put(extraConverter.getKey(), extraConverter.getValue());
                     }
                 }
 
-                binder.bind(BytesTransformerFactory.class).to(bytesTransformerFactoryType);
+                if (bytesTransformerFactoryType != null) {
+                    binder.bind(BytesTransformerFactory.class).to(bytesTransformerFactoryType);
+                }
 
                 if (keySource != null) {
                     binder.bind(KeySource.class).toInstance(keySource);
-                } else {
+                } else if (keySourceType != null) {
                     binder.bind(KeySource.class).to(keySourceType);
                 }
 
@@ -376,66 +289,32 @@ public class CryptoModuleBuilder {
                     binder.bind(ColumnMapper.class).toInstance(new PatternColumnMapper(columnMapperPattern));
                 } else if (columnMapperType != null) {
                     binder.bind(ColumnMapper.class).to(columnMapperType);
-                } else {
+                } else if (columnMapper != null) {
                     binder.bind(ColumnMapper.class).toInstance(columnMapper);
                 }
-
-                binder.decorate(BatchTranslatorFactory.class).before(CryptoBatchTranslatorFactoryDecorator.class);
-                binder.decorate(RowReaderFactory.class).before(CryptoRowReaderFactoryDecorator.class);
-
-                // decorate our own services to allow Cayenne to operate over plaintext entities
-                // even if crypto keys are not available.
-                binder.decorate(ValueTransformerFactory.class).after(LazyValueTransformerFactory.class);
-                binder.decorate(BytesTransformerFactory.class).after(LazyBytesTransformerFactory.class);
             }
         };
     }
 
-    private static void contributeDefaultDbConverters(MapBuilder<BytesConverter<?>> mapBuilder) {
-
-        mapBuilder.put(String.valueOf(Types.BINARY), BytesToBytesConverter.INSTANCE);
-        mapBuilder.put(String.valueOf(Types.BLOB), BytesToBytesConverter.INSTANCE);
-        mapBuilder.put(String.valueOf(Types.VARBINARY), BytesToBytesConverter.INSTANCE);
-        mapBuilder.put(String.valueOf(Types.LONGVARBINARY), BytesToBytesConverter.INSTANCE);
-
-        mapBuilder.put(String.valueOf(Types.CHAR), Base64StringConverter.INSTANCE);
-        mapBuilder.put(String.valueOf(Types.NCHAR), Base64StringConverter.INSTANCE);
-        mapBuilder.put(String.valueOf(Types.CLOB), Base64StringConverter.INSTANCE);
-        mapBuilder.put(String.valueOf(Types.NCLOB), Base64StringConverter.INSTANCE);
-        mapBuilder.put(String.valueOf(Types.LONGVARCHAR), Base64StringConverter.INSTANCE);
-        mapBuilder.put(String.valueOf(Types.LONGNVARCHAR), Base64StringConverter.INSTANCE);
-        mapBuilder.put(String.valueOf(Types.VARCHAR), Base64StringConverter.INSTANCE);
-        mapBuilder.put(String.valueOf(Types.NVARCHAR), Base64StringConverter.INSTANCE);
-    }
-
-    private static void contributeDefaultObjectConverters(MapBuilder<BytesConverter<?>> mapBuilder) {
-
-        mapBuilder.put("byte[]", BytesToBytesConverter.INSTANCE);
-        mapBuilder.put(String.class.getName(), Utf8StringConverter.INSTANCE);
-
-        mapBuilder.put(Double.class.getName(), DoubleConverter.INSTANCE);
-        mapBuilder.put(Double.TYPE.getName(), DoubleConverter.INSTANCE);
-
-        mapBuilder.put(Float.class.getName(), FloatConverter.INSTANCE);
-        mapBuilder.put(Float.TYPE.getName(), FloatConverter.INSTANCE);
-
-        mapBuilder.put(Long.class.getName(), LongConverter.INSTANCE);
-        mapBuilder.put(Long.TYPE.getName(), LongConverter.INSTANCE);
+    protected String keyStoreUrl() {
+        if (this.keyStoreUrl != null) {
+            return this.keyStoreUrl.toExternalForm();
+        }
 
-        mapBuilder.put(Integer.class.getName(), IntegerConverter.INSTANCE);
-        mapBuilder.put(Integer.TYPE.getName(), IntegerConverter.INSTANCE);
+        if (this.keyStoreUrlString != null) {
+            return this.keyStoreUrlString;
+        }
 
-        mapBuilder.put(Short.class.getName(), ShortConverter.INSTANCE);
-        mapBuilder.put(Short.TYPE.getName(), ShortConverter.INSTANCE);
+        if (keyStoreFile != null) {
+            try {
+                return keyStoreFile.toURI().toURL().toExternalForm();
+            } catch (MalformedURLException e) {
+                throw new IllegalStateException("Invalid keyStore file", e);
+            }
+        }
 
-        mapBuilder.put(Byte.class.getName(), ByteConverter.INSTANCE);
-        mapBuilder.put(Byte.TYPE.getName(), ByteConverter.INSTANCE);
+        return null;
+    }
 
-        mapBuilder.put(Boolean.class.getName(), BooleanConverter.INSTANCE);
-        mapBuilder.put(Boolean.TYPE.getName(), BooleanConverter.INSTANCE);
 
-        mapBuilder.put(Date.class.getName(), UtilDateConverter.INSTANCE);
-        mapBuilder.put(BigInteger.class.getName(), BigIntegerConverter.INSTANCE);
-        mapBuilder.put(BigDecimal.class.getName(), BigDecimalConverter.INSTANCE);
-    }
 }


[03/10] cayenne git commit: CAY-2166 Auto-loading of Cayenne modules

Posted by aa...@apache.org.
CAY-2166 Auto-loading of Cayenne modules

* auto-loading


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/ba59e95e
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/ba59e95e
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/ba59e95e

Branch: refs/heads/master
Commit: ba59e95eaf440b1c3cc7c01ecc32dc88f1c5d86d
Parents: 3dc06bd
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Sun Dec 11 17:05:33 2016 +0300
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Sun Dec 11 19:19:51 2016 +0300

----------------------------------------------------------------------
 .../server/ServerModuleProvider.java            | 45 ++++++++++++++
 .../configuration/server/ServerRuntime.java     |  8 +--
 .../server/ServerRuntimeBuilder.java            | 37 +++++++++---
 .../org.apache.cayenne.di.spi.ModuleProvider    |  1 +
 .../server/ServerModuleProviderTest.java        | 30 ++++++++++
 .../server/ServerRuntimeBuilderTest.java        | 13 +---
 .../unit/util/ModuleProviderChecker.java        | 62 ++++++++++++++++++++
 docs/doc/src/main/resources/UPGRADE.txt         |  6 ++
 8 files changed, 179 insertions(+), 23 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/ba59e95e/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModuleProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModuleProvider.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModuleProvider.java
new file mode 100644
index 0000000..55d681d
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModuleProvider.java
@@ -0,0 +1,45 @@
+/*
+ *    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.cayenne.configuration.server;
+
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.di.spi.ModuleProvider;
+
+/**
+ * ServerModule auto-loading facility.
+ *
+ * @since 4.0
+ */
+public class ServerModuleProvider implements ModuleProvider {
+
+    @Override
+    public Module module() {
+        return new ServerModule();
+    }
+
+    @Override
+    public Class<? extends Module> moduleType() {
+        return ServerModule.class;
+    }
+
+    @Override
+    public Class<? extends Module>[] overrides() {
+        return new Class[0];
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ba59e95e/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntime.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntime.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntime.java
index d6029a2..8425dca 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntime.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntime.java
@@ -59,7 +59,7 @@ public class ServerRuntime extends CayenneRuntime {
     /**
      * Creates a builder of ServerRuntime.
      *
-     * @param name
+     * @param name optional symbolic name of the created runtime.
      * @return a named builder of ServerRuntime.
      */
     public static ServerRuntimeBuilder builder(String name) {
@@ -71,19 +71,19 @@ public class ServerRuntime extends CayenneRuntime {
         Collection<Module> modules = new ArrayList<>();
         modules.add(new ServerModule());
 
-        if(configurationLocations.length > 0) {
+        if (configurationLocations.length > 0) {
             modules.add(new Module() {
                 @Override
                 public void configure(Binder binder) {
                     ListBuilder<String> locationsBinder = ServerModule.contributeProjectLocations(binder);
-                    for(String c : configurationLocations) {
+                    for (String c : configurationLocations) {
                         locationsBinder.add(c);
                     }
                 }
             });
         }
 
-        if(extraModules != null) {
+        if (extraModules != null) {
             modules.addAll(asList(extraModules));
         }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ba59e95e/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilder.java
index e5e2ae2..19938b5 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilder.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilder.java
@@ -25,6 +25,7 @@ import org.apache.cayenne.di.Binder;
 import org.apache.cayenne.di.ListBuilder;
 import org.apache.cayenne.di.MapBuilder;
 import org.apache.cayenne.di.Module;
+import org.apache.cayenne.di.spi.ModuleLoader;
 
 import javax.sql.DataSource;
 import java.util.*;
@@ -72,7 +73,11 @@ public class ServerRuntimeBuilder {
 
     /**
      * Creates an empty builder.
+     *
+     * @deprecated since 4.0.M5 in favor of {@link ServerRuntime#builder()}
      */
+    @Deprecated
+    // TODO remove once we are comfortable with removal of the deprecated API
     public ServerRuntimeBuilder() {
         this(null);
     }
@@ -81,7 +86,11 @@ public class ServerRuntimeBuilder {
      * Creates a builder with a fixed name of the DataDomain of the resulting
      * ServerRuntime. Specifying explicit name is often needed for consistency
      * in runtimes merged from multiple configs, each having its own name.
+     *
+     * @deprecated since 4.0.M5 in favor of {@link ServerRuntime#builder(String)}
      */
+    @Deprecated
+    // TODO make private once we are comfortable with removal of the deprecated API
     public ServerRuntimeBuilder(String name) {
         this.configs = new LinkedHashSet<String>();
         this.modules = new ArrayList<Module>();
@@ -211,28 +220,38 @@ public class ServerRuntimeBuilder {
 
     public ServerRuntime build() {
 
-        Collection<Module> configModules = buildConfigModules();
-
         Collection<Module> allModules = new ArrayList<>();
-        // TODO: make ServerModule auto-loadable?
-        allModules.add(new ServerModule());
-        allModules.addAll(configModules);
-        // custom modules override config modules...
+
+        // first load default or auto-loaded modules...
+        allModules.addAll(autoLoadModules ? autoLoadedModules() : defaultModules());
+
+        // custom modules override default and auto-loaded modules...
         allModules.addAll(this.modules);
 
+        // builder modules override default, auto-loaded and custom modules...
+        allModules.addAll(builderModules());
+
         return new ServerRuntime(allModules);
     }
 
-    private Collection<Module> buildConfigModules() {
+    private Collection<? extends Module> autoLoadedModules() {
+        return new ModuleLoader().load();
+    }
+
+    private Collection<? extends Module> defaultModules() {
+        return Collections.singleton(new ServerModule());
+    }
+
+    private Collection<? extends Module> builderModules() {
 
         Collection<Module> modules = new ArrayList<>();
 
-        if(!configs.isEmpty()) {
+        if (!configs.isEmpty()) {
             modules.add(new Module() {
                 @Override
                 public void configure(Binder binder) {
                     ListBuilder<String> locationsBinder = ServerModule.contributeProjectLocations(binder);
-                    for(String c : configs) {
+                    for (String c : configs) {
                         locationsBinder.add(c);
                     }
                 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ba59e95e/cayenne-server/src/main/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider b/cayenne-server/src/main/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider
new file mode 100644
index 0000000..9e5f5bb
--- /dev/null
+++ b/cayenne-server/src/main/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider
@@ -0,0 +1 @@
+org.apache.cayenne.configuration.server.ServerModuleProvider
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ba59e95e/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerModuleProviderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerModuleProviderTest.java b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerModuleProviderTest.java
new file mode 100644
index 0000000..6f30ec0
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerModuleProviderTest.java
@@ -0,0 +1,30 @@
+/*
+ *    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.cayenne.configuration.server;
+
+import org.apache.cayenne.unit.util.ModuleProviderChecker;
+import org.junit.Test;
+
+public class ServerModuleProviderTest {
+
+    @Test
+    public void testProviderPresent() {
+        ModuleProviderChecker.testProviderPresent(ServerModuleProvider.class);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ba59e95e/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilderTest.java b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilderTest.java
index 2617476..95e7a44 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilderTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilderTest.java
@@ -26,7 +26,6 @@ import org.junit.Test;
 
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Iterator;
 import java.util.List;
 
 import static org.hamcrest.CoreMatchers.instanceOf;
@@ -104,15 +103,9 @@ public class ServerRuntimeBuilderTest {
 
 		Collection<Module> modules = runtime.getModules();
 		assertEquals(3, modules.size());
-
-		Iterator<Module> it = modules.iterator();
-
-		assertThat(it.next(), instanceOf(ServerModule.class));
-
-		// rewind - this module is name fix module
-		it.next();
-		
-		assertSame(m, it.next());
+		Module[] array = modules.toArray(new Module[3]);
+		assertThat(array[0], instanceOf(ServerModule.class));
+		assertSame(m, array[1]);
 	}
 
 	@Test

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ba59e95e/cayenne-server/src/test/java/org/apache/cayenne/unit/util/ModuleProviderChecker.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/unit/util/ModuleProviderChecker.java b/cayenne-server/src/test/java/org/apache/cayenne/unit/util/ModuleProviderChecker.java
new file mode 100644
index 0000000..43770a5
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/unit/util/ModuleProviderChecker.java
@@ -0,0 +1,62 @@
+/*
+ *    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.cayenne.unit.util;
+
+import org.apache.cayenne.di.spi.ModuleProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.ServiceLoader;
+
+import static org.junit.Assert.fail;
+
+public class ModuleProviderChecker {
+
+    private Class<? extends ModuleProvider> expectedProvider;
+
+    public static void testProviderPresent(Class<? extends ModuleProvider> expectedProvider) {
+        new ModuleProviderChecker(expectedProvider).testProviderPresent();
+    }
+
+    protected ModuleProviderChecker(Class<? extends ModuleProvider> expectedProvider) {
+        this.expectedProvider = Objects.requireNonNull(expectedProvider);
+    }
+
+    protected void testProviderPresent() {
+
+        List<ModuleProvider> providers = new ArrayList<>();
+        for (ModuleProvider p : ServiceLoader.load(ModuleProvider.class)) {
+            if (expectedProvider.equals(p.getClass())) {
+                providers.add(p);
+            }
+        }
+
+        switch (providers.size()) {
+            case 0:
+                fail("Expected provider '" + expectedProvider.getName() + "' is not found");
+                break;
+            case 1:
+                break;
+            default:
+                fail("Expected provider '" + expectedProvider.getName() + "' is found more then once: " + providers.size());
+                break;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ba59e95e/docs/doc/src/main/resources/UPGRADE.txt
----------------------------------------------------------------------
diff --git a/docs/doc/src/main/resources/UPGRADE.txt b/docs/doc/src/main/resources/UPGRADE.txt
index b352224..e0b9c39 100644
--- a/docs/doc/src/main/resources/UPGRADE.txt
+++ b/docs/doc/src/main/resources/UPGRADE.txt
@@ -6,6 +6,12 @@ IMPORTANT: be sure to read all notes for the intermediate releases between your
 -------------------------------------------------------------------------------
 UPGRADING TO 4.0.M5
 
+* Per CAY-2166, Cayenne supports auto-loading of DI modules. The part of this is a tweak in DI service override policies.
+  In the previous 4.0 releases custom modules would override "builder" modules (i.e. implicit modules that wrap around
+  various customizations made in response to the builder method calls). It seemed logical to reverse this order, and
+  let builder modules override custom modules. As the builder is invoked explicitly when the stack assembly is performed,
+  while modules can be written without any knowledge of the final stack.
+
 * Per CAY-2164, creating a ServerRuntimeBuilder is done via a static method on ServerRuntime ("ServerRuntime.builder()").
   The previous style (ServerRuntimeBuilder.builder()) is deprecated and will soon be removed, so you should replace it
   with the new API.


[09/10] cayenne git commit: CAY-2166 Auto-loading of Cayenne modules

Posted by aa...@apache.org.
CAY-2166 Auto-loading of Cayenne modules

* back to overrides as collection instead of Class[]


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/302ca65c
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/302ca65c
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/302ca65c

Branch: refs/heads/master
Commit: 302ca65c6cd31f00033517e9ddfae127c7bd0578
Parents: 374aab3
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Sun Dec 11 17:05:33 2016 +0300
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Sun Dec 11 20:50:15 2016 +0300

----------------------------------------------------------------------
 cayenne-crypto/pom.xml                          |  2 +-
 .../cayenne/crypto/CryptoModuleBuilder.java     |  5 +--
 .../cayenne/crypto/CryptoModuleProvider.java    | 47 ++++++++++++++++++++
 .../apache/cayenne/di/spi/ModuleProvider.java   |  4 +-
 .../apache/cayenne/di/spi/ModuleLoaderTest.java | 21 +++++----
 .../cayenne/java8/Java8ModuleProvider.java      |  7 ++-
 .../apache/cayenne/joda/JodaModuleProvider.java |  7 ++-
 .../server/ServerModuleProvider.java            |  7 ++-
 8 files changed, 80 insertions(+), 20 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/302ca65c/cayenne-crypto/pom.xml
----------------------------------------------------------------------
diff --git a/cayenne-crypto/pom.xml b/cayenne-crypto/pom.xml
index c1156ec..ea4e759 100644
--- a/cayenne-crypto/pom.xml
+++ b/cayenne-crypto/pom.xml
@@ -68,7 +68,7 @@
 	</dependencies>
 	<build>
 		<plugins>
-			<!-- This ensures LICESNE and NOTICE inclusion in all jars -->
+			<!-- This ensures LICENSE and NOTICE inclusion in all jars -->
             <plugin>
                 <artifactId>maven-remote-resources-plugin</artifactId>
                 <executions>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/302ca65c/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModuleBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModuleBuilder.java b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModuleBuilder.java
index 63adfe8..62a8af2 100644
--- a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModuleBuilder.java
+++ b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModuleBuilder.java
@@ -37,10 +37,7 @@ import java.util.Map;
 import java.util.Objects;
 
 /**
- * A builder of a Cayenne DI module that will contain all extension to Cayenne
- * runtime needed to enable encryption of certain data columns. Builder allows
- * to specify custom ciphers, as well as a strategy for discovering which
- * columns are encrypted.
+ * A builder that allows to customize {@link CryptoModule} module.
  *
  * @since 4.0
  */

http://git-wip-us.apache.org/repos/asf/cayenne/blob/302ca65c/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModuleProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModuleProvider.java b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModuleProvider.java
new file mode 100644
index 0000000..c65b4df
--- /dev/null
+++ b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModuleProvider.java
@@ -0,0 +1,47 @@
+/*
+ *    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.cayenne.crypto;
+
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.di.spi.ModuleProvider;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * @since 4.0
+ */
+public class CryptoModuleProvider implements ModuleProvider {
+
+    @Override
+    public Module module() {
+        return new CryptoModule();
+    }
+
+    @Override
+    public Class<? extends Module> moduleType() {
+        return CryptoModule.class;
+    }
+
+    @Override
+    public Collection<Class<? extends Module>> overrides() {
+        // we don't override anything, we only decorate ServerModule services...
+        return Collections.emptyList();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/302ca65c/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ModuleProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ModuleProvider.java b/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ModuleProvider.java
index e96322e..679b593 100644
--- a/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ModuleProvider.java
+++ b/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ModuleProvider.java
@@ -20,6 +20,8 @@ package org.apache.cayenne.di.spi;
 
 import org.apache.cayenne.di.Module;
 
+import java.util.Collection;
+
 /**
  * Provider of modules used by module auto-loading mechanism to identify and load modules.
  *
@@ -37,5 +39,5 @@ public interface ModuleProvider {
      *
      * @return a collection of module types this module overrides.
      */
-    Class<? extends Module>[] overrides();
+    Collection<Class<? extends Module>> overrides();
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/302ca65c/cayenne-di/src/test/java/org/apache/cayenne/di/spi/ModuleLoaderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-di/src/test/java/org/apache/cayenne/di/spi/ModuleLoaderTest.java b/cayenne-di/src/test/java/org/apache/cayenne/di/spi/ModuleLoaderTest.java
index 3456b5b..9443524 100644
--- a/cayenne-di/src/test/java/org/apache/cayenne/di/spi/ModuleLoaderTest.java
+++ b/cayenne-di/src/test/java/org/apache/cayenne/di/spi/ModuleLoaderTest.java
@@ -24,6 +24,8 @@ import org.apache.cayenne.di.Injector;
 import org.apache.cayenne.di.Module;
 import org.junit.Test;
 
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
 import static org.junit.Assert.assertEquals;
@@ -90,9 +92,11 @@ public class ModuleLoaderTest {
             return Module1.class;
         }
 
+
         @Override
-        public Class<? extends Module>[] overrides() {
-            return new Class[]{Module2.class};
+        public Collection<Class<? extends Module>> overrides() {
+            Collection c = Collections.singletonList(Module2.class);
+            return c;
         }
     }
 
@@ -109,8 +113,8 @@ public class ModuleLoaderTest {
         }
 
         @Override
-        public Class<? extends Module>[] overrides() {
-            return new Class[0];
+        public Collection<Class<? extends Module>> overrides() {
+            return Collections.emptyList();
         }
     }
 
@@ -127,8 +131,8 @@ public class ModuleLoaderTest {
         }
 
         @Override
-        public Class<? extends Module>[] overrides() {
-            return new Class[0];
+        public Collection<Class<? extends Module>> overrides() {
+            return Collections.emptyList();
         }
     }
 
@@ -145,8 +149,9 @@ public class ModuleLoaderTest {
         }
 
         @Override
-        public Class<? extends Module>[] overrides() {
-            return new Class[]{Module3.class};
+        public Collection<Class<? extends Module>> overrides() {
+            Collection c = Collections.singletonList(Module3.class);
+            return c;
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/302ca65c/cayenne-java8/src/main/java/org/apache/cayenne/java8/Java8ModuleProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-java8/src/main/java/org/apache/cayenne/java8/Java8ModuleProvider.java b/cayenne-java8/src/main/java/org/apache/cayenne/java8/Java8ModuleProvider.java
index 243dc73..f8fe1b6 100644
--- a/cayenne-java8/src/main/java/org/apache/cayenne/java8/Java8ModuleProvider.java
+++ b/cayenne-java8/src/main/java/org/apache/cayenne/java8/Java8ModuleProvider.java
@@ -21,6 +21,9 @@ package org.apache.cayenne.java8;
 import org.apache.cayenne.di.Module;
 import org.apache.cayenne.di.spi.ModuleProvider;
 
+import java.util.Collection;
+import java.util.Collections;
+
 /**
  * @since 4.0
  */
@@ -37,7 +40,7 @@ public class Java8ModuleProvider implements ModuleProvider {
     }
 
     @Override
-    public Class<? extends Module>[] overrides() {
-        return new Class[0];
+    public Collection<Class<? extends Module>> overrides() {
+        return Collections.emptyList();
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/302ca65c/cayenne-joda/src/main/java/org/apache/cayenne/joda/JodaModuleProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-joda/src/main/java/org/apache/cayenne/joda/JodaModuleProvider.java b/cayenne-joda/src/main/java/org/apache/cayenne/joda/JodaModuleProvider.java
index 9c9e204..d390937 100644
--- a/cayenne-joda/src/main/java/org/apache/cayenne/joda/JodaModuleProvider.java
+++ b/cayenne-joda/src/main/java/org/apache/cayenne/joda/JodaModuleProvider.java
@@ -21,6 +21,9 @@ package org.apache.cayenne.joda;
 import org.apache.cayenne.di.Module;
 import org.apache.cayenne.di.spi.ModuleProvider;
 
+import java.util.Collection;
+import java.util.Collections;
+
 /**
  * @since 4.0
  */
@@ -37,7 +40,7 @@ public class JodaModuleProvider implements ModuleProvider {
     }
 
     @Override
-    public Class<? extends Module>[] overrides() {
-        return new Class[0];
+    public Collection<Class<? extends Module>> overrides() {
+        return Collections.emptyList();
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/302ca65c/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModuleProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModuleProvider.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModuleProvider.java
index 55d681d..e8e126d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModuleProvider.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModuleProvider.java
@@ -21,6 +21,9 @@ package org.apache.cayenne.configuration.server;
 import org.apache.cayenne.di.Module;
 import org.apache.cayenne.di.spi.ModuleProvider;
 
+import java.util.Collection;
+import java.util.Collections;
+
 /**
  * ServerModule auto-loading facility.
  *
@@ -39,7 +42,7 @@ public class ServerModuleProvider implements ModuleProvider {
     }
 
     @Override
-    public Class<? extends Module>[] overrides() {
-        return new Class[0];
+    public Collection<Class<? extends Module>> overrides() {
+        return Collections.emptyList();
     }
 }


[02/10] cayenne git commit: CAY-2166 Auto-loading of Cayenne modules

Posted by aa...@apache.org.
CAY-2166 Auto-loading of Cayenne modules

* refactoring modules and runtimes to prepare for auto-loading...
  essentially making things more flexible and removing self-init
  code into builders


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/3dc06bd7
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/3dc06bd7
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/3dc06bd7

Branch: refs/heads/master
Commit: 3dc06bd78c39509fbab4a88791507d0ec66921a6
Parents: 2fd200b
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Sun Dec 11 17:05:33 2016 +0300
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Sun Dec 11 18:40:28 2016 +0300

----------------------------------------------------------------------
 .../rop/client/ClientLocalRuntime.java          |  59 +++--
 .../configuration/rop/client/ClientRuntime.java |  40 ++-
 .../rop/client/ClientLocalRuntimeTest.java      |   3 +-
 .../rop/client/ClientRuntimeTest.java           |   7 +-
 .../cayenne/crypto/Runtime_AES128_Base.java     |   2 +-
 .../value/DefaultValueTransformerFactoryIT.java |   2 +-
 .../cayenne/configuration/CayenneRuntime.java   | 242 +++++++++---------
 .../cayenne/configuration/ModuleCollection.java |  82 ------
 .../configuration/server/ServerModule.java      |  26 +-
 .../configuration/server/ServerRuntime.java     |  53 +++-
 .../server/ServerRuntimeBuilder.java            |  63 +++--
 .../rop/server/ROPServletTest.java              |  43 ++--
 .../server/ServerRuntimeBuilderTest.java        |  33 ++-
 .../configuration/server/ServerRuntimeTest.java | 247 +++++++++----------
 .../configuration/web/CayenneFilterTest.java    |  19 +-
 15 files changed, 473 insertions(+), 448 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/3dc06bd7/cayenne-client/src/main/java/org/apache/cayenne/configuration/rop/client/ClientLocalRuntime.java
----------------------------------------------------------------------
diff --git a/cayenne-client/src/main/java/org/apache/cayenne/configuration/rop/client/ClientLocalRuntime.java b/cayenne-client/src/main/java/org/apache/cayenne/configuration/rop/client/ClientLocalRuntime.java
index 81a731d..2e11a0d 100644
--- a/cayenne-client/src/main/java/org/apache/cayenne/configuration/rop/client/ClientLocalRuntime.java
+++ b/cayenne-client/src/main/java/org/apache/cayenne/configuration/rop/client/ClientLocalRuntime.java
@@ -18,44 +18,61 @@
  ****************************************************************/
 package org.apache.cayenne.configuration.rop.client;
 
-import java.util.Collection;
-import java.util.Map;
-
 import org.apache.cayenne.DataChannel;
-import org.apache.cayenne.configuration.ModuleCollection;
 import org.apache.cayenne.di.Binder;
 import org.apache.cayenne.di.Injector;
 import org.apache.cayenne.di.Key;
 import org.apache.cayenne.di.Module;
 import org.apache.cayenne.remote.ClientConnection;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import static java.util.Arrays.asList;
+
 /**
  * A {@link ClientRuntime} that provides an ROP stack based on a local
  * connection on top of a server stack.
- * 
+ *
  * @since 3.1
  */
+// TODO: module auto-loading and ClientLocalRuntimeBuilder
 public class ClientLocalRuntime extends ClientRuntime {
 
-	public static final String CLIENT_SERVER_CHANNEL_KEY = "client-server-channel";
+    public static final String CLIENT_SERVER_CHANNEL_KEY = "client-server-channel";
+
+    private static Collection<Module> collectModules(
+            Injector serverInjector,
+            Module... extraModules) {
+
+        Collection<Module> modules = extraModules != null ? asList(extraModules) : Collections.<Module>emptyList();
+        return collectModules(serverInjector, modules);
+    }
+
+    private static Collection<Module> collectModules(final Injector serverInjector, Collection<Module> extraModules) {
+
+        Collection<Module> modules = new ArrayList<>(extraModules.size() + 1);
+        modules.add(new Module() {
 
-	private static ModuleCollection mainModuleOverride(final Injector serverInjector) {
-		return new ModuleCollection(new Module() {
+            public void configure(Binder binder) {
+                binder.bind(Key.get(DataChannel.class, CLIENT_SERVER_CHANNEL_KEY)).toProviderInstance(
+                        new LocalClientServerChannelProvider(serverInjector));
+                binder.bind(ClientConnection.class).toProviderInstance(new LocalConnectionProvider());
+            }
+        });
 
-			public void configure(Binder binder) {
-				binder.bind(Key.get(DataChannel.class, CLIENT_SERVER_CHANNEL_KEY)).toProviderInstance(
-						new LocalClientServerChannelProvider(serverInjector));
-				binder.bind(ClientConnection.class).toProviderInstance(new LocalConnectionProvider());
-			}
-		});
-	}
+        modules.addAll(extraModules);
+        return modules;
+    }
 
-	public ClientLocalRuntime(Injector serverInjector, Map<String, String> properties, Collection<Module> extraModules) {
-		super(properties, mainModuleOverride(serverInjector).add(extraModules));
-	}
+    public ClientLocalRuntime(Injector serverInjector, Map<String, String> properties, Collection<Module> extraModules) {
+        super(properties, collectModules(serverInjector, extraModules));
+    }
 
-	public ClientLocalRuntime(Injector serverInjector, Map<String, String> properties, Module... extraModules) {
-		super(properties, mainModuleOverride(serverInjector).add(extraModules));
-	}
+    public ClientLocalRuntime(Injector serverInjector, Map<String, String> properties, Module... extraModules) {
+        super(properties, collectModules(serverInjector, extraModules));
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3dc06bd7/cayenne-client/src/main/java/org/apache/cayenne/configuration/rop/client/ClientRuntime.java
----------------------------------------------------------------------
diff --git a/cayenne-client/src/main/java/org/apache/cayenne/configuration/rop/client/ClientRuntime.java b/cayenne-client/src/main/java/org/apache/cayenne/configuration/rop/client/ClientRuntime.java
index c0ddc05..7e54c8c 100644
--- a/cayenne-client/src/main/java/org/apache/cayenne/configuration/rop/client/ClientRuntime.java
+++ b/cayenne-client/src/main/java/org/apache/cayenne/configuration/rop/client/ClientRuntime.java
@@ -18,23 +18,40 @@
  ****************************************************************/
 package org.apache.cayenne.configuration.rop.client;
 
-import java.util.Collection;
-import java.util.Map;
-
 import org.apache.cayenne.configuration.CayenneRuntime;
-import org.apache.cayenne.configuration.ModuleCollection;
 import org.apache.cayenne.di.Module;
 import org.apache.cayenne.remote.ClientConnection;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import static java.util.Arrays.asList;
+
 /**
  * A user application entry point to Cayenne stack on the ROP client.
  * 
  * @since 3.1
  */
+// TODO: module auto-loading and ClientRuntimeBuilder
 public class ClientRuntime extends CayenneRuntime {
 
-	private static ModuleCollection mainModule(Map<String, String> properties) {
-		return new ModuleCollection(new ClientModule(properties));
+	private static Collection<Module> collectModules(Map<String, String> properties, Module... extraModules) {
+
+		Collection<Module> modules = extraModules != null ? asList(extraModules) : Collections.<Module>emptyList();
+		return collectModules(properties, modules);
+	}
+
+	private static Collection<Module> collectModules(Map<String, String> properties, Collection<Module> extraModules) {
+		Collection<Module> modules = new ArrayList<>();
+		modules.add(new ClientModule(properties));
+
+		if(extraModules != null) {
+			modules.addAll(extraModules);
+		}
+
+		return modules;
 	}
 
 	/**
@@ -46,7 +63,7 @@ public class ClientRuntime extends CayenneRuntime {
 	 * services.
 	 */
 	public ClientRuntime(Map<String, String> properties, Collection<Module> extraModules) {
-		super(mainModule(properties).add(extraModules));
+		this(collectModules(properties, extraModules));
 	}
 
 	/**
@@ -58,7 +75,14 @@ public class ClientRuntime extends CayenneRuntime {
 	 * user services.
 	 */
 	public ClientRuntime(Map<String, String> properties, Module... extraModules) {
-		super(mainModule(properties).add(extraModules));
+		this(collectModules(properties, extraModules));
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	protected ClientRuntime(Collection<Module> modules) {
+		super(modules);
 	}
 
 	public ClientConnection getConnection() {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3dc06bd7/cayenne-client/src/test/java/org/apache/cayenne/configuration/rop/client/ClientLocalRuntimeTest.java
----------------------------------------------------------------------
diff --git a/cayenne-client/src/test/java/org/apache/cayenne/configuration/rop/client/ClientLocalRuntimeTest.java b/cayenne-client/src/test/java/org/apache/cayenne/configuration/rop/client/ClientLocalRuntimeTest.java
index b49e1db..d581104 100644
--- a/cayenne-client/src/test/java/org/apache/cayenne/configuration/rop/client/ClientLocalRuntimeTest.java
+++ b/cayenne-client/src/test/java/org/apache/cayenne/configuration/rop/client/ClientLocalRuntimeTest.java
@@ -22,7 +22,6 @@ import org.apache.cayenne.DataChannel;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.access.ClientServerChannel;
 import org.apache.cayenne.access.DataContext;
-import org.apache.cayenne.configuration.ModuleCollection;
 import org.apache.cayenne.configuration.ObjectContextFactory;
 import org.apache.cayenne.di.Binder;
 import org.apache.cayenne.di.DIBootstrap;
@@ -53,7 +52,7 @@ public class ClientLocalRuntimeTest {
 
 		ClientLocalRuntime runtime = new ClientLocalRuntime(DIBootstrap.createInjector(serverModule),
 				Collections.<String, String> emptyMap());
-		Collection<Module> cmodules = ((ModuleCollection) runtime.getModule()).getModules();
+		Collection<Module> cmodules = runtime.getModules();
 		assertEquals(2, cmodules.size());
 
 		assertTrue(cmodules.toArray()[0] instanceof ClientModule);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3dc06bd7/cayenne-client/src/test/java/org/apache/cayenne/configuration/rop/client/ClientRuntimeTest.java
----------------------------------------------------------------------
diff --git a/cayenne-client/src/test/java/org/apache/cayenne/configuration/rop/client/ClientRuntimeTest.java b/cayenne-client/src/test/java/org/apache/cayenne/configuration/rop/client/ClientRuntimeTest.java
index 86fea70..2156299 100644
--- a/cayenne-client/src/test/java/org/apache/cayenne/configuration/rop/client/ClientRuntimeTest.java
+++ b/cayenne-client/src/test/java/org/apache/cayenne/configuration/rop/client/ClientRuntimeTest.java
@@ -21,7 +21,6 @@ package org.apache.cayenne.configuration.rop.client;
 import org.apache.cayenne.CayenneContext;
 import org.apache.cayenne.DataChannel;
 import org.apache.cayenne.ObjectContext;
-import org.apache.cayenne.configuration.ModuleCollection;
 import org.apache.cayenne.di.Binder;
 import org.apache.cayenne.di.Module;
 import org.apache.cayenne.event.DefaultEventManager;
@@ -50,7 +49,7 @@ public class ClientRuntimeTest {
 	public void testDefaultConstructor() {
 		ClientRuntime runtime = new ClientRuntime(Collections.<String, String> emptyMap());
 
-		Collection<Module> modules = ((ModuleCollection) runtime.getModule()).getModules();
+		Collection<Module> modules = runtime.getModules();
 		assertEquals(1, modules.size());
 		Object[] marray = modules.toArray();
 
@@ -79,7 +78,7 @@ public class ClientRuntimeTest {
 		Map<String, String> properties = new HashMap<>();
 
 		ClientRuntime runtime = new ClientRuntime(properties, m1, m2);
-		Collection<Module> modules = ((ModuleCollection) runtime.getModule()).getModules();
+		Collection<Module> modules = runtime.getModules();
 		assertEquals(3, modules.size());
 
 		assertTrue(configured[0]);
@@ -110,7 +109,7 @@ public class ClientRuntimeTest {
 		Map<String, String> properties = new HashMap<>();
 
 		ClientRuntime runtime = new ClientRuntime(properties, modules);
-		Collection<Module> cmodules = ((ModuleCollection) runtime.getModule()).getModules();
+		Collection<Module> cmodules = runtime.getModules();
 		assertEquals(3, cmodules.size());
 
 		assertTrue(configured[0]);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3dc06bd7/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_Base.java
----------------------------------------------------------------------
diff --git a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_Base.java b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_Base.java
index 4b5774c..2d11f86 100644
--- a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_Base.java
+++ b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_Base.java
@@ -56,7 +56,7 @@ public class Runtime_AES128_Base {
     }
 
     protected ServerRuntime createRuntime(Module crypto) {
-        return new ServerRuntime("cayenne-crypto.xml", crypto);
+        return ServerRuntime.builder().addConfig("cayenne-crypto.xml").addModule(crypto).build();
     }
 
     protected Module createCryptoModule(boolean compress) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3dc06bd7/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/transformer/value/DefaultValueTransformerFactoryIT.java
----------------------------------------------------------------------
diff --git a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/transformer/value/DefaultValueTransformerFactoryIT.java b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/transformer/value/DefaultValueTransformerFactoryIT.java
index e40137e..9f42758 100644
--- a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/transformer/value/DefaultValueTransformerFactoryIT.java
+++ b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/transformer/value/DefaultValueTransformerFactoryIT.java
@@ -52,7 +52,7 @@ public class DefaultValueTransformerFactoryIT {
 
     @BeforeClass
     public static void beforeClass() throws Exception {
-        ServerRuntime runtime = new ServerRuntime("cayenne-crypto.xml");
+        ServerRuntime runtime = ServerRuntime.builder().addConfig("cayenne-crypto.xml").build();
         t1 = runtime.getChannel().getEntityResolver().getDbEntity("TABLE1");
         t2 = runtime.getChannel().getEntityResolver().getDbEntity("TABLE2");
         t3 = runtime.getChannel().getEntityResolver().getDbEntity("TABLE3");

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3dc06bd7/cayenne-server/src/main/java/org/apache/cayenne/configuration/CayenneRuntime.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/CayenneRuntime.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/CayenneRuntime.java
index 9ee5379..822a1b0 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/CayenneRuntime.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/CayenneRuntime.java
@@ -25,140 +25,130 @@ import org.apache.cayenne.di.DIBootstrap;
 import org.apache.cayenne.di.Injector;
 import org.apache.cayenne.di.Module;
 
+import java.util.Collection;
+import java.util.Objects;
+
 /**
  * A superclass of various Cayenne runtime stacks. A Runtime is the main access
  * point to Cayenne for a user application. It provides a default Cayenne
  * configuration as well as a way to customize this configuration via a built-in
  * dependency injection (DI) container. In fact implementation-wise, Runtime
  * object is just a convenience thin wrapper around a DI {@link Injector}.
- * 
+ *
  * @since 3.1
  */
 public abstract class CayenneRuntime {
 
-	/**
-	 * A holder of an Injector bound to the current thread. Used mainly to allow
-	 * serializable contexts to attach to correct Cayenne stack on
-	 * deserialization.
-	 * 
-	 * @since 3.1
-	 */
-	protected static final ThreadLocal<Injector> threadInjector = new ThreadLocal<Injector>();
-
-	/**
-	 * Binds a DI {@link Injector} bound to the current thread. It is primarily
-	 * intended for deserialization of ObjectContexts.
-	 * 
-	 * @since 3.1
-	 */
-	public static void bindThreadInjector(Injector injector) {
-		threadInjector.set(injector);
-	}
-
-	/**
-	 * Returns the {@link Injector} bound to the current thread. Will return
-	 * null if none is bound.
-	 * 
-	 * @since 3.1
-	 */
-	public static Injector getThreadInjector() {
-		return threadInjector.get();
-	}
-
-	protected Injector injector;
-	protected Module module;
-
-	/**
-	 * Creates a CayenneRuntime with configuration based on the supplied array
-	 * of DI modules.
-	 */
-	public CayenneRuntime(Module module) {
-		this.module = module;
-		this.injector = DIBootstrap.createInjector(module);
-	}
-
-	/**
-	 * Returns an array of modules used to initialize this runtime.
-	 * 
-	 * @deprecated since 4.0. We only keep one module now, so use
-	 *             {@link #getModule()}.
-	 */
-	@Deprecated
-	public Module[] getModules() {
-		return new Module[] { module };
-	}
-
-	/**
-	 * 
-	 * Returns the module used to initialize this runtime.
-	 * 
-	 * @since 4.0
-	 */
-	public Module getModule() {
-		return module;
-	}
-
-	/**
-	 * Returns DI injector used by this runtime.
-	 */
-	public Injector getInjector() {
-		return injector;
-	}
-
-	/**
-	 * Shuts down the DI injector of this runtime, giving all services that need
-	 * to release some resources a chance to do that.
-	 */
-	// the following annotation is for environments that manage CayenneRuntimes
-	// within
-	// another DI registry (e.g. unit tests)
-	@BeforeScopeEnd
-	public void shutdown() {
-		injector.shutdown();
-	}
-
-	/**
-	 * Returns the runtime {@link DataChannel}.
-	 */
-	public DataChannel getChannel() {
-		return injector.getInstance(DataChannel.class);
-	}
-
-	/**
-	 * Returns a new ObjectContext instance based on the runtime's main
-	 * DataChannel.
-	 * 
-	 * @since 4.0
-	 */
-	public ObjectContext newContext() {
-		return injector.getInstance(ObjectContextFactory.class).createContext();
-	}
-
-	/**
-	 * Returns a new ObjectContext which is a child of the specified
-	 * DataChannel. This method is used for creation of nested ObjectContexts,
-	 * with parent ObjectContext passed as an argument.
-	 * 
-	 * @since 4.0
-	 */
-	public ObjectContext newContext(DataChannel parentChannel) {
-		return injector.getInstance(ObjectContextFactory.class).createContext(parentChannel);
-	}
-
-	/**
-	 * @deprecated since 3.1 use better named {@link #newContext()} instead.
-	 */
-	@Deprecated
-	public ObjectContext getContext() {
-		return newContext();
-	}
-
-	/**
-	 * @deprecated since 3.1 use better named {@link #newContext(DataChannel)}
-	 *             instead.
-	 */
-	@Deprecated
-	public ObjectContext getContext(DataChannel parentChannel) {
-		return newContext(parentChannel);
-	}
+    /**
+     * A holder of an Injector bound to the current thread. Used mainly to allow
+     * serializable contexts to attach to correct Cayenne stack on
+     * deserialization.
+     *
+     * @since 3.1
+     */
+    protected static final ThreadLocal<Injector> threadInjector = new ThreadLocal<Injector>();
+
+    /**
+     * Binds a DI {@link Injector} bound to the current thread. It is primarily
+     * intended for deserialization of ObjectContexts.
+     *
+     * @since 3.1
+     */
+    public static void bindThreadInjector(Injector injector) {
+        threadInjector.set(injector);
+    }
+
+    /**
+     * Returns the {@link Injector} bound to the current thread. Will return
+     * null if none is bound.
+     *
+     * @since 3.1
+     */
+    public static Injector getThreadInjector() {
+        return threadInjector.get();
+    }
+
+    protected Injector injector;
+    protected Collection<Module> modules;
+
+    /**
+     * Creates a CayenneRuntime with configuration based on the supplied collection of DI modules.
+     */
+    protected CayenneRuntime(Collection<Module> modules) {
+        this.modules = Objects.requireNonNull(modules);
+        this.injector = DIBootstrap.createInjector(modules);
+    }
+
+    /**
+     * Returns the collection of modules used to initialize this runtime.
+     *
+     * @since 4.0
+     */
+    public Collection<Module> getModules() {
+        return modules;
+    }
+
+    /**
+     * Returns DI injector used by this runtime.
+     */
+    public Injector getInjector() {
+        return injector;
+    }
+
+    /**
+     * Shuts down the DI injector of this runtime, giving all services that need
+     * to release some resources a chance to do that.
+     */
+    // the following annotation is for environments that manage CayenneRuntimes
+    // within
+    // another DI registry (e.g. unit tests)
+    @BeforeScopeEnd
+    public void shutdown() {
+        injector.shutdown();
+    }
+
+    /**
+     * Returns the runtime {@link DataChannel}.
+     */
+    public DataChannel getChannel() {
+        return injector.getInstance(DataChannel.class);
+    }
+
+    /**
+     * Returns a new ObjectContext instance based on the runtime's main
+     * DataChannel.
+     *
+     * @since 4.0
+     */
+    public ObjectContext newContext() {
+        return injector.getInstance(ObjectContextFactory.class).createContext();
+    }
+
+    /**
+     * Returns a new ObjectContext which is a child of the specified
+     * DataChannel. This method is used for creation of nested ObjectContexts,
+     * with parent ObjectContext passed as an argument.
+     *
+     * @since 4.0
+     */
+    public ObjectContext newContext(DataChannel parentChannel) {
+        return injector.getInstance(ObjectContextFactory.class).createContext(parentChannel);
+    }
+
+    /**
+     * @deprecated since 3.1 use better named {@link #newContext()} instead.
+     */
+    @Deprecated
+    public ObjectContext getContext() {
+        return newContext();
+    }
+
+    /**
+     * @deprecated since 3.1 use better named {@link #newContext(DataChannel)}
+     * instead.
+     */
+    @Deprecated
+    public ObjectContext getContext(DataChannel parentChannel) {
+        return newContext(parentChannel);
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3dc06bd7/cayenne-server/src/main/java/org/apache/cayenne/configuration/ModuleCollection.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/ModuleCollection.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/ModuleCollection.java
deleted file mode 100644
index 19c2ed5..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/ModuleCollection.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*****************************************************************
- *   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.cayenne.configuration;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-import org.apache.cayenne.di.Binder;
-import org.apache.cayenne.di.Module;
-
-/**
- * A module that decorates a collection of other modules. Used as a helper for
- * multi-module runtimes initialization.
- * 
- * @since 4.0
- */
-public class ModuleCollection implements Module {
-
-	private Collection<Module> modules;
-
-	public ModuleCollection(Module... modules) {
-
-		this.modules = new ArrayList<Module>();
-		add(modules);
-	}
-
-	public ModuleCollection add(Module... modules) {
-		if (modules != null) {
-			for (Module m : modules) {
-				addModule(m);
-			}
-		}
-
-		return this;
-	}
-
-	public ModuleCollection add(Collection<Module> modules) {
-		if (modules != null) {
-			for (Module m : modules) {
-				addModule(m);
-			}
-		}
-
-		return this;
-	}
-
-	private void addModule(Module m) {
-		if (m instanceof ModuleCollection) {
-			// flatten
-			add(((ModuleCollection) m).getModules());
-		} else {
-			this.modules.add(m);
-		}
-	}
-
-	public Collection<Module> getModules() {
-		return modules;
-	}
-
-	@Override
-	public void configure(Binder binder) {
-		for (Module m : modules) {
-			m.configure(binder);
-		}
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3dc06bd7/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
index 8042949..1e32efc 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
@@ -93,6 +93,7 @@ public class ServerModule implements Module {
 
     private static final int DEFAULT_MAX_ID_QUALIFIER_SIZE = 10000;
 
+    @Deprecated
     protected String[] configurationLocations;
 
     /**
@@ -182,16 +183,33 @@ public class ServerModule implements Module {
     }
 
     /**
-     * Creates a ServerModule with at least one configuration location. For
-     * multi-module projects additional locations can be specified as well.
+     * Creates a new {@link ServerModule}.
+     *
+     * @since 4.0
      */
-    public ServerModule(String... configurationLocations) {
+    public ServerModule() {
+        this.configurationLocations = new String[0];
+    }
 
+    /**
+     * Creates a ServerModule with at least one configuration location. For multi-module projects additional locations
+     * can be specified as well.
+     *
+     * @deprecated since 4.0 use {@link ServerRuntimeBuilder#addConfig(String)} and/or
+     * {@link ServerModule#contributeProjectLocations(Binder)} to specify locations.
+     */
+    @Deprecated
+    public ServerModule(String firstConfigLocation, String... configurationLocations) {
         if (configurationLocations == null) {
             configurationLocations = new String[0];
         }
 
-        this.configurationLocations = configurationLocations;
+        this.configurationLocations = new String[configurationLocations.length + 1];
+        this.configurationLocations[0] = firstConfigLocation;
+
+        if(configurationLocations.length > 0) {
+            System.arraycopy(configurationLocations, 0, this.configurationLocations, 1, configurationLocations.length);
+        }
     }
 
     public void configure(Binder binder) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3dc06bd7/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntime.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntime.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntime.java
index 51ec9bf..d6029a2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntime.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntime.java
@@ -21,13 +21,18 @@ package org.apache.cayenne.configuration.server;
 import org.apache.cayenne.access.DataDomain;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.configuration.CayenneRuntime;
-import org.apache.cayenne.configuration.ModuleCollection;
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.ListBuilder;
 import org.apache.cayenne.di.Module;
 import org.apache.cayenne.tx.TransactionListener;
 import org.apache.cayenne.tx.TransactionManager;
 import org.apache.cayenne.tx.TransactionalOperation;
 
 import javax.sql.DataSource;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import static java.util.Arrays.asList;
 
 /**
  * An object representing Cayenne server-stack that connects directly to the
@@ -61,8 +66,28 @@ public class ServerRuntime extends CayenneRuntime {
         return new ServerRuntimeBuilder(name);
     }
 
-    private static ModuleCollection mainModule(String... configurationLocations) {
-        return new ModuleCollection(new ServerModule(configurationLocations));
+    @Deprecated
+    private static Collection<Module> collectModules(final String[] configurationLocations, Module... extraModules) {
+        Collection<Module> modules = new ArrayList<>();
+        modules.add(new ServerModule());
+
+        if(configurationLocations.length > 0) {
+            modules.add(new Module() {
+                @Override
+                public void configure(Binder binder) {
+                    ListBuilder<String> locationsBinder = ServerModule.contributeProjectLocations(binder);
+                    for(String c : configurationLocations) {
+                        locationsBinder.add(c);
+                    }
+                }
+            });
+        }
+
+        if(extraModules != null) {
+            modules.addAll(asList(extraModules));
+        }
+
+        return modules;
     }
 
     /**
@@ -70,9 +95,12 @@ public class ServerRuntime extends CayenneRuntime {
      * contained in {@link ServerModule}. CayenneServerModule is created with
      * provided 'configurationLocation'. An optional array of extra modules may
      * contain service overrides and/or user services.
+     *
+     * @deprecated since 4.0 use {@link ServerRuntime#builder()}.
      */
+    @Deprecated
     public ServerRuntime(String configurationLocation, Module... extraModules) {
-        super(mainModule(configurationLocation).add(extraModules));
+        this(collectModules(new String[]{configurationLocation}, extraModules));
     }
 
     /**
@@ -80,9 +108,24 @@ public class ServerRuntime extends CayenneRuntime {
      * contained in {@link ServerModule}. CayenneServerModule is created with
      * one or more 'configurationLocations'. An optional array of extra modules
      * may contain service overrides and/or user services.
+     *
+     * @deprecated since 4.0 use {@link ServerRuntime#builder()}.
      */
+    @Deprecated
     public ServerRuntime(String[] configurationLocations, Module... extraModules) {
-        super(mainModule(configurationLocations).add(extraModules));
+        this(collectModules(configurationLocations, extraModules));
+    }
+
+    /**
+     * Creates a server runtime configuring it with a standard set of services
+     * contained in {@link ServerModule}. CayenneServerModule is created with
+     * one or more 'configurationLocations'. An optional array of extra modules
+     * may contain service overrides and/or user services.
+     *
+     * @since 4.0
+     */
+    protected ServerRuntime(Collection<Module> modules) {
+        super(modules);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3dc06bd7/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilder.java
index 1c067cd..e5e2ae2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilder.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilder.java
@@ -22,6 +22,7 @@ import org.apache.cayenne.access.DataDomain;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.datasource.DataSourceBuilder;
 import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.ListBuilder;
 import org.apache.cayenne.di.MapBuilder;
 import org.apache.cayenne.di.Module;
 
@@ -51,6 +52,7 @@ public class ServerRuntimeBuilder {
     private int jdbcMaxConnections;
     private long maxQueueWaitTime;
     private String validationQuery;
+    private boolean autoLoadModules;
 
     /**
      * @deprecated since 4.0.M5 in favor of {@link ServerRuntime#builder()}
@@ -84,6 +86,19 @@ public class ServerRuntimeBuilder {
         this.configs = new LinkedHashSet<String>();
         this.modules = new ArrayList<Module>();
         this.name = name;
+        this.autoLoadModules = true;
+    }
+
+    /**
+     * Disables DI module auto-loading. By default auto-loading is enabled based on
+     * {@link org.apache.cayenne.di.spi.ModuleLoader} service provider inetrface. If you decide to disable auto-loading,
+     * make sure you provide all the modules that you need.
+     *
+     * @return this builder instance.
+     */
+    public ServerRuntimeBuilder disableModulesAutoLoading() {
+        this.autoLoadModules = false;
+        return this;
     }
 
     /**
@@ -196,20 +211,37 @@ public class ServerRuntimeBuilder {
 
     public ServerRuntime build() {
 
-        buildModules();
+        Collection<Module> configModules = buildConfigModules();
+
+        Collection<Module> allModules = new ArrayList<>();
+        // TODO: make ServerModule auto-loadable?
+        allModules.add(new ServerModule());
+        allModules.addAll(configModules);
+        // custom modules override config modules...
+        allModules.addAll(this.modules);
 
-        String[] configs = this.configs.toArray(new String[this.configs.size()]);
-        Module[] modules = this.modules.toArray(new Module[this.modules.size()]);
-        return new ServerRuntime(configs, modules);
+        return new ServerRuntime(allModules);
     }
 
-    private void buildModules() {
+    private Collection<Module> buildConfigModules() {
 
-        String nameOverride = name;
+        Collection<Module> modules = new ArrayList<>();
 
+        if(!configs.isEmpty()) {
+            modules.add(new Module() {
+                @Override
+                public void configure(Binder binder) {
+                    ListBuilder<String> locationsBinder = ServerModule.contributeProjectLocations(binder);
+                    for(String c : configs) {
+                        locationsBinder.add(c);
+                    }
+                }
+            });
+        }
+
+        String nameOverride = name;
         if (nameOverride == null) {
-            // check if we need to force the default name ... we do when no
-            // configs or multiple configs are supplied.
+            // check if we need to force the default name ... we do when no configs or multiple configs are supplied.
             if (configs.size() != 1) {
                 nameOverride = DEFAULT_NAME;
             }
@@ -218,7 +250,7 @@ public class ServerRuntimeBuilder {
         if (nameOverride != null) {
 
             final String finalNameOverride = nameOverride;
-            prepend(new Module() {
+            modules.add(new Module() {
                 @Override
                 public void configure(Binder binder) {
                     ServerModule.contributeProperties(binder).put(Constants.SERVER_DOMAIN_NAME_PROPERTY, finalNameOverride);
@@ -228,7 +260,7 @@ public class ServerRuntimeBuilder {
 
         if (dataSourceFactory != null) {
 
-            prepend(new Module() {
+            modules.add(new Module() {
                 @Override
                 public void configure(Binder binder) {
                     binder.bind(DataDomain.class).toProvider(SyntheticNodeDataDomainProvider.class);
@@ -237,10 +269,9 @@ public class ServerRuntimeBuilder {
             });
 
         }
-        // URL and driver are the minimal requirement for
-        // DelegatingDataSourceFactory to work
+        // URL and driver are the minimal requirement for DelegatingDataSourceFactory to work
         else if (jdbcUrl != null && jdbcDriver != null) {
-            prepend(new Module() {
+            modules.add(new Module() {
                 @Override
                 public void configure(Binder binder) {
                     binder.bind(DataDomain.class).toProvider(SyntheticNodeDataDomainProvider.class);
@@ -273,11 +304,7 @@ public class ServerRuntimeBuilder {
                 }
             });
         }
-    }
 
-    private void prepend(Module module) {
-        // prepend any special modules BEFORE custom modules, to allow callers
-        // to override our stuff
-        modules.add(0, module);
+        return modules;
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3dc06bd7/cayenne-server/src/test/java/org/apache/cayenne/configuration/rop/server/ROPServletTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/configuration/rop/server/ROPServletTest.java b/cayenne-server/src/test/java/org/apache/cayenne/configuration/rop/server/ROPServletTest.java
index 8495e99..17c3953 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/configuration/rop/server/ROPServletTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/configuration/rop/server/ROPServletTest.java
@@ -18,18 +18,10 @@
  ****************************************************************/
 package org.apache.cayenne.configuration.rop.server;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
+import com.mockrunner.mock.web.MockServletConfig;
+import com.mockrunner.mock.web.MockServletContext;
 import org.apache.cayenne.configuration.CayenneRuntime;
 import org.apache.cayenne.configuration.Constants;
-import org.apache.cayenne.configuration.ModuleCollection;
 import org.apache.cayenne.configuration.server.ServerModule;
 import org.apache.cayenne.configuration.web.MockModule1;
 import org.apache.cayenne.configuration.web.MockModule2;
@@ -42,8 +34,11 @@ import org.apache.cayenne.rop.ROPServlet;
 import org.junit.After;
 import org.junit.Test;
 
-import com.mockrunner.mock.web.MockServletConfig;
-import com.mockrunner.mock.web.MockServletContext;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import static org.junit.Assert.*;
 
 public class ROPServletTest {
 
@@ -122,12 +117,13 @@ public class ROPServletTest {
 
 		assertEquals(Arrays.asList(name + ".xml"), locations);
 		
-		Collection<Module> modules = ((ModuleCollection) runtime.getModule()).getModules();
-		assertEquals(2, modules.size());
+		Collection<Module> modules = runtime.getModules();
+		assertEquals(3, modules.size());
 		Object[] marray = modules.toArray();
 
 		assertTrue(marray[0] instanceof ServerModule);
-		assertTrue(marray[1] instanceof ROPServerModule);
+		// [1] is an inner class
+		assertTrue(marray[2] instanceof ROPServerModule);
 	}
 
 	@Test
@@ -148,15 +144,16 @@ public class ROPServletTest {
 		runtime = WebUtil.getCayenneRuntime(context);
 		assertNotNull(runtime);
 
-		Collection<Module> modules = ((ModuleCollection) runtime.getModule()).getModules();
-		assertEquals(4, modules.size());
+		Collection<Module> modules = runtime.getModules();
+		assertEquals(5, modules.size());
 
 		Object[] marray = modules.toArray();
 
 		assertTrue(marray[0] instanceof ServerModule);
-		assertTrue(marray[1] instanceof ROPServerModule);
-		assertTrue(marray[2] instanceof MockModule1);
-		assertTrue(marray[3] instanceof MockModule2);
+		// [1] is an inner class
+		assertTrue(marray[2] instanceof ROPServerModule);
+		assertTrue(marray[3] instanceof MockModule1);
+		assertTrue(marray[4] instanceof MockModule2);
 
 		RequestHandler handler = runtime.getInjector().getInstance(RequestHandler.class);
 		assertTrue(handler instanceof MockRequestHandler);
@@ -176,12 +173,12 @@ public class ROPServletTest {
 
 		servlet.init(config);
 		runtime = WebUtil.getCayenneRuntime(context);
-		Collection<Module> modules = ((ModuleCollection) runtime.getModule()).getModules();
-		assertEquals(3, modules.size());
+		Collection<Module> modules = runtime.getModules();
+		assertEquals(4, modules.size());
 
 		Object[] marray = modules.toArray();
 
-		assertTrue(marray[2] instanceof ROPHessianServlet_ConfigModule);
+		assertTrue(marray[3] instanceof ROPHessianServlet_ConfigModule);
 
 		// TODO: mock servlet request to check that the right service instance
 		// is invoked

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3dc06bd7/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilderTest.java b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilderTest.java
index 9177fac..2617476 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilderTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeBuilderTest.java
@@ -18,24 +18,20 @@
  ****************************************************************/
 package org.apache.cayenne.configuration.server;
 
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
+import org.apache.cayenne.configuration.Constants;
+import org.apache.cayenne.di.Key;
+import org.apache.cayenne.di.Module;
+import org.junit.After;
+import org.junit.Test;
 
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 
-import org.apache.cayenne.configuration.Constants;
-import org.apache.cayenne.configuration.ModuleCollection;
-import org.apache.cayenne.di.Key;
-import org.apache.cayenne.di.Module;
-import org.junit.After;
-import org.junit.Test;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.mock;
 
 public class ServerRuntimeBuilderTest {
 
@@ -62,9 +58,8 @@ public class ServerRuntimeBuilderTest {
 				Key.get(List.class, Constants.SERVER_PROJECT_LOCATIONS_LIST));
 
 		assertEquals(Arrays.asList(), locations);
-		assertTrue(runtime.getModule() instanceof ModuleCollection);
 
-		Collection<Module> modules = ((ModuleCollection) runtime.getModule()).getModules();
+		Collection<Module> modules = runtime.getModules();
 		assertEquals(2, modules.size());
 		assertThat(modules.iterator().next(), instanceOf(ServerModule.class));
 	}
@@ -79,8 +74,8 @@ public class ServerRuntimeBuilderTest {
 
 		assertEquals(Arrays.asList("xxxx"), locations);
 
-		Collection<Module> modules = ((ModuleCollection) runtime.getModule()).getModules();
-		assertEquals(1, modules.size());
+		Collection<Module> modules = runtime.getModules();
+		assertEquals(2, modules.size());
 		assertThat(modules.iterator().next(), instanceOf(ServerModule.class));
 
 	}
@@ -95,8 +90,8 @@ public class ServerRuntimeBuilderTest {
 
 		assertEquals(Arrays.asList("xxxx", "yyyy"), locations);
 
-		Collection<Module> modules = ((ModuleCollection) runtime.getModule()).getModules();
-		assertEquals(2, modules.size());
+		Collection<Module> modules = runtime.getModules();
+		assertEquals(3, modules.size());
 		assertThat(modules.iterator().next(), instanceOf(ServerModule.class));
 	}
 
@@ -107,7 +102,7 @@ public class ServerRuntimeBuilderTest {
 
 		runtime = new ServerRuntimeBuilder().addModule(m).build();
 
-		Collection<Module> modules = ((ModuleCollection) runtime.getModule()).getModules();
+		Collection<Module> modules = runtime.getModules();
 		assertEquals(3, modules.size());
 
 		Iterator<Module> it = modules.iterator();

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3dc06bd7/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeTest.java b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeTest.java
index cee4c67..febbad8 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/ServerRuntimeTest.java
@@ -18,22 +18,11 @@
  ****************************************************************/
 package org.apache.cayenne.configuration.server;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
 import org.apache.cayenne.DataChannel;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.QueryResponse;
 import org.apache.cayenne.access.DataContext;
 import org.apache.cayenne.configuration.Constants;
-import org.apache.cayenne.configuration.ModuleCollection;
 import org.apache.cayenne.configuration.ObjectContextFactory;
 import org.apache.cayenne.di.Binder;
 import org.apache.cayenne.di.Key;
@@ -47,159 +36,167 @@ import org.apache.cayenne.tx.TransactionFactory;
 import org.apache.cayenne.tx.TransactionalOperation;
 import org.junit.Test;
 
-public class ServerRuntimeTest {
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
 
-	@Test
-	public void testPerformInTransaction() {
+import static java.util.Arrays.asList;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
-		final BaseTransaction tx = mock(BaseTransaction.class);
-		final TransactionFactory txFactory = mock(TransactionFactory.class);
-		when(txFactory.createTransaction()).thenReturn(tx);
+public class ServerRuntimeTest {
 
-		Module module = new Module() {
+    @Test
+    public void testPerformInTransaction() {
 
-			public void configure(Binder binder) {
-				binder.bind(TransactionFactory.class).toInstance(txFactory);
-			}
-		};
+        final BaseTransaction tx = mock(BaseTransaction.class);
+        final TransactionFactory txFactory = mock(TransactionFactory.class);
+        when(txFactory.createTransaction()).thenReturn(tx);
 
-		ServerRuntime runtime = new ServerRuntime("xxxx", module);
-		try {
+        Module module = new Module() {
 
-			final Object expectedResult = new Object();
-			Object result = runtime.performInTransaction(new TransactionalOperation<Object>() {
-				public Object perform() {
-					assertSame(tx, BaseTransaction.getThreadTransaction());
-					return expectedResult;
-				}
-			});
+            public void configure(Binder binder) {
+                binder.bind(TransactionFactory.class).toInstance(txFactory);
+            }
+        };
 
-			assertSame(expectedResult, result);
-		} finally {
-			runtime.shutdown();
-		}
+        ServerRuntime runtime = ServerRuntime.builder().addConfig("xxxx").addModule(module).build();
+        try {
 
-	}
+            final Object expectedResult = new Object();
+            Object result = runtime.performInTransaction(new TransactionalOperation<Object>() {
+                public Object perform() {
+                    assertSame(tx, BaseTransaction.getThreadTransaction());
+                    return expectedResult;
+                }
+            });
 
-	@Test
-	public void testDefaultConstructor_SingleLocation() {
-		ServerRuntime runtime = new ServerRuntime("xxxx");
+            assertSame(expectedResult, result);
+        } finally {
+            runtime.shutdown();
+        }
 
-		List<?> locations = runtime.getInjector().getInstance(
-				Key.get(List.class, Constants.SERVER_PROJECT_LOCATIONS_LIST));
+    }
 
-		assertEquals(Arrays.asList("xxxx"), locations);
+    @Deprecated
+    @Test
+    public void testDefaultConstructor_SingleLocation() {
+        ServerRuntime runtime = new ServerRuntime("xxxx");
 
-		Collection<Module> modules = ((ModuleCollection) runtime.getModule()).getModules();
-		assertEquals(1, modules.size());
-		Module m0 = modules.iterator().next();
-		assertTrue(m0 instanceof ServerModule);
-		assertEquals("xxxx", ((ServerModule) m0).configurationLocations[0]);
-	}
+        List<?> locations = runtime.getInjector().getInstance(
+                Key.get(List.class, Constants.SERVER_PROJECT_LOCATIONS_LIST));
 
-	@Test
-	public void testDefaultConstructor_MultipleLocations() {
-		ServerRuntime runtime = new ServerRuntime(new String[] { "xxxx", "yyyy" });
+        assertEquals(Arrays.asList("xxxx"), locations);
 
-		List<?> locations = runtime.getInjector().getInstance(
-				Key.get(List.class, Constants.SERVER_PROJECT_LOCATIONS_LIST));
+        Collection<Module> modules = runtime.getModules();
+        assertEquals(2, modules.size());
+        Module m0 = modules.iterator().next();
+        assertTrue(m0 instanceof ServerModule);
+    }
 
-		assertEquals(Arrays.asList("xxxx", "yyyy"), locations);
+    @Test
+    @Deprecated
+    public void testDefaultConstructor_MultipleLocations() {
+        ServerRuntime runtime = new ServerRuntime(new String[]{"xxxx", "yyyy"});
 
-		assertTrue(runtime.getModule() instanceof ModuleCollection);
+        List<?> locations = runtime.getInjector().getInstance(
+                Key.get(List.class, Constants.SERVER_PROJECT_LOCATIONS_LIST));
 
-		Collection<Module> modules = ((ModuleCollection) runtime.getModule()).getModules();
-		assertEquals(1, modules.size());
-		Module m0 = modules.iterator().next();
-		assertTrue(m0 instanceof ServerModule);
+        assertEquals(Arrays.asList("xxxx", "yyyy"), locations);
 
-		assertEquals("xxxx", ((ServerModule) m0).configurationLocations[0]);
-		assertEquals("yyyy", ((ServerModule) m0).configurationLocations[1]);
-	}
+        Collection<Module> modules = runtime.getModules();
+        assertEquals(2, modules.size());
+        Module m0 = modules.iterator().next();
+        assertTrue(m0 instanceof ServerModule);
+    }
 
-	@Test
-	public void testConstructor_Modules() {
+    @Test
+    @Deprecated
+    public void testConstructor_Modules() {
 
-		final boolean[] configured = new boolean[2];
+        final boolean[] configured = new boolean[2];
 
-		Module m1 = new Module() {
+        Module m1 = new Module() {
 
-			public void configure(Binder binder) {
-				configured[0] = true;
-			}
-		};
+            public void configure(Binder binder) {
+                configured[0] = true;
+            }
+        };
 
-		Module m2 = new Module() {
+        Module m2 = new Module() {
 
-			public void configure(Binder binder) {
-				configured[1] = true;
-			}
-		};
+            public void configure(Binder binder) {
+                configured[1] = true;
+            }
+        };
 
-		ServerRuntime runtime = new ServerRuntime("xxxx", m1, m2);
+        ServerRuntime runtime = new ServerRuntime(asList(m1, m2));
 
-		Collection<Module> modules = ((ModuleCollection) runtime.getModule()).getModules();
-		assertEquals(3, modules.size());
+        Collection<Module> modules = runtime.getModules();
+        assertEquals(2, modules.size());
 
-		assertTrue(configured[0]);
-		assertTrue(configured[1]);
-	}
+        assertTrue(configured[0]);
+        assertTrue(configured[1]);
+    }
 
-	@Test
-	public void testGetDataChannel_CustomModule() {
-		final DataChannel channel = new DataChannel() {
+    @Test
+    @Deprecated
+    public void testGetDataChannel_CustomModule() {
+        final DataChannel channel = new DataChannel() {
 
-			public EntityResolver getEntityResolver() {
-				return null;
-			}
+            public EntityResolver getEntityResolver() {
+                return null;
+            }
 
-			public EventManager getEventManager() {
-				return null;
-			}
+            public EventManager getEventManager() {
+                return null;
+            }
 
-			public QueryResponse onQuery(ObjectContext originatingContext, Query query) {
-				return null;
-			}
+            public QueryResponse onQuery(ObjectContext originatingContext, Query query) {
+                return null;
+            }
 
-			public GraphDiff onSync(ObjectContext originatingContext, GraphDiff changes, int syncType) {
-				return null;
-			}
-		};
+            public GraphDiff onSync(ObjectContext originatingContext, GraphDiff changes, int syncType) {
+                return null;
+            }
+        };
 
-		Module module = new Module() {
+        Module module = new Module() {
 
-			public void configure(Binder binder) {
-				binder.bind(DataChannel.class).toInstance(channel);
-			}
-		};
+            public void configure(Binder binder) {
+                binder.bind(DataChannel.class).toInstance(channel);
+            }
+        };
 
-		ServerRuntime runtime = new ServerRuntime("Yuis", module);
-		assertSame(channel, runtime.getChannel());
-	}
+        ServerRuntime runtime = new ServerRuntime("Yuis", module);
+        assertSame(channel, runtime.getChannel());
+    }
 
-	@Test
-	public void testGetObjectContext_CustomModule() {
-		final ObjectContext context = new DataContext();
-		final ObjectContextFactory factory = new ObjectContextFactory() {
+    @Test
+    @Deprecated
+    public void testGetObjectContext_CustomModule() {
+        final ObjectContext context = new DataContext();
+        final ObjectContextFactory factory = new ObjectContextFactory() {
 
-			public ObjectContext createContext(DataChannel parent) {
-				return context;
-			}
+            public ObjectContext createContext(DataChannel parent) {
+                return context;
+            }
 
-			public ObjectContext createContext() {
-				return context;
-			}
-		};
+            public ObjectContext createContext() {
+                return context;
+            }
+        };
 
-		Module module = new Module() {
+        Module module = new Module() {
 
-			public void configure(Binder binder) {
-				binder.bind(ObjectContextFactory.class).toInstance(factory);
-			}
-		};
+            public void configure(Binder binder) {
+                binder.bind(ObjectContextFactory.class).toInstance(factory);
+            }
+        };
 
-		ServerRuntime runtime = new ServerRuntime("mnYw", module);
-		assertSame(context, runtime.newContext());
-		assertSame(context, runtime.newContext());
-	}
+        ServerRuntime runtime = new ServerRuntime("mnYw", module);
+        assertSame(context, runtime.newContext());
+        assertSame(context, runtime.newContext());
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3dc06bd7/cayenne-server/src/test/java/org/apache/cayenne/configuration/web/CayenneFilterTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/configuration/web/CayenneFilterTest.java b/cayenne-server/src/test/java/org/apache/cayenne/configuration/web/CayenneFilterTest.java
index ab8c687..5b73b37 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/configuration/web/CayenneFilterTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/configuration/web/CayenneFilterTest.java
@@ -25,7 +25,6 @@ import com.mockrunner.mock.web.MockHttpServletResponse;
 import com.mockrunner.mock.web.MockServletContext;
 import org.apache.cayenne.configuration.CayenneRuntime;
 import org.apache.cayenne.configuration.Constants;
-import org.apache.cayenne.configuration.ModuleCollection;
 import org.apache.cayenne.configuration.server.ServerModule;
 import org.apache.cayenne.di.Key;
 import org.apache.cayenne.di.Module;
@@ -106,13 +105,14 @@ public class CayenneFilterTest {
 				Key.get(List.class, Constants.SERVER_PROJECT_LOCATIONS_LIST));
 
 		assertEquals(Arrays.asList("cayenne-abc.xml"), locations);
-		Collection<Module> modules = ((ModuleCollection) runtime.getModule()).getModules();
-		assertEquals(2, modules.size());
+		Collection<Module> modules = runtime.getModules();
+		assertEquals(3, modules.size());
 
 		Object[] marray = modules.toArray();
 
 		assertTrue(marray[0] instanceof ServerModule);
-		assertTrue(marray[1] instanceof WebModule);
+		// [1] is an inner class
+		assertTrue(marray[2] instanceof WebModule);
 
 		RequestHandler handler = runtime.getInjector().getInstance(RequestHandler.class);
 		assertTrue(handler instanceof SessionContextRequestHandler);
@@ -135,14 +135,15 @@ public class CayenneFilterTest {
 		CayenneRuntime runtime = WebUtil.getCayenneRuntime(context);
 		assertNotNull(runtime);
 
-		Collection<Module> modules = ((ModuleCollection) runtime.getModule()).getModules();
-		assertEquals(4, modules.size());
+		Collection<Module> modules = runtime.getModules();
+		assertEquals(5, modules.size());
 
 		Object[] marray = modules.toArray();
 		assertTrue(marray[0] instanceof ServerModule);
-		assertTrue(marray[1] instanceof WebModule);
-		assertTrue(marray[2] instanceof MockModule1);
-		assertTrue(marray[3] instanceof MockModule2);
+		// [1] is an inner class
+		assertTrue(marray[2] instanceof WebModule);
+		assertTrue(marray[3] instanceof MockModule1);
+		assertTrue(marray[4] instanceof MockModule2);
 
 		RequestHandler handler = runtime.getInjector().getInstance(RequestHandler.class);
 		assertTrue(handler instanceof MockRequestHandler);


[10/10] cayenne git commit: CAY-2166 Auto-loading of Cayenne modules

Posted by aa...@apache.org.
CAY-2166 Auto-loading of Cayenne modules

* auto-loading CryptoModule


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/2bc717d2
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/2bc717d2
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/2bc717d2

Branch: refs/heads/master
Commit: 2bc717d28d80f3b025bc2b52e4cefbc8c5d839f0
Parents: 302ca65
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Sun Dec 11 17:05:33 2016 +0300
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Sun Dec 11 20:56:05 2016 +0300

----------------------------------------------------------------------
 cayenne-crypto/pom.xml                          |  7 +++++
 .../org.apache.cayenne.di.spi.ModuleProvider    |  1 +
 .../cayenne/crypto/CryptoModuleBuilderTest.java | 14 ++++-----
 .../crypto/CryptoModuleProviderTest.java        | 30 ++++++++++++++++++++
 .../cayenne/crypto/Runtime_AES128_Base.java     |  5 ++--
 5 files changed, 48 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/2bc717d2/cayenne-crypto/pom.xml
----------------------------------------------------------------------
diff --git a/cayenne-crypto/pom.xml b/cayenne-crypto/pom.xml
index ea4e759..0d7d1fb 100644
--- a/cayenne-crypto/pom.xml
+++ b/cayenne-crypto/pom.xml
@@ -41,6 +41,13 @@
 			<scope>test</scope>
 		</dependency>
 		<dependency>
+			<groupId>org.apache.cayenne</groupId>
+			<artifactId>cayenne-server</artifactId>
+			<version>${project.version}</version>
+			<type>test-jar</type>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
 			<groupId>org.mockito</groupId>
 			<artifactId>mockito-all</artifactId>
 			<scope>test</scope>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2bc717d2/cayenne-crypto/src/main/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider
----------------------------------------------------------------------
diff --git a/cayenne-crypto/src/main/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider b/cayenne-crypto/src/main/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider
new file mode 100644
index 0000000..e782376
--- /dev/null
+++ b/cayenne-crypto/src/main/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider
@@ -0,0 +1 @@
+org.apache.cayenne.crypto.CryptoModuleProvider
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2bc717d2/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/CryptoModuleBuilderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/CryptoModuleBuilderTest.java b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/CryptoModuleBuilderTest.java
index 1741c0a..f6c4b20 100644
--- a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/CryptoModuleBuilderTest.java
+++ b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/CryptoModuleBuilderTest.java
@@ -18,12 +18,6 @@
  ****************************************************************/
 package org.apache.cayenne.crypto;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import java.net.URL;
-import java.security.Key;
-
 import org.apache.cayenne.crypto.key.JceksKeySourceTest;
 import org.apache.cayenne.crypto.key.KeySource;
 import org.apache.cayenne.crypto.transformer.value.DefaultValueTransformerFactory;
@@ -32,6 +26,12 @@ import org.apache.cayenne.di.Injector;
 import org.apache.cayenne.di.Module;
 import org.junit.Test;
 
+import java.net.URL;
+import java.security.Key;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
 public class CryptoModuleBuilderTest {
 
     @Test
@@ -41,7 +41,7 @@ public class CryptoModuleBuilderTest {
         Module m = new CryptoModuleBuilder().keyStore(ksUrl, JceksKeySourceTest.TEST_KEY_PASS, "k1")
                 .valueTransformer(DefaultValueTransformerFactory.class).build();
 
-        Injector injector = DIBootstrap.createInjector(m);
+        Injector injector = DIBootstrap.createInjector(new CryptoModule(), m);
 
         KeySource ks = injector.getInstance(KeySource.class);
         Key k1 = ks.getKey("k1");

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2bc717d2/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/CryptoModuleProviderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/CryptoModuleProviderTest.java b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/CryptoModuleProviderTest.java
new file mode 100644
index 0000000..80db1fc
--- /dev/null
+++ b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/CryptoModuleProviderTest.java
@@ -0,0 +1,30 @@
+/*
+ *    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.cayenne.crypto;
+
+import org.apache.cayenne.unit.util.ModuleProviderChecker;
+import org.junit.Test;
+
+public class CryptoModuleProviderTest {
+
+    @Test
+    public void testProviderPresent() {
+        ModuleProviderChecker.testProviderPresent(CryptoModuleProvider.class);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2bc717d2/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_Base.java
----------------------------------------------------------------------
diff --git a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_Base.java b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_Base.java
index 2d11f86..aea8775 100644
--- a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_Base.java
+++ b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_Base.java
@@ -62,8 +62,9 @@ public class Runtime_AES128_Base {
     protected Module createCryptoModule(boolean compress) {
         URL keyStoreUrl = JceksKeySourceTest.class.getResource(JceksKeySourceTest.KS1_JCEKS);
 
-        CryptoModuleBuilder builder = new CryptoModuleBuilder().keyStore(keyStoreUrl, JceksKeySourceTest.TEST_KEY_PASS,
-                "k3");
+        CryptoModuleBuilder builder = CryptoModule
+                .builder()
+                .keyStore(keyStoreUrl, JceksKeySourceTest.TEST_KEY_PASS, "k3");
 
         if (compress) {
             builder.compress();


[07/10] cayenne git commit: CAY-2166 Auto-loading of Cayenne modules

Posted by aa...@apache.org.
CAY-2166 Auto-loading of Cayenne modules

* auto-loading of JodaModule
* renamed CayenneJodaModule to JodaModule


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/08fc9f42
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/08fc9f42
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/08fc9f42

Branch: refs/heads/master
Commit: 08fc9f42740b1674b2546c0c2e5bae8c24fcc76e
Parents: 29c640e
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Sun Dec 11 17:05:33 2016 +0300
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Sun Dec 11 20:12:48 2016 +0300

----------------------------------------------------------------------
 .../org/apache/cayenne/java8/RuntimeBase.java   |   2 +-
 cayenne-joda/pom.xml                            |   7 ++
 .../apache/cayenne/joda/CayenneJodaModule.java  |  51 --------
 .../org/apache/cayenne/joda/JodaModule.java     |  44 +++++++
 .../apache/cayenne/joda/JodaModuleProvider.java |  43 +++++++
 .../org.apache.cayenne.di.spi.ModuleProvider    |   1 +
 .../org/apache/cayenne/joda/JodaModuleIT.java   | 122 ++++++++++++++++++
 .../cayenne/joda/JodaModuleProviderTest.java    |  30 +++++
 .../org/apache/cayenne/joda/JodaTimeIT.java     | 126 -------------------
 docs/doc/src/main/resources/UPGRADE.txt         |  14 ++-
 10 files changed, 257 insertions(+), 183 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/08fc9f42/cayenne-java8/src/test/java/org/apache/cayenne/java8/RuntimeBase.java
----------------------------------------------------------------------
diff --git a/cayenne-java8/src/test/java/org/apache/cayenne/java8/RuntimeBase.java b/cayenne-java8/src/test/java/org/apache/cayenne/java8/RuntimeBase.java
index 8b75880..4a8df90 100644
--- a/cayenne-java8/src/test/java/org/apache/cayenne/java8/RuntimeBase.java
+++ b/cayenne-java8/src/test/java/org/apache/cayenne/java8/RuntimeBase.java
@@ -27,7 +27,7 @@ public class RuntimeBase {
     protected ServerRuntime runtime;
 
     @Before
-    public void setUpRuntime() throws Exception {
+    public void setUpRuntime() {
         this.runtime = ServerRuntime.builder().addConfig("cayenne-java8.xml").build();
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/08fc9f42/cayenne-joda/pom.xml
----------------------------------------------------------------------
diff --git a/cayenne-joda/pom.xml b/cayenne-joda/pom.xml
index 3f2fbe8..b03b5e5 100644
--- a/cayenne-joda/pom.xml
+++ b/cayenne-joda/pom.xml
@@ -66,6 +66,13 @@
 			<scope>test</scope>
 		</dependency>
 		<dependency>
+			<groupId>org.apache.cayenne</groupId>
+			<artifactId>cayenne-server</artifactId>
+			<version>${project.version}</version>
+			<type>test-jar</type>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
 			<groupId>mockrunner</groupId>
 			<artifactId>mockrunner</artifactId>
 			<scope>test</scope>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/08fc9f42/cayenne-joda/src/main/java/org/apache/cayenne/joda/CayenneJodaModule.java
----------------------------------------------------------------------
diff --git a/cayenne-joda/src/main/java/org/apache/cayenne/joda/CayenneJodaModule.java b/cayenne-joda/src/main/java/org/apache/cayenne/joda/CayenneJodaModule.java
deleted file mode 100644
index 0821b68..0000000
--- a/cayenne-joda/src/main/java/org/apache/cayenne/joda/CayenneJodaModule.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package org.apache.cayenne.joda;
-
-/**
- * **************************************************************
- * 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
- * <p/>
- * http://www.apache.org/licenses/LICENSE-2.0
- * <p/>
- * 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.
- * **************************************************************
- */
-
-import org.apache.cayenne.configuration.server.ServerModule;
-import org.apache.cayenne.di.Binder;
-import org.apache.cayenne.di.Module;
-import org.apache.cayenne.joda.access.types.DateTimeType;
-import org.apache.cayenne.joda.access.types.LocalDateTimeType;
-import org.apache.cayenne.joda.access.types.LocalDateType;
-import org.apache.cayenne.joda.access.types.LocalTimeType;
-
-/**
- * Include this module when creating a ServerRuntime in order to add support for
- * joda-time ObjAttributes.
- *
- * @since 4.0
- */
-public class CayenneJodaModule implements Module {
-
-    public CayenneJodaModule() {
-    }
-
-    @Override
-    public void configure(Binder binder) {
-        ServerModule.contributeDefaultTypes(binder)
-                .add(new DateTimeType())
-                .add(new LocalDateType())
-                .add(new LocalTimeType())
-                .add(new LocalDateTimeType());
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/08fc9f42/cayenne-joda/src/main/java/org/apache/cayenne/joda/JodaModule.java
----------------------------------------------------------------------
diff --git a/cayenne-joda/src/main/java/org/apache/cayenne/joda/JodaModule.java b/cayenne-joda/src/main/java/org/apache/cayenne/joda/JodaModule.java
new file mode 100644
index 0000000..8532d63
--- /dev/null
+++ b/cayenne-joda/src/main/java/org/apache/cayenne/joda/JodaModule.java
@@ -0,0 +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.cayenne.joda;
+
+import org.apache.cayenne.configuration.server.ServerModule;
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.joda.access.types.DateTimeType;
+import org.apache.cayenne.joda.access.types.LocalDateTimeType;
+import org.apache.cayenne.joda.access.types.LocalDateType;
+import org.apache.cayenne.joda.access.types.LocalTimeType;
+
+/**
+ * Auto-loadable Cayenne module that adds support for Joda {@link org.apache.cayenne.access.types.ExtendedType} types.
+ *
+ * @since 4.0
+ */
+public class JodaModule implements Module {
+
+    @Override
+    public void configure(Binder binder) {
+        ServerModule.contributeDefaultTypes(binder)
+                .add(new DateTimeType())
+                .add(new LocalDateType())
+                .add(new LocalTimeType())
+                .add(new LocalDateTimeType());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/08fc9f42/cayenne-joda/src/main/java/org/apache/cayenne/joda/JodaModuleProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-joda/src/main/java/org/apache/cayenne/joda/JodaModuleProvider.java b/cayenne-joda/src/main/java/org/apache/cayenne/joda/JodaModuleProvider.java
new file mode 100644
index 0000000..9c9e204
--- /dev/null
+++ b/cayenne-joda/src/main/java/org/apache/cayenne/joda/JodaModuleProvider.java
@@ -0,0 +1,43 @@
+/*
+ *    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.cayenne.joda;
+
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.di.spi.ModuleProvider;
+
+/**
+ * @since 4.0
+ */
+public class JodaModuleProvider implements ModuleProvider {
+
+    @Override
+    public Module module() {
+        return new JodaModule();
+    }
+
+    @Override
+    public Class<? extends Module> moduleType() {
+        return JodaModule.class;
+    }
+
+    @Override
+    public Class<? extends Module>[] overrides() {
+        return new Class[0];
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/08fc9f42/cayenne-joda/src/main/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider
----------------------------------------------------------------------
diff --git a/cayenne-joda/src/main/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider b/cayenne-joda/src/main/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider
new file mode 100644
index 0000000..a4e3ebb
--- /dev/null
+++ b/cayenne-joda/src/main/resources/META-INF/services/org.apache.cayenne.di.spi.ModuleProvider
@@ -0,0 +1 @@
+org.apache.cayenne.joda.JodaModuleProvider
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/08fc9f42/cayenne-joda/src/test/java/org/apache/cayenne/joda/JodaModuleIT.java
----------------------------------------------------------------------
diff --git a/cayenne-joda/src/test/java/org/apache/cayenne/joda/JodaModuleIT.java b/cayenne-joda/src/test/java/org/apache/cayenne/joda/JodaModuleIT.java
new file mode 100644
index 0000000..93282d7
--- /dev/null
+++ b/cayenne-joda/src/test/java/org/apache/cayenne/joda/JodaModuleIT.java
@@ -0,0 +1,122 @@
+/*****************************************************************
+ *   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.cayenne.joda;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.apache.cayenne.joda.db.DateTimeTestEntity;
+import org.apache.cayenne.joda.db.LocalDateTestEntity;
+import org.apache.cayenne.joda.db.LocalDateTimeTestEntity;
+import org.apache.cayenne.joda.db.LocalTimeTestEntity;
+import org.apache.cayenne.query.ObjectSelect;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.joda.time.LocalDateTime;
+import org.joda.time.LocalTime;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class JodaModuleIT {
+
+    private ServerRuntime runtime;
+
+    @Before
+    public void setUp() {
+        this.runtime = ServerRuntime.builder().addConfig("cayenne-joda.xml").build();
+    }
+
+    @Test
+    public void testJodaDateTime() {
+        ObjectContext context = runtime.newContext();
+
+        DateTimeTestEntity dateTimeTestEntity = context.newObject(DateTimeTestEntity.class);
+        DateTime dateTime = DateTime.now();
+        dateTimeTestEntity.setTimestamp(dateTime);
+
+        context.commitChanges();
+
+        DateTimeTestEntity testRead = ObjectSelect.query(DateTimeTestEntity.class).selectOne(context);
+
+        DateTime timestamp = testRead.getTimestamp();
+        assertNotNull(timestamp);
+        assertEquals(DateTime.class, timestamp.getClass());
+        assertEquals(dateTime, timestamp);
+    }
+
+    @Test
+    public void testJodaLocalDate() {
+        ObjectContext context = runtime.newContext();
+
+        LocalDateTestEntity localDateTestEntity = context.newObject(LocalDateTestEntity.class);
+        LocalDate localDate = LocalDate.now();
+        localDateTestEntity.setDate(localDate);
+
+        context.commitChanges();
+
+        LocalDateTestEntity testRead = ObjectSelect.query(LocalDateTestEntity.class).selectOne(context);
+
+        LocalDate date = testRead.getDate();
+        assertNotNull(date);
+        assertEquals(LocalDate.class, date.getClass());
+        assertEquals(localDate, date);
+    }
+
+    @Test
+    public void testJodaLocalTime() {
+        ObjectContext context = runtime.newContext();
+
+        LocalTimeTestEntity localTimeTestEntity = context.newObject(LocalTimeTestEntity.class);
+        LocalTime localTime = LocalTime.now();
+        localTimeTestEntity.setTime(localTime);
+
+        context.commitChanges();
+
+        LocalTimeTestEntity testRead = ObjectSelect.query(LocalTimeTestEntity.class).selectOne(context);
+
+        LocalTime time = testRead.getTime();
+        assertNotNull(time);
+        assertEquals(LocalTime.class, time.getClass());
+        assertEquals(localTime.getSecondOfMinute(), time.getSecondOfMinute());
+        assertEquals(localTime.getMinuteOfHour(), time.getMinuteOfHour());
+        assertEquals(localTime.getHourOfDay(), time.getHourOfDay());
+    }
+
+    @Test
+    public void testJodaLocalDateTime() {
+        ObjectContext context = runtime.newContext();
+
+        LocalDateTimeTestEntity localDateTimeTestEntity = context.newObject(LocalDateTimeTestEntity.class);
+        LocalDateTime localDateTime = LocalDateTime.now();
+        localDateTimeTestEntity.setTimestamp(localDateTime);
+
+        context.commitChanges();
+
+        LocalDateTimeTestEntity testRead = ObjectSelect.query(LocalDateTimeTestEntity.class).selectOne(context);
+
+        LocalDateTime timestamp = testRead.getTimestamp();
+        assertNotNull(timestamp);
+        assertEquals(LocalDateTime.class, timestamp.getClass());
+        assertEquals(localDateTime, timestamp);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/08fc9f42/cayenne-joda/src/test/java/org/apache/cayenne/joda/JodaModuleProviderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-joda/src/test/java/org/apache/cayenne/joda/JodaModuleProviderTest.java b/cayenne-joda/src/test/java/org/apache/cayenne/joda/JodaModuleProviderTest.java
new file mode 100644
index 0000000..a0cd1e5
--- /dev/null
+++ b/cayenne-joda/src/test/java/org/apache/cayenne/joda/JodaModuleProviderTest.java
@@ -0,0 +1,30 @@
+/*
+ *    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.cayenne.joda;
+
+import org.apache.cayenne.unit.util.ModuleProviderChecker;
+import org.junit.Test;
+
+public class JodaModuleProviderTest {
+
+    @Test
+    public void testProviderPresent() {
+        ModuleProviderChecker.testProviderPresent(JodaModuleProvider.class);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/08fc9f42/cayenne-joda/src/test/java/org/apache/cayenne/joda/JodaTimeIT.java
----------------------------------------------------------------------
diff --git a/cayenne-joda/src/test/java/org/apache/cayenne/joda/JodaTimeIT.java b/cayenne-joda/src/test/java/org/apache/cayenne/joda/JodaTimeIT.java
deleted file mode 100644
index 56040d3..0000000
--- a/cayenne-joda/src/test/java/org/apache/cayenne/joda/JodaTimeIT.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*****************************************************************
- *   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.cayenne.joda;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import java.sql.SQLException;
-
-import org.apache.cayenne.ObjectContext;
-import org.apache.cayenne.configuration.server.ServerRuntime;
-import org.apache.cayenne.di.Module;
-import org.apache.cayenne.joda.db.DateTimeTestEntity;
-import org.apache.cayenne.joda.db.LocalDateTestEntity;
-import org.apache.cayenne.joda.db.LocalDateTimeTestEntity;
-import org.apache.cayenne.joda.db.LocalTimeTestEntity;
-import org.apache.cayenne.query.ObjectSelect;
-import org.joda.time.DateTime;
-import org.joda.time.LocalDate;
-import org.joda.time.LocalDateTime;
-import org.joda.time.LocalTime;
-import org.junit.Before;
-import org.junit.Test;
-
-public class JodaTimeIT {
-
-	private ServerRuntime runtime;
-
-	@Before
-	public void setUp() throws Exception {
-		Module jodaModule = new CayenneJodaModule();
-		this.runtime = new ServerRuntime("cayenne-joda.xml", jodaModule);
-	}
-
-	@Test
-	public void testJodaDateTime() throws SQLException {
-		ObjectContext context = runtime.newContext();
-
-		DateTimeTestEntity dateTimeTestEntity = context.newObject(DateTimeTestEntity.class);
-		DateTime dateTime = DateTime.now();
-		dateTimeTestEntity.setTimestamp(dateTime);
-
-		context.commitChanges();
-
-		DateTimeTestEntity testRead = ObjectSelect.query(DateTimeTestEntity.class).selectOne(context);
-
-		DateTime timestamp = testRead.getTimestamp();
-		assertNotNull(timestamp);
-		assertEquals(DateTime.class, timestamp.getClass());
-		assertEquals(dateTime, timestamp);
-	}
-
-	@Test
-	public void testJodaLocalDate() {
-		ObjectContext context = runtime.newContext();
-
-		LocalDateTestEntity localDateTestEntity = context.newObject(LocalDateTestEntity.class);
-		LocalDate localDate = LocalDate.now();
-		localDateTestEntity.setDate(localDate);
-
-		context.commitChanges();
-
-		LocalDateTestEntity testRead = ObjectSelect.query(LocalDateTestEntity.class).selectOne(context);
-
-		LocalDate date = testRead.getDate();
-		assertNotNull(date);
-		assertEquals(LocalDate.class, date.getClass());
-		assertEquals(localDate, date);
-	}
-
-	@Test
-	public void testJodaLocalTime() {
-		ObjectContext context = runtime.newContext();
-
-		LocalTimeTestEntity localTimeTestEntity = context.newObject(LocalTimeTestEntity.class);
-		LocalTime localTime = LocalTime.now();
-		localTimeTestEntity.setTime(localTime);
-
-		context.commitChanges();
-
-		LocalTimeTestEntity testRead = ObjectSelect.query(LocalTimeTestEntity.class).selectOne(context);
-
-		LocalTime time = testRead.getTime();
-		assertNotNull(time);
-		assertEquals(LocalTime.class, time.getClass());
-		assertEquals(localTime.getSecondOfMinute(), time.getSecondOfMinute());
-		assertEquals(localTime.getMinuteOfHour(), time.getMinuteOfHour());
-		assertEquals(localTime.getHourOfDay(), time.getHourOfDay());
-	}
-
-	@Test
-	public void testJodaLocalDateTime() {
-		ObjectContext context = runtime.newContext();
-
-		LocalDateTimeTestEntity localDateTimeTestEntity = context.newObject(LocalDateTimeTestEntity.class);
-		LocalDateTime localDateTime = LocalDateTime.now();
-		localDateTimeTestEntity.setTimestamp(localDateTime);
-
-		context.commitChanges();
-
-		LocalDateTimeTestEntity testRead = ObjectSelect.query(LocalDateTimeTestEntity.class).selectOne(context);
-
-		LocalDateTime timestamp = testRead.getTimestamp();
-		assertNotNull(timestamp);
-		assertEquals(LocalDateTime.class, timestamp.getClass());
-		assertEquals(localDateTime, timestamp);
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/08fc9f42/docs/doc/src/main/resources/UPGRADE.txt
----------------------------------------------------------------------
diff --git a/docs/doc/src/main/resources/UPGRADE.txt b/docs/doc/src/main/resources/UPGRADE.txt
index e0b9c39..06be221 100644
--- a/docs/doc/src/main/resources/UPGRADE.txt
+++ b/docs/doc/src/main/resources/UPGRADE.txt
@@ -6,11 +6,15 @@ IMPORTANT: be sure to read all notes for the intermediate releases between your
 -------------------------------------------------------------------------------
 UPGRADING TO 4.0.M5
 
-* Per CAY-2166, Cayenne supports auto-loading of DI modules. The part of this is a tweak in DI service override policies.
-  In the previous 4.0 releases custom modules would override "builder" modules (i.e. implicit modules that wrap around
-  various customizations made in response to the builder method calls). It seemed logical to reverse this order, and
-  let builder modules override custom modules. As the builder is invoked explicitly when the stack assembly is performed,
-  while modules can be written without any knowledge of the final stack.
+* Per CAY-2166, Cayenne supports auto-loading of DI modules. There are a few changes worth mentioning:
+  - Service override policies. In the previous 4.0 milestones custom modules would override "builder" modules
+  (i.e. implicit modules that wrap around various customizations made in response to the builder method calls). It
+  seemed logical to reverse this order, and let builder modules override custom modules. As the builder is
+  invoked explicitly when the stack assembly is performed, while modules can be written without any knowledge of the
+  final stack.
+  - Module Renaming and Explicit Loading of Modules. If you see compile errors (class not found for CayenneJodaModule,
+   CayenneJava8Module), just remove explicit loading of those modules. They will be autoloade if they are on classpath.
+   If you explicitly turned off auto-loading, use the new names for these modules: JodaModule and Java8Module.
 
 * Per CAY-2164, creating a ServerRuntimeBuilder is done via a static method on ServerRuntime ("ServerRuntime.builder()").
   The previous style (ServerRuntimeBuilder.builder()) is deprecated and will soon be removed, so you should replace it