org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsDomainBinderTests.java Source code

Java tutorial

Introduction

Here is the source code for org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsDomainBinderTests.java

Source

/*
 * Copyright 2004-2005 the original author or authors.
 * 
 * Licensed 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.codehaus.groovy.grails.orm.hibernate.cfg;

import groovy.lang.GroovyClassLoader;
import groovy.lang.IntRange;
import groovy.lang.ObjectRange;
import junit.framework.TestCase;
import org.codehaus.groovy.grails.commons.DefaultGrailsApplication;
import org.codehaus.groovy.grails.commons.DefaultGrailsDomainClass;
import org.codehaus.groovy.grails.commons.GrailsApplication;
import org.codehaus.groovy.grails.commons.GrailsDomainClass;
import org.codehaus.groovy.grails.validation.ConstrainedProperty;
import org.codehaus.groovy.grails.validation.TestClass;
import org.hibernate.mapping.*;
import org.hibernate.mapping.Table;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * @author Jason Rudolph
 * @author Sergey Nebolsin
 * @since 0.4
 *
 * Created: 06-Jan-2007
 */
public class GrailsDomainBinderTests extends TestCase {

    private static final String ONE_TO_ONE_CLASSES_DEFINITION = "class Species {\n" + "    Long id \n"
            + "    Long version \n" + "    String name \n" + "} \n" + "class Pet {\n" + "    Long id \n"
            + "    Long version \n" + "    Species species \n" + "}";

    private static final String ONE_TO_MANY_CLASSES_DEFINITION = "class Visit {\n" + "    Long id \n"
            + "    Long version \n" + "    String description \n" + "} \n" + "class Pet {\n" + "    Long id \n"
            + "    Long version \n" + "    Set visits \n" + "    static hasMany = [visits:Visit] \n"
            + "    static mapping = { visits joinTable:false, nullable:false }" + "}";

    private static final String MANY_TO_MANY_CLASSES_DEFINITION = "class Specialty {\n" + "    Long id \n"
            + "    Long version \n" + "    String name \n" + "    Set vets \n"
            + "    static hasMany = [vets:Vet] \n" + "    static belongsTo = Vet \n" + "} \n" + "class Vet {\n"
            + "    Long id \n" + "    Long version \n" + "    Set specialities \n"
            + "    static hasMany = [specialities:Specialty] \n" + "}";

    private static final String MULTI_COLUMN_USER_TYPE_DEFINITION = "import org.codehaus.groovy.grails.orm.hibernate.cfg.*\n"
            + "class Item {\n" + "    Long id \n" + "    Long version \n" + "    String name \n"
            + "    MyType other \n" + "    MonetaryAmount price \n" + "    static mapping = {\n"
            + "        name column: 's_name', sqlType: 'text'\n"
            + "        other type: MyUserType, sqlType: 'wrapper-characters', params:[param1: 'myParam1', param2: 'myParam2']\n"
            + "        price type: MonetaryAmountUserType, {\n" + "            column name: 'value'\n"
            + "            column name: 'currency_code', sqlType: 'text'\n" + "        }\n" + "    }\n" + "}";

    private static final String UNIQUE_PROPERTIES = "class User {\n" + "    Long id \n" + "    Long version \n"
            + "    String login \n" + "    String group \n" + "    String camelCased \n"
            + "    String employeeID \n" + "    static constraints = {    \n"
            + "        employeeID(unique:true)     \n" + "        group(unique:'camelCased')     \n"
            + "        login(unique:['group','camelCased'])   \n" + "   }\n" + "}";

    public void testUniqueConstraintGeneration() {
        DefaultGrailsDomainConfiguration config = getDomainConfig(UNIQUE_PROPERTIES);
        assertEquals("Tables created", 1, getTableCount(config));
        List expectedKeyColumns1 = Arrays
                .asList(new Column[] { new Column("camel_cased"), new Column("group"), new Column("login") });
        List expectedKeyColumns2 = Arrays.asList(new Column[] { new Column("camel_cased"), new Column("group") });
        Table mapping = (Table) config.getTableMappings().next();
        int cnt = 0;
        boolean found1 = false, found2 = false;
        for (Iterator i = mapping.getUniqueKeyIterator(); i.hasNext();) {
            UniqueKey key = (UniqueKey) i.next();
            List keyColumns = key.getColumns();
            if (keyColumns.equals(expectedKeyColumns1)) {
                found1 = true;
            }
            if (keyColumns.equals(expectedKeyColumns2)) {
                found2 = true;
            }
            cnt++;
        }
        assertEquals(2, cnt);
        assertEquals(true, mapping.getColumn(new Column("employeeID")).isUnique());
        assertEquals(true, found1);
        assertEquals(true, found2);
    }

    public void testOneToOneBindingTables() {
        DefaultGrailsDomainConfiguration config = getDomainConfig(ONE_TO_ONE_CLASSES_DEFINITION);
        assertEquals("Tables created", 2, getTableCount(config));
    }

    public void testOneToOneBindingFk() {
        DefaultGrailsDomainConfiguration config = getDomainConfig(ONE_TO_ONE_CLASSES_DEFINITION);
        assertForeignKey("species", "pet", config);
    }

    public void testOneToOneBindingFkColumn() {
        DefaultGrailsDomainConfiguration config = getDomainConfig(ONE_TO_ONE_CLASSES_DEFINITION);
        assertColumnNotNullable("pet", "species_id", config);
    }

    public void testOneToManyBindingTables() {
        DefaultGrailsDomainConfiguration config = getDomainConfig(ONE_TO_MANY_CLASSES_DEFINITION);
        assertEquals("Tables created", 2, getTableCount(config));
    }

    public void testOneToManyBindingFk() {
        DefaultGrailsDomainConfiguration config = getDomainConfig(ONE_TO_MANY_CLASSES_DEFINITION);
        assertForeignKey("pet", "visit", config);
    }

    /*    public void testOneToManyBindingFkColumn() {
    DefaultGrailsDomainConfiguration config = getDomainConfig(ONE_TO_MANY_CLASSES_DEFINITION);
    assertColumnNotNullable("visit", "pet_visits_id", config);
        }*/

    public void testManyToManyBindingTables() {
        DefaultGrailsDomainConfiguration config = getDomainConfig(MANY_TO_MANY_CLASSES_DEFINITION);
        assertEquals("Tables created", 3, getTableCount(config));
    }

    public void testManyToManyBindingPk() {
        DefaultGrailsDomainConfiguration config = getDomainConfig(MANY_TO_MANY_CLASSES_DEFINITION);
        Table table = getTableMapping("vet_specialities", config);
        assertNotNull("VET_SPECIALTY table has a PK", table.getPrimaryKey());
        assertEquals("VET_SPECIALTY table has a 2 column PK", 2, table.getPrimaryKey().getColumns().size());
    }

    public void testManyToManyBindingFk() {
        DefaultGrailsDomainConfiguration config = getDomainConfig(MANY_TO_MANY_CLASSES_DEFINITION);
        assertForeignKey("specialty", "vet_specialities", config);
        assertForeignKey("vet", "vet_specialities", config);
    }

    public void testManyToManyBindingFkColumn() {
        DefaultGrailsDomainConfiguration config = getDomainConfig(MANY_TO_MANY_CLASSES_DEFINITION);
        assertColumnNotNullable("vet_specialities", "vet_id", config);
        assertColumnNotNullable("vet_specialities", "specialty_id", config);
    }

    /**
     * Tests that single- and multi-column user type mappings work
     * correctly. Also Checks that the "sqlType" property is honoured.
     */
    public void testUserTypeMappings() {
        DefaultGrailsDomainConfiguration config = getDomainConfig(MULTI_COLUMN_USER_TYPE_DEFINITION);
        PersistentClass persistentClass = config.getClassMapping("Item");

        // First check the "name" property and its associated column.
        Property nameProperty = persistentClass.getProperty("name");
        assertEquals(1, nameProperty.getColumnSpan());
        assertEquals("name", nameProperty.getName());

        Column column = (Column) nameProperty.getColumnIterator().next();
        assertEquals("s_name", column.getName());
        assertEquals("text", column.getSqlType());

        // Next the "other" property.
        Property otherProperty = persistentClass.getProperty("other");
        assertEquals(1, otherProperty.getColumnSpan());
        assertEquals("other", otherProperty.getName());

        column = (Column) otherProperty.getColumnIterator().next();
        assertEquals("other", column.getName());
        assertEquals("wrapper-characters", column.getSqlType());
        assertEquals(MyUserType.class.getName(), column.getValue().getType().getName());
        assertTrue(column.getValue() instanceof SimpleValue);
        SimpleValue v = (SimpleValue) column.getValue();
        assertEquals("myParam1", v.getTypeParameters().get("param1"));
        assertEquals("myParam2", v.getTypeParameters().get("param2"));

        // And now for the "price" property, which should have two
        // columns.
        Property priceProperty = persistentClass.getProperty("price");
        assertEquals(2, priceProperty.getColumnSpan());
        assertEquals("price", priceProperty.getName());

        Iterator colIter = priceProperty.getColumnIterator();
        column = (Column) colIter.next();
        assertEquals("value", column.getName());
        assertNull("SQL type should have been 'null' for 'value' column.", column.getSqlType());

        column = (Column) colIter.next();
        assertEquals("currency_code", column.getName());
        assertEquals("text", column.getSqlType());
    }

    public void testDomainClassBinding() {
        GroovyClassLoader cl = new GroovyClassLoader();
        GrailsDomainClass domainClass = new DefaultGrailsDomainClass(
                cl.parseClass("public class BinderTestClass {\n" + "    Long id; \n" + "    Long version; \n" + "\n"
                        + "    String firstName; \n" + "    String lastName; \n" + "    String comment; \n"
                        + "    Integer age;\n" + "    boolean active = true" + "\n" + "    static constraints = {\n"
                        + "        firstName(nullable:true,size:4..15)\n" + "        lastName(nullable:false)\n"
                        + "        age(nullable:true)\n" + "    }\n" + "}"));
        DefaultGrailsDomainConfiguration config = getDomainConfig(cl, cl.getLoadedClasses());

        // Test database mappings
        PersistentClass persistentClass = config.getClassMapping("BinderTestClass");
        assertTrue("Property [firstName] must be optional in db mapping",
                persistentClass.getProperty("firstName").isOptional());
        assertFalse("Property [lastName] must be required in db mapping",
                persistentClass.getProperty("lastName").isOptional());
        // Property must be required by default
        assertFalse("Property [comment] must be required in db mapping",
                persistentClass.getProperty("comment").isOptional());

        // Test properties
        assertTrue("Property [firstName] must be optional",
                domainClass.getPropertyByName("firstName").isOptional());
        assertFalse("Property [lastName] must be optional", domainClass.getPropertyByName("lastName").isOptional());
        assertFalse("Property [comment] must be required", domainClass.getPropertyByName("comment").isOptional());
        assertTrue("Property [age] must be optional", domainClass.getPropertyByName("age").isOptional());

    }

    public void testForeignKeyColumnBinding() {
        GroovyClassLoader cl = new GroovyClassLoader();
        GrailsDomainClass oneClass = new DefaultGrailsDomainClass(
                cl.parseClass("class TestOneSide {\n" + "    Long id \n" + "    Long version \n"
                        + "    String name \n" + "    String description \n" + "}"));
        GrailsDomainClass domainClass = new DefaultGrailsDomainClass(cl.parseClass("class TestManySide {\n"
                + "    Long id \n" + "    Long version \n" + "    String name \n" + "    TestOneSide testOneSide \n"
                + "\n" + "    static mapping = {\n" + "        columns {\n"
                + "            testOneSide column:'EXPECTED_COLUMN_NAME'" + "        }\n" + "    }\n" + "}"));

        DefaultGrailsDomainConfiguration config = getDomainConfig(cl,
                new Class[] { oneClass.getClazz(), domainClass.getClazz() });

        PersistentClass persistentClass = config.getClassMapping("TestManySide");

        Column column = (Column) persistentClass.getProperty("testOneSide").getColumnIterator().next();
        assertEquals("EXPECTED_COLUMN_NAME", column.getName());
    }

    /**
      * @see GrailsDomainBinder#bindStringColumnConstraints(Column, ConstrainedProperty)
      */
    public void testBindStringColumnConstraints() {
        // Verify that the correct length is set when a maxSize constraint is applied
        ConstrainedProperty constrainedProperty = getConstrainedStringProperty();
        constrainedProperty.applyConstraint(ConstrainedProperty.MAX_SIZE_CONSTRAINT, new Integer(30));
        assertColumnLength(constrainedProperty, 30);

        // Verify that the correct length is set when a size constraint is applied
        constrainedProperty = getConstrainedStringProperty();
        constrainedProperty.applyConstraint(ConstrainedProperty.SIZE_CONSTRAINT, new IntRange(6, 32768));
        assertColumnLength(constrainedProperty, 32768);

        // Verify that the default length remains intact when no size-related constraints are applied
        constrainedProperty = getConstrainedStringProperty();
        assertColumnLength(constrainedProperty, Column.DEFAULT_LENGTH);

        // Verify that the correct length is set when an inList constraint is applied
        constrainedProperty = getConstrainedStringProperty();
        List validValuesList = Arrays.asList(new String[] { "Groovy", "Java", "C++" });
        constrainedProperty.applyConstraint(ConstrainedProperty.IN_LIST_CONSTRAINT, validValuesList);
        assertColumnLength(constrainedProperty, 6);

        // Verify that the correct length is set when a maxSize constraint *and* an inList constraint are *both* applied
        constrainedProperty = getConstrainedStringProperty();
        constrainedProperty.applyConstraint(ConstrainedProperty.IN_LIST_CONSTRAINT, validValuesList);
        constrainedProperty.applyConstraint(ConstrainedProperty.MAX_SIZE_CONSTRAINT, new Integer(30));
        assertColumnLength(constrainedProperty, 30);
    }

    /**
     * @see GrailsDomainBinder#bindNumericColumnConstraints(Column, ConstrainedProperty)
     */
    public void testBindNumericColumnConstraints() {
        ConstrainedProperty constrainedProperty = getConstrainedBigDecimalProperty();
        // maxSize and minSize constraint has the number with the most digits
        constrainedProperty.applyConstraint(ConstrainedProperty.MAX_SIZE_CONSTRAINT, new Integer(123));
        constrainedProperty.applyConstraint(ConstrainedProperty.MIN_SIZE_CONSTRAINT, new Integer(0));
        assertColumnPrecisionAndScale(constrainedProperty, Column.DEFAULT_PRECISION, Column.DEFAULT_SCALE);

        // Verify that the correct precision is set when the max constraint has the number with the most digits
        constrainedProperty = getConstrainedBigDecimalProperty();
        constrainedProperty.applyConstraint(ConstrainedProperty.MAX_CONSTRAINT, new BigDecimal("123.45"));
        constrainedProperty.applyConstraint(ConstrainedProperty.MIN_CONSTRAINT, new BigDecimal("0"));
        assertColumnPrecisionAndScale(constrainedProperty, 5, Column.DEFAULT_SCALE);

        // Verify that the correct precision is set when the minSize constraint has the number with the most digits
        constrainedProperty = getConstrainedBigDecimalProperty();
        constrainedProperty.applyConstraint(ConstrainedProperty.MAX_CONSTRAINT, new BigDecimal("123"));
        constrainedProperty.applyConstraint(ConstrainedProperty.MIN_CONSTRAINT, new BigDecimal("-123.45"));
        assertColumnPrecisionAndScale(constrainedProperty, 5, Column.DEFAULT_SCALE);

        // Verify that the correct precision is set when the high value of a floating point range constraint has the number with the most digits
        constrainedProperty = getConstrainedBigDecimalProperty();
        constrainedProperty.applyConstraint(ConstrainedProperty.RANGE_CONSTRAINT,
                new ObjectRange(new BigDecimal("0"), new BigDecimal("123.45")));
        assertColumnPrecisionAndScale(constrainedProperty, 5, Column.DEFAULT_SCALE);

        // Verify that the correct precision is set when the low value of a floating point range constraint has the number with the most digits
        constrainedProperty = getConstrainedBigDecimalProperty();
        constrainedProperty.applyConstraint(ConstrainedProperty.RANGE_CONSTRAINT,
                new ObjectRange(new BigDecimal("-123.45"), new BigDecimal("123")));
        assertColumnPrecisionAndScale(constrainedProperty, 5, Column.DEFAULT_SCALE);

        // Verify that the correct scale is set when the scale constraint is specified in isolation
        constrainedProperty = getConstrainedBigDecimalProperty();
        constrainedProperty.applyConstraint(ConstrainedProperty.SCALE_CONSTRAINT, new Integer(4));
        assertColumnPrecisionAndScale(constrainedProperty, Column.DEFAULT_PRECISION, 4);

        // Verify that the precision is set correctly for a floating point number with a min/max constraint and a scale...
        //  1) where the min/max constraint includes fewer decimal places than the scale constraint
        constrainedProperty = getConstrainedBigDecimalProperty();
        constrainedProperty.applyConstraint(ConstrainedProperty.MAX_CONSTRAINT, new BigDecimal("123.45"));
        constrainedProperty.applyConstraint(ConstrainedProperty.MIN_CONSTRAINT, new BigDecimal("0"));
        constrainedProperty.applyConstraint(ConstrainedProperty.SCALE_CONSTRAINT, new Integer(3));
        assertColumnPrecisionAndScale(constrainedProperty, 6, 3); // precision (6) = number of integer digits in max constraint ("123.45") + scale (3)

        //  2) where the min/max constraint includes more decimal places than the scale constraint
        constrainedProperty = getConstrainedBigDecimalProperty();
        constrainedProperty.applyConstraint(ConstrainedProperty.MAX_CONSTRAINT, new BigDecimal("123.4567"));
        constrainedProperty.applyConstraint(ConstrainedProperty.MIN_CONSTRAINT, new BigDecimal("0"));
        constrainedProperty.applyConstraint(ConstrainedProperty.SCALE_CONSTRAINT, new Integer(3));
        assertColumnPrecisionAndScale(constrainedProperty, 7, 3); // precision (7) = number of digits in max constraint ("123.4567") 

        // Verify that the correct precision is set when the only one of 'min' and 'max' constraint specified
        constrainedProperty = getConstrainedBigDecimalProperty();
        constrainedProperty.applyConstraint(ConstrainedProperty.MAX_CONSTRAINT, new BigDecimal("123.4567"));
        assertColumnPrecisionAndScale(constrainedProperty, Column.DEFAULT_PRECISION, Column.DEFAULT_SCALE);
        constrainedProperty = getConstrainedBigDecimalProperty();
        constrainedProperty.applyConstraint(ConstrainedProperty.MAX_CONSTRAINT,
                new BigDecimal("12345678901234567890.4567"));
        assertColumnPrecisionAndScale(constrainedProperty, 24, Column.DEFAULT_SCALE);
        constrainedProperty = getConstrainedBigDecimalProperty();
        constrainedProperty.applyConstraint(ConstrainedProperty.MIN_CONSTRAINT, new BigDecimal("-123.4567"));
        assertColumnPrecisionAndScale(constrainedProperty, Column.DEFAULT_PRECISION, Column.DEFAULT_SCALE);
        constrainedProperty = getConstrainedBigDecimalProperty();
        constrainedProperty.applyConstraint(ConstrainedProperty.MIN_CONSTRAINT,
                new BigDecimal("-12345678901234567890.4567"));
        assertColumnPrecisionAndScale(constrainedProperty, 24, Column.DEFAULT_SCALE);
    }

    private DefaultGrailsDomainConfiguration getDomainConfig(String classesDefinition) {
        GroovyClassLoader cl = new GroovyClassLoader();
        cl.parseClass(classesDefinition);
        return getDomainConfig(cl, cl.getLoadedClasses());
    }

    private DefaultGrailsDomainConfiguration getDomainConfig(GroovyClassLoader cl, Class[] classes) {
        GrailsApplication grailsApplication = new DefaultGrailsApplication(classes, cl);
        grailsApplication.initialise();
        DefaultGrailsDomainConfiguration config = new DefaultGrailsDomainConfiguration();
        config.setGrailsApplication(grailsApplication);
        config.buildMappings();
        return config;
    }

    private Table getTableMapping(String tablename, DefaultGrailsDomainConfiguration config) {
        Table result = null;
        for (Iterator tableMappings = config.getTableMappings(); tableMappings.hasNext();) {
            Table table = (Table) tableMappings.next();
            if (tablename.equals(table.getName())) {
                result = table;
            }
        }
        return result;
    }

    private int getTableCount(DefaultGrailsDomainConfiguration config) {
        int count = 0;
        for (Iterator tables = config.getTableMappings(); tables.hasNext(); tables.next()) {
            count++;
        }
        return count;
    }

    private void assertForeignKey(String parentTablename, String childTablename,
            DefaultGrailsDomainConfiguration config) {
        boolean fkFound = false;
        Table childTable = getTableMapping(childTablename, config);
        for (Iterator fks = childTable.getForeignKeyIterator(); fks.hasNext();) {
            ForeignKey fk = (ForeignKey) fks.next();
            if (parentTablename.equals(fk.getReferencedTable().getName())) {
                fkFound = true;
            }
        }
        assertTrue("FK exists " + childTablename + "->" + parentTablename, fkFound);
    }

    private void assertColumnNotNullable(String tablename, String columnName,
            DefaultGrailsDomainConfiguration config) {
        Table table = getTableMapping(tablename, config);
        assertTrue(table.getName() + "." + columnName + " is not nullable",
                !table.getColumn(new Column(columnName)).isNullable());
    }

    private void assertColumnLength(ConstrainedProperty constrainedProperty, int expectedLength) {
        Column column = new Column();
        GrailsDomainBinder.bindStringColumnConstraints(column, constrainedProperty);
        assertEquals(expectedLength, column.getLength());
    }

    private void assertColumnPrecisionAndScale(ConstrainedProperty constrainedProperty, int expectedPrecision,
            int expectedScale) {
        Column column = new Column();
        GrailsDomainBinder.bindNumericColumnConstraints(column, constrainedProperty);
        assertEquals(expectedPrecision, column.getPrecision());
        assertEquals(expectedScale, column.getScale());
    }

    private ConstrainedProperty getConstrainedBigDecimalProperty() {
        return getConstrainedProperty("testBigDecimal");
    }

    private ConstrainedProperty getConstrainedStringProperty() {
        return getConstrainedProperty("testString");
    }

    private ConstrainedProperty getConstrainedProperty(String propertyName) {
        BeanWrapper constrainedBean = new BeanWrapperImpl(new TestClass());
        return new ConstrainedProperty(constrainedBean.getWrappedClass(), propertyName,
                constrainedBean.getPropertyType(propertyName));
    }
}