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 18:00:02 UTC

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

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);
-    }
 }