killbill-aplcache

start of property validation

2/28/2012 2:41:29 PM

Details

beatrix/pom.xml 2(+1 -1)

diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index 64a7b38..654c129 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -120,7 +120,7 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
                 <configuration>
-                    <groups>fast,slow</groups>
+                    <groups>fast,slow, stress</groups>
                 </configuration>
             </plugin>
             <plugin>
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBasic.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBasic.java
index cdd7464..45c58d8 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBasic.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBasic.java
@@ -266,7 +266,7 @@ public class TestBasic {
         Thread.sleep(600000);
     }
 
-    @Test(groups = "stress", enabled = false)
+    @Test(groups = "stress", enabled = true)
     public void stressTest() throws Exception {
         final int maxIterations = 7;
         for (int curIteration = 0; curIteration < maxIterations; curIteration++) {
diff --git a/util/src/main/java/com/ning/billing/util/validation/ColumnInfo.java b/util/src/main/java/com/ning/billing/util/validation/ColumnInfo.java
new file mode 100644
index 0000000..31d8bf0
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/validation/ColumnInfo.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.util.validation;
+
+public class ColumnInfo {
+    private final String tableName;
+    private final String columnName;
+    private final int scale;
+    private final int precision;
+    private final boolean isNullable;
+
+    public ColumnInfo(String tableName, String columnName, int scale, int precision, boolean nullable) {
+        this.tableName = tableName;
+        this.columnName = columnName;
+        this.scale = scale;
+        this.precision = precision;
+        isNullable = nullable;
+    }
+
+    public String getTableName() {
+        return tableName;
+    }
+
+    public String getColumnName() {
+        return columnName;
+    }
+
+    public int getScale() {
+        return scale;
+    }
+
+    public int getPrecision() {
+        return precision;
+    }
+
+    public boolean getIsNullable() {
+        return isNullable;
+    }
+}
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/validation/dao/DatabaseSchemaDao.java b/util/src/main/java/com/ning/billing/util/validation/dao/DatabaseSchemaDao.java
new file mode 100644
index 0000000..c5f88e3
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/validation/dao/DatabaseSchemaDao.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.util.validation.dao;
+
+import com.google.inject.Inject;
+import com.ning.billing.util.validation.ColumnInfo;
+import org.skife.jdbi.v2.IDBI;
+
+import java.util.List;
+
+public class DatabaseSchemaDao {
+    private final DatabaseSchemaSqlDao dao;
+
+    @Inject
+    public DatabaseSchemaDao(IDBI dbi) {
+        this.dao = dbi.onDemand(DatabaseSchemaSqlDao.class);
+    }
+
+    public List<ColumnInfo> getColumnInfoList(final String schemaName) {
+        return dao.getSchemaInfo(schemaName);
+    }
+}
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/validation/dao/DatabaseSchemaSqlDao.java b/util/src/main/java/com/ning/billing/util/validation/dao/DatabaseSchemaSqlDao.java
new file mode 100644
index 0000000..250ecce
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/validation/dao/DatabaseSchemaSqlDao.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.util.validation.dao;
+
+import com.ning.billing.util.validation.ColumnInfo;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+@ExternalizedSqlViaStringTemplate3
+@RegisterMapper(DatabaseSchemaSqlDao.ColumnInfoMapper.class)
+public interface DatabaseSchemaSqlDao {
+    @SqlQuery
+    List<ColumnInfo> getSchemaInfo(@Bind("schemaName") final String schemaName);
+
+    class ColumnInfoMapper implements ResultSetMapper<ColumnInfo> {
+        @Override
+        public ColumnInfo map(int index, ResultSet r, StatementContext ctx) throws SQLException {
+            final String tableName = r.getString("table_name");
+            final String columnName = r.getString("column_name");
+            final Integer scale = r.getInt("numeric_scale");
+            final Integer precision = r.getInt("numeric_precision");
+            final boolean isNullable = r.getBoolean("is_nullable");
+
+            return new ColumnInfo(tableName, columnName, scale, precision, isNullable);
+        }
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/validation/ValidationConfiguration.java b/util/src/main/java/com/ning/billing/util/validation/ValidationConfiguration.java
new file mode 100644
index 0000000..7d0d926
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/validation/ValidationConfiguration.java
@@ -0,0 +1,14 @@
+package com.ning.billing.util.validation;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ValidationConfiguration extends HashMap<String, ColumnInfo> {
+    public void addMapping(String propertyName, ColumnInfo columnInfo) {
+        super.put(propertyName, columnInfo);
+    }
+
+    public boolean hasMapping(String propertyName) {
+        return super.get(propertyName) != null;
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/validation/ValidationManager.java b/util/src/main/java/com/ning/billing/util/validation/ValidationManager.java
new file mode 100644
index 0000000..4bd8e8e
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/validation/ValidationManager.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.util.validation;
+
+import com.google.inject.Inject;
+import com.ning.billing.util.validation.dao.DatabaseSchemaDao;
+
+import java.lang.reflect.Field;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ValidationManager {
+    private final DatabaseSchemaDao dao;
+
+    // table name, string name, column info
+    private final Map<String, Map<String, ColumnInfo>> columnInfoMap = new HashMap<String, Map<String, ColumnInfo>>();
+    private final Map<Class, ValidationConfiguration> configurations = new HashMap<Class, ValidationConfiguration>();
+
+    @Inject
+    public ValidationManager(DatabaseSchemaDao dao) {
+        this.dao = dao;
+    }
+
+    // replaces existing schema information with the information for the specified schema
+    public void loadSchemaInformation(final String schemaName) {
+        columnInfoMap.clear();
+
+        // get schema information and map it to columnInfo
+        List<ColumnInfo> columnInfoList = dao.getColumnInfoList(schemaName);
+        for (ColumnInfo columnInfo : columnInfoList) {
+            final String tableName = columnInfo.getTableName();
+
+            if (!columnInfoMap.containsKey(tableName)) {
+                columnInfoMap.put(tableName, new HashMap<String, ColumnInfo>());
+            }
+
+            columnInfoMap.get(tableName).put(columnInfo.getColumnName(), columnInfo);
+        }
+    }
+
+    public Collection<ColumnInfo> getTableInfo(final String tableName) {
+        return columnInfoMap.get(tableName).values();
+    }
+
+    public ColumnInfo getColumnInfo(final String tableName, final String columnName) {
+        return (columnInfoMap.get(tableName) == null) ? null : columnInfoMap.get(tableName).get(columnName);
+    }
+
+    public boolean validate(Object o) {
+        ValidationConfiguration configuration = getConfiguration(o.getClass());
+
+        // if no configuration exists for this class, the object is valid
+        if (configuration == null) {return true;}
+
+        Class clazz = o.getClass();
+        for (String propertyName : configuration.keySet()) {
+            try {
+                Field field = clazz.getDeclaredField(propertyName);
+                if (!field.isAccessible()) {
+                    field.setAccessible(true);
+                }
+
+                Object value = field.get(o);
+
+                ColumnInfo columnInfo = configuration.get(propertyName);
+                if (columnInfo == null) {
+                    // no column info means the property hasn't been properly mapped; suppress validation
+                    return true;
+                }
+
+                if (!columnInfo.getIsNullable()) {
+                    if (value == null) {return false;}
+                }
+            } catch (NoSuchFieldException e) {
+                // if the field doesn't exist, assume the configuration is faulty and skip this property
+            } catch (IllegalAccessException e) {
+                // TODO: something? deliberate no op?
+            }
+
+        }
+
+        return true;
+    }
+
+    public boolean hasConfiguration(Class clazz) {
+        return configurations.containsKey(clazz);
+    }
+
+    public ValidationConfiguration getConfiguration(Class clazz) {
+        return configurations.get(clazz);
+    }
+
+    public void setConfiguration(Class clazz, String propertyName, ColumnInfo columnInfo) {
+        if (!configurations.containsKey(clazz)) {
+            configurations.put(clazz, new ValidationConfiguration());
+        }
+
+        configurations.get(clazz).addMapping(propertyName, columnInfo);
+    }
+}
diff --git a/util/src/main/resources/com/ning/billing/util/validation/dao/DatabaseSchemaSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/validation/dao/DatabaseSchemaSqlDao.sql.stg
new file mode 100644
index 0000000..1008369
--- /dev/null
+++ b/util/src/main/resources/com/ning/billing/util/validation/dao/DatabaseSchemaSqlDao.sql.stg
@@ -0,0 +1,10 @@
+group DatabaseSchemaSqlDao;
+
+getSchemaInfo() ::= <<
+    SELECT TABLE_NAME, COLUMN_NAME, IS_NULLABLE, DATA_TYPE,
+    CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE
+    FROM information_schema.columns
+    WHERE TABLE_SCHEMA = :schemaName
+    ORDER BY TABLE_NAME, COLUMN_NAME;
+>>
+
diff --git a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
index 93814f3..9237997 100644
--- a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
+++ b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
@@ -160,4 +160,8 @@ public class MysqlTestingHelper
             }
         });
     }
+
+    public String getDbName() {
+        return DB_NAME;
+    }
 }
diff --git a/util/src/test/java/com/ning/billing/util/validation/TestValidationManager.java b/util/src/test/java/com/ning/billing/util/validation/TestValidationManager.java
new file mode 100644
index 0000000..f664da3
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/validation/TestValidationManager.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.util.validation;
+
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.util.validation.dao.DatabaseSchemaDao;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.IDBI;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+public class TestValidationManager {
+    private final MysqlTestingHelper helper = new MysqlTestingHelper();
+    private static final String TABLE_NAME = "validation_test";
+
+    private ValidationManager vm;
+    
+    @BeforeClass(alwaysRun = true)
+    public void setup() throws IOException {
+        setupDatabase();
+        setupDao();
+    }
+
+    private void setupDao() {
+        IDBI dbi = helper.getDBI();
+        DatabaseSchemaDao dao = new DatabaseSchemaDao(dbi);
+        vm = new ValidationManager(dao);
+        vm.loadSchemaInformation(helper.getDbName());
+    }
+
+    private void setupDatabase() throws IOException {
+        helper.startMysql();
+        StringBuilder ddl = new StringBuilder();
+        ddl.append(String.format("DROP TABLE IF EXISTS %s;", TABLE_NAME));
+        ddl.append(String.format("CREATE TABLE %s (column1 varchar(1), column2 char(2) NOT NULL, column3 numeric(10,4), column4 datetime) ENGINE = innodb;", TABLE_NAME));
+        helper.initDb(ddl.toString());
+    }
+
+    @AfterClass(alwaysRun = true)
+    public void tearDown() {
+        stopDatabase();
+    }
+
+    private void stopDatabase() {
+        helper.stopMysql();
+    }
+
+    @Test
+    public void testRetrievingColumnInfo() {
+        Collection<ColumnInfo> columnInfoList = vm.getTableInfo(TABLE_NAME);
+        assertEquals(columnInfoList.size(), 4);
+        assertNotNull(vm.getColumnInfo(TABLE_NAME, "column1"));
+        assertNull(vm.getColumnInfo(TABLE_NAME, "bogus"));
+
+        ColumnInfo numericColumnInfo = vm.getColumnInfo(TABLE_NAME, "column3");
+        assertNotNull(numericColumnInfo);
+        assertEquals(numericColumnInfo.getScale(), 4);
+        assertEquals(numericColumnInfo.getPrecision(), 10);
+    }
+
+    @Test
+    public void testSimpleConfiguration() {
+        String STRING_FIELD_2 = "column2";
+        String STRING_FIELD_2_PROPERTY = "stringField2";
+
+        SimpleTestClass testObject = new SimpleTestClass("a", null, 7.9, new DateTime());
+
+        vm.setConfiguration(testObject.getClass(), STRING_FIELD_2_PROPERTY, vm.getColumnInfo(TABLE_NAME, STRING_FIELD_2));
+
+        assertTrue(vm.hasConfiguration(testObject.getClass()));
+        assertFalse(vm.hasConfiguration(ValidationManager.class));
+
+        ValidationConfiguration configuration = vm.getConfiguration(SimpleTestClass.class);
+        assertNotNull(configuration);
+        assertTrue(configuration.hasMapping(STRING_FIELD_2_PROPERTY));
+
+        assertFalse(vm.validate(testObject));
+        testObject.setStringField2("ab");
+        assertTrue(vm.validate(testObject));
+
+    }
+
+    private class SimpleTestClass {
+        private String stringField1;
+        private String stringField2;
+        private double numericField1;
+        private DateTime dateTimeField1;
+
+        public SimpleTestClass(String stringField1, String stringField2, double numericField1, DateTime dateTimeField1) {
+            this.stringField1 = stringField1;
+            this.stringField2 = stringField2;
+            this.numericField1 = numericField1;
+            this.dateTimeField1 = dateTimeField1;
+        }
+
+        public String getStringField1() {
+            return stringField1;
+        }
+
+        public void setStringField1(String stringField1) {
+            this.stringField1 = stringField1;
+        }
+
+        public String getStringField2() {
+            return stringField2;
+        }
+
+        public void setStringField2(String stringField2) {
+            this.stringField2 = stringField2;
+        }
+
+        public double getNumericField1() {
+            return numericField1;
+        }
+
+        public void setNumericField1(double numericField1) {
+            this.numericField1 = numericField1;
+        }
+
+        public DateTime getDateTimeField1() {
+            return dateTimeField1;
+        }
+
+        public void setDateTimeField1(DateTime dateTimeField1) {
+            this.dateTimeField1 = dateTimeField1;
+        }
+    }
+}