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

abulatski pushed a commit to branch STABLE-4.1
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 5c9b194d0ca7c11b56fe5f387947cfc74efdb835
Author: Nikita Timofeev <stari...@gmail.com>
AuthorDate: Thu Jun 6 18:09:47 2019 +0300

    CAY-2584 Crypto: can't use ColumnSelect with encrypted columns
---
 RELEASE-NOTES.txt                                  |   1 +
 .../reader/CryptoRowReaderFactoryDecorator.java    | 155 ++++++++++++++++-----
 .../apache/cayenne/crypto/Runtime_AES128_IT.java   | 100 +++++++++++++
 .../cayenne/access/jdbc/ColumnDescriptor.java      |   4 +
 .../jdbc/reader/DefaultRowReaderFactory.java       |  10 +-
 .../translator/select/DefaultSelectTranslator.java |  26 ++--
 6 files changed, 246 insertions(+), 50 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index d0ac1c6..144aa21 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -16,6 +16,7 @@ Bug Fixes:
 
 CAY-2573 DI field injection is triggered when creating sql Driver
 CAY-2582 Double insert of manyToMany relationship mapped to Set
+CAY-2584 Crypto: can't use ColumnSelect with encrypted columns
 
 ----------------------------------
 Release: 4.1.B2
diff --git 
a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/reader/CryptoRowReaderFactoryDecorator.java
 
b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/reader/CryptoRowReaderFactoryDecorator.java
index ab2c08d..e60600a 100644
--- 
a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/reader/CryptoRowReaderFactoryDecorator.java
+++ 
b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/reader/CryptoRowReaderFactoryDecorator.java
@@ -20,6 +20,7 @@ package org.apache.cayenne.crypto.reader;
 
 import org.apache.cayenne.access.jdbc.ColumnDescriptor;
 import org.apache.cayenne.access.jdbc.RowDescriptor;
+import org.apache.cayenne.access.jdbc.reader.DefaultRowReaderFactory;
 import org.apache.cayenne.access.jdbc.reader.RowReader;
 import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
 import org.apache.cayenne.access.types.ExtendedType;
@@ -27,68 +28,70 @@ import org.apache.cayenne.access.types.ExtendedTypeMap;
 import org.apache.cayenne.crypto.map.ColumnMapper;
 import org.apache.cayenne.crypto.transformer.MapTransformer;
 import org.apache.cayenne.crypto.transformer.TransformerFactory;
+import org.apache.cayenne.crypto.transformer.bytes.BytesDecryptor;
+import org.apache.cayenne.crypto.transformer.bytes.BytesTransformerFactory;
+import org.apache.cayenne.crypto.transformer.value.ValueDecryptor;
+import org.apache.cayenne.crypto.transformer.value.ValueTransformerFactory;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.query.EntityResultSegment;
 import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.query.ScalarResultSegment;
 
 import java.sql.ResultSet;
 import java.util.Map;
 
-public class CryptoRowReaderFactoryDecorator implements RowReaderFactory {
+public class CryptoRowReaderFactoryDecorator extends DefaultRowReaderFactory {
 
-    private RowReaderFactory delegate;
     private TransformerFactory transformerFactory;
     private ColumnMapper columnMapper;
+    private BytesTransformerFactory bytesTransformerFactory;
+    private ValueTransformerFactory valueTransformerFactory;
 
     public CryptoRowReaderFactoryDecorator(@Inject RowReaderFactory delegate,
                                            @Inject TransformerFactory 
transformerFactory,
-                                           @Inject ColumnMapper columnMapper) {
-        this.delegate = delegate;
+                                           @Inject ColumnMapper columnMapper,
+                                           @Inject BytesTransformerFactory 
bytesTransformerFactory,
+                                           @Inject ValueTransformerFactory 
valueTransformerFactory) {
         this.transformerFactory = transformerFactory;
         this.columnMapper = columnMapper;
+        this.bytesTransformerFactory = bytesTransformerFactory;
+        this.valueTransformerFactory = valueTransformerFactory;
     }
 
     @Override
-    public RowReader<?> rowReader(final RowDescriptor descriptor, 
QueryMetadata queryMetadata, DbAdapter adapter,
+    public RowReader<?> rowReader(RowDescriptor descriptor, QueryMetadata 
queryMetadata, DbAdapter adapter,
                                   Map<ObjAttribute, ColumnDescriptor> 
attributeOverrides) {
+        RowDescriptor encryptedRowDescriptor = 
encryptedRowDescriptor(descriptor, adapter.getExtendedTypes());
+        return super.rowReader(encryptedRowDescriptor, queryMetadata, adapter, 
attributeOverrides);
+    }
 
-        final RowReader<?> delegateReader = 
delegate.rowReader(encryptedRowDescriptor(descriptor, 
adapter.getExtendedTypes()),
-                queryMetadata,
-                adapter,
-                attributeOverrides);
-
-        return new RowReader<Object>() {
-
-            private boolean decryptorCompiled;
-            private MapTransformer decryptor;
-
-            private void ensureDecryptorCompiled(Object row) {
-                if (!decryptorCompiled) {
-                    decryptor = 
transformerFactory.decryptor(descriptor.getColumns(), row);
-                    decryptorCompiled = true;
-                }
-            }
-
-            @Override
-            public Object readRow(ResultSet resultSet) {
-                Object row = delegateReader.readRow(resultSet);
-
-                ensureDecryptorCompiled(row);
-
-                if (decryptor != null) {
-
-                    @SuppressWarnings({"unchecked", "rawtypes"})
-                    Map<String, Object> map = (Map) row;
+    @Override
+    protected RowReader<?> createScalarRowReader(RowDescriptor descriptor, 
QueryMetadata queryMetadata,
+                                                 ScalarResultSegment segment) {
+        RowReader<?> scalarRowReader = super
+                .createScalarRowReader(descriptor, queryMetadata, segment);
+        return new 
DecoratedScalarRowReader(descriptor.getColumns()[segment.getColumnOffset()], 
scalarRowReader);
+    }
 
-                    decryptor.transform(map);
-                }
+    @Override
+    protected RowReader<?> createEntityRowReader(RowDescriptor descriptor, 
QueryMetadata queryMetadata,
+                                                 EntityResultSegment 
resultMetadata,
+                                                 PostprocessorFactory 
postProcessorFactory) {
+        RowReader<?> entityRowReader = super
+                .createEntityRowReader(descriptor, queryMetadata, 
resultMetadata, postProcessorFactory);
+        return new DecoratedFullRowReader(descriptor, entityRowReader);
+    }
 
-                return row;
-            }
-        };
+    @Override
+    protected RowReader<?> createFullRowReader(RowDescriptor descriptor, 
QueryMetadata queryMetadata,
+                                               PostprocessorFactory 
postProcessorFactory) {
+        RowReader<?> fullRowReader = super
+                .createFullRowReader(descriptor, queryMetadata, 
postProcessorFactory);
+        return new DecoratedFullRowReader(descriptor, fullRowReader);
     }
 
     protected RowDescriptor encryptedRowDescriptor(RowDescriptor descriptor, 
ExtendedTypeMap typeMap) {
@@ -121,6 +124,82 @@ public class CryptoRowReaderFactoryDecorator implements 
RowReaderFactory {
             encryptedConverters[i] = t;
         }
 
-        return new RowDescriptor(originalColumns, encryptedConverters);
+        return new DecoratedRowDescriptor(descriptor, originalColumns, 
encryptedConverters);
+    }
+
+    private static class DecoratedRowDescriptor extends RowDescriptor {
+
+        private final RowDescriptor original;
+
+        DecoratedRowDescriptor(RowDescriptor rowDescriptor, ColumnDescriptor[] 
columns, ExtendedType[] converters) {
+            this.original = rowDescriptor;
+            this.columns = columns;
+            this.converters = converters;
+        }
+
+        public RowDescriptor unwrap() {
+            return original;
+        }
+    }
+
+    private class DecoratedScalarRowReader implements RowReader<Object> {
+        private final RowReader<?> delegateReader;
+        private final ValueDecryptor valueDecryptor;
+        private final BytesDecryptor bytesDecryptor;
+
+        DecoratedScalarRowReader(ColumnDescriptor descriptor, RowReader<?> 
delegateReader) {
+            this.delegateReader = delegateReader;
+            if(descriptor.getAttribute() != null && 
columnMapper.isEncrypted(descriptor.getAttribute())) {
+                this.valueDecryptor = 
valueTransformerFactory.decryptor(descriptor.getAttribute());
+                this.bytesDecryptor = bytesTransformerFactory.decryptor();
+            } else {
+                this.valueDecryptor = null;
+                this.bytesDecryptor = null;
+            }
+        }
+
+        @Override
+        public Object readRow(ResultSet resultSet) {
+            Object value = delegateReader.readRow(resultSet);
+            if(valueDecryptor == null) {
+                return value;
+            }
+            return valueDecryptor.decrypt(bytesDecryptor, value);
+        }
+    }
+
+    private class DecoratedFullRowReader implements RowReader<Object> {
+
+        private final RowDescriptor descriptor;
+        private final RowReader<?> delegateReader;
+        private boolean decryptorCompiled;
+        private MapTransformer decryptor;
+
+        DecoratedFullRowReader(RowDescriptor descriptor, RowReader<?> 
delegateReader) {
+            this.descriptor = descriptor;
+            this.delegateReader = delegateReader;
+        }
+
+        private void ensureDecryptorCompiled(Object row) {
+            if (!decryptorCompiled) {
+                decryptor = 
transformerFactory.decryptor(descriptor.getColumns(), row);
+                decryptorCompiled = true;
+            }
+        }
+
+        @Override
+        public Object readRow(ResultSet resultSet) {
+            Object row = delegateReader.readRow(resultSet);
+
+            ensureDecryptorCompiled(row);
+
+            if (decryptor != null) {
+                @SuppressWarnings("unchecked")
+                Map<String, Object> map = (Map<String, Object>) row;
+                decryptor.transform(map);
+            }
+
+            return row;
+        }
     }
 }
diff --git 
a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_IT.java 
b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_IT.java
index 1e4b4bc..b6f75d3 100644
--- 
a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_IT.java
+++ 
b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_IT.java
@@ -23,6 +23,8 @@ import org.apache.cayenne.crypto.db.Table1;
 import org.apache.cayenne.crypto.db.Table2;
 import org.apache.cayenne.crypto.transformer.value.IntegerConverter;
 import org.apache.cayenne.crypto.unit.CryptoUnitUtils;
+import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.query.ObjectSelect;
 import org.apache.cayenne.query.SelectQuery;
 import org.junit.Before;
 import org.junit.Test;
@@ -156,4 +158,102 @@ public class Runtime_AES128_IT extends 
Runtime_AES128_Base {
         assertEquals(61, result.get(0).getCryptoInt());
     }
 
+    @Test
+    public void test_ColumnQueryObject() {
+
+        ObjectContext context = runtime.newContext();
+
+        Table1 t1 = context.newObject(Table1.class);
+        t1.setCryptoInt(1);
+        t1.setCryptoString("test");
+        context.commitChanges();
+
+        List<Table1> result = ObjectSelect
+                .columnQuery(Table1.class, Property.createSelf(Table1.class))
+                .select(context);
+
+        assertEquals(1, result.size());
+        assertEquals(1, result.get(0).getCryptoInt());
+        assertEquals("test", result.get(0).getCryptoString());
+    }
+
+    @Test
+    public void test_ColumnQueryObjectWithPlainScalar() {
+
+        ObjectContext context = runtime.newContext();
+
+        Table1 t1 = context.newObject(Table1.class);
+        t1.setCryptoInt(1);
+        t1.setPlainInt(2);
+        t1.setCryptoString("test");
+        context.commitChanges();
+
+        List<Object[]> result = ObjectSelect
+                .columnQuery(Table1.class, Property.createSelf(Table1.class), 
Table1.PLAIN_INT)
+                .select(context);
+
+        assertEquals(1, result.size());
+        assertEquals(1, ((Table1)result.get(0)[0]).getCryptoInt());
+        assertEquals("test", ((Table1)result.get(0)[0]).getCryptoString());
+        assertEquals(2, result.get(0)[1]);
+    }
+
+    @Test
+    public void test_ColumnQueryObjectWithEncryptedScalar() {
+
+        ObjectContext context = runtime.newContext();
+
+        Table1 t1 = context.newObject(Table1.class);
+        t1.setCryptoInt(1);
+        t1.setPlainInt(2);
+        t1.setCryptoString("test");
+        context.commitChanges();
+
+        List<Object[]> result = ObjectSelect
+                .columnQuery(Table1.class, Property.createSelf(Table1.class), 
Table1.CRYPTO_INT)
+                .select(context);
+
+        assertEquals(1, result.size());
+        assertEquals(1, ((Table1)result.get(0)[0]).getCryptoInt());
+        assertEquals("test", ((Table1)result.get(0)[0]).getCryptoString());
+        assertEquals(1, result.get(0)[1]);
+    }
+
+    @Test
+    public void test_ColumnQuerySingleScalar() {
+        ObjectContext context = runtime.newContext();
+
+        Table1 t1 = context.newObject(Table1.class);
+        t1.setCryptoInt(1);
+        t1.setCryptoString("test");
+        context.commitChanges();
+
+        List<String> result = ObjectSelect
+                .columnQuery(Table1.class, Table1.CRYPTO_STRING)
+                .select(context);
+
+        assertEquals(1, result.size());
+        assertEquals("test", result.get(0));
+    }
+
+    @Test
+    public void test_ColumnQueryMultipleScalars() {
+        ObjectContext context = runtime.newContext();
+
+        Table1 t1 = context.newObject(Table1.class);
+        t1.setCryptoInt(1);
+        t1.setCryptoString("test");
+        t1.setPlainInt(2);
+        context.commitChanges();
+
+        List<Object[]> result = ObjectSelect
+                .columnQuery(Table1.class, Table1.CRYPTO_STRING, 
Table1.CRYPTO_INT, Table1.PLAIN_INT)
+                .select(context);
+
+        assertEquals(1, result.size());
+        assertEquals("test", result.get(0)[0]);
+        assertEquals(1, result.get(0)[1]);
+        assertEquals(2, result.get(0)[2]);
+    }
+
 }
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java
index 632f5e3..cd8d1fc 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java
@@ -149,6 +149,10 @@ public class ColumnDescriptor {
         return name;
     }
 
+    public void setAttribute(DbAttribute attribute) {
+        this.attribute = attribute;
+    }
+
     /**
      * Returns a DbAttribute for this column. Since columns descriptors can be
      * initialized in a context where a DbAttribite is unknown, this method may
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java
index 36c7027..507cbbb 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java
@@ -70,7 +70,7 @@ public class DefaultRowReaderFactory implements 
RowReaderFactory {
                                return createEntityRowReader(descriptor, 
queryMetadata, (EntityResultSegment) segment,
                                                postProcessorFactory);
                        } else {
-                               return new ScalarRowReader<>(descriptor, 
(ScalarResultSegment) segment);
+                               return createScalarRowReader(descriptor, 
queryMetadata, (ScalarResultSegment) segment);
                        }
                } else {
                        CompoundRowReader reader = new 
CompoundRowReader(resultWidth);
@@ -84,7 +84,7 @@ public class DefaultRowReaderFactory implements 
RowReaderFactory {
                                                        
createEntityRowReader(descriptor, queryMetadata, (EntityResultSegment) segment,
                                                                        
postProcessorFactory));
                                } else {
-                                       reader.addRowReader(i, new 
ScalarRowReader<>(descriptor, (ScalarResultSegment) segment));
+                                       reader.addRowReader(i, 
createScalarRowReader(descriptor, queryMetadata, (ScalarResultSegment) 
segment));
                                }
                        }
 
@@ -92,7 +92,11 @@ public class DefaultRowReaderFactory implements 
RowReaderFactory {
                }
        }
 
-       private RowReader<?> createEntityRowReader(RowDescriptor descriptor, 
QueryMetadata queryMetadata,
+       protected RowReader<?> createScalarRowReader(RowDescriptor descriptor, 
QueryMetadata queryMetadata, ScalarResultSegment segment) {
+               return new ScalarRowReader<Object>(descriptor, segment);
+       }
+
+       protected RowReader<?> createEntityRowReader(RowDescriptor descriptor, 
QueryMetadata queryMetadata,
                        EntityResultSegment resultMetadata, 
PostprocessorFactory postProcessorFactory) {
 
                if (queryMetadata.getPageSize() > 0) {
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
index 4a2c591..2eefd81 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
@@ -437,7 +437,8 @@ public class DefaultSelectTranslator extends QueryAssembler 
implements SelectTra
 
                for(Property<?> property : query.getColumns()) {
                        joinTableAliasForProperty[0] = null;
-                       int expressionType = property.getExpression().getType();
+                       Expression propertyExpression = 
property.getExpression();
+                       int expressionType = propertyExpression.getType();
 
                        // forbid direct selection of toMany relationships 
columns
                        if(property.getType() != null && (expressionType == 
Expression.OBJ_PATH || expressionType == Expression.DB_PATH)
@@ -456,7 +457,7 @@ public class DefaultSelectTranslator extends QueryAssembler 
implements SelectTra
                        // Qualifier Translator in case of Object Columns have 
side effect -
                        // it will create required joins, that we catch with 
listener above.
                        // And we force created join alias for all columns of 
Object we select.
-                       
qualifierTranslator.setQualifier(property.getExpression());
+                       qualifierTranslator.setQualifier(propertyExpression);
                        
qualifierTranslator.setForceJoinForRelations(objectProperty);
                        StringBuilder builder = 
qualifierTranslator.appendPart(new StringBuilder());
 
@@ -481,13 +482,15 @@ public class DefaultSelectTranslator extends 
QueryAssembler implements SelectTra
                                        builder.append(" AS ").append(alias);
                                }
 
-                               int type = getJdbcTypeForProperty(property);
+                               DbAttribute attribute = 
getAttributeForProperty(propertyExpression);
+                               int type = attribute == null ? 
getJdbcTypeForProperty(property) : attribute.getType();
                                ColumnDescriptor descriptor;
                                if(property.getType() != null) {
                                        descriptor = new 
ColumnDescriptor(builder.toString(), type, 
property.getType().getCanonicalName());
                                } else {
                                        descriptor = new 
ColumnDescriptor(builder.toString(), type);
                                }
+                               descriptor.setAttribute(attribute);
                                descriptor.setDataRowKey(alias);
                                descriptor.setIsExpression(true);
                                columns.add(descriptor);
@@ -508,18 +511,18 @@ public class DefaultSelectTranslator extends 
QueryAssembler implements SelectTra
                return columns;
        }
 
-       private int getJdbcTypeForProperty(Property<?> property) {
-               int expressionType = property.getExpression().getType();
+       private DbAttribute getAttributeForProperty(Expression 
propertyExpression) {
+               int expressionType = propertyExpression.getType();
                if(expressionType == Expression.OBJ_PATH) {
                        // Scan obj path, stop as soon as DbAttribute found
                        for (PathComponent<ObjAttribute, ObjRelationship> 
component :
-                                       
getQueryMetadata().getObjEntity().resolvePath(property.getExpression(), 
getPathAliases())) {
+                                       
getQueryMetadata().getObjEntity().resolvePath(propertyExpression, 
getPathAliases())) {
                                if(component.getAttribute() != null) {
                                        Iterator<CayenneMapEntry> 
dbPathIterator = component.getAttribute().getDbPathIterator();
                                        while (dbPathIterator.hasNext()) {
                                                Object pathPart = 
dbPathIterator.next();
                                                if (pathPart instanceof 
DbAttribute) {
-                                                       return ((DbAttribute) 
pathPart).getType();
+                                                       return ((DbAttribute) 
pathPart);
                                                }
                                        }
                                }
@@ -527,12 +530,17 @@ public class DefaultSelectTranslator extends 
QueryAssembler implements SelectTra
                } else if(expressionType == Expression.DB_PATH) {
                        // Scan db path, stop as soon as DbAttribute found
                        for (PathComponent<DbAttribute, DbRelationship> 
component :
-                                       
getQueryMetadata().getDbEntity().resolvePath(property.getExpression(), 
getPathAliases())) {
+                                       
getQueryMetadata().getDbEntity().resolvePath(propertyExpression, 
getPathAliases())) {
                                if(component.getAttribute() != null) {
-                                       return 
component.getAttribute().getType();
+                                       return component.getAttribute();
                                }
                        }
                }
+
+               return null;
+       }
+
+       private int getJdbcTypeForProperty(Property<?> property) {
                // NOTE: If no attribute found or expression have some other 
type
                // return JDBC type based on Java type of the property.
                // This can lead to incorrect behavior in case we deal with 
some custom type

Reply via email to