Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

...

There's almost nothing to this step but just to call a constructor:

Code Block
borderStyledashed
titleStep 1
borderStyledashed
import groovytools.builder.*

MetaBuilder mb = new MetaBuilder(getClass().getClassLoader())

...

MetaBuilder provides a DSL, implemented as a Groovy builder, for defining your own builders. If you are already familiar with builders in Groovy, it takes just one simple example to get you started. Continuing where we left off above, let's start defining a customer class and customer builder:

Code Block
borderStyledashed
titleStep 2
borderStyledashed
class Customer {
    def name
    def dateOfBirth
    def ssn
    def phone
}

mb.define {
    customer(factory: Customer) {
        properties {
            name()
            dateOfBirth()
            ssn()
        }
    }
}

...

Building objects is now just a matter of telling MetaBuilder that is what you want to do:

Code Block
borderStyledashed
titleStep 3
borderStyledashed
def aCustomer = mb.build {
    customer {
        name = 'J. Doe'
        dateOfBirth  = '1/1/1900'
        ssn  = '555-55-5555'
    }
}

// this is equivalent
aCustomer = mb.build {
    customer ( name: 'J. Doe', dateOfBirth: '1/1/1900', ssn: '555-55-5555')
}

// you can even mix up the styles:
aCustomer = mb.build {
    customer ( name: 'J. Doe', dateOfBirth: '1/1/1900') {
        ssn = '555-55-5555'
    }
}

...

What would happen if you used a property that was not in the schema or mistyped a legitimate property name? For example:

Code Block
borderStyledashed
titleCatching Errors
borderStyledashed
aCustomer = mb.build {
    customer {
        name = 'J. Doe'
        dob  = '1/1/1900'      // should have been 'dataOfBirth'
        ssn  = '555-55-5555'
        phone = '1-555-555-5555' // not allowed
    }
}

...

This next example demonstrates how one might use closure to create Customer objects:

Code Block
borderStyledashed
titleSpecifying a Factory using a Closure
borderStyledashed
mb.define {
    customer2(factory: { new Customer() } )) {
        properties {
            name()
            dateOfBirth()
            ssn()
        }
    }
}

def aCustomer2 = mb.build {
    customer2 {
        name = 'J. Doe'
        dateOfBirth  = '1/1/1900'
        ssn  = '555-55-5555'
    }
}

...

  • def: the default value or a Closure to produce the default value, as needed
  • req: if true, a value must be specfied (it could be null though)
  • property: an alternative property name or Closure to use to set the value
  • min: the minimum length for a property value
  • max: the maximum length for a property value
  • check: causes an exception if the value fails the following test:
    Code Block
    borderStyledashed
    switch(value) {
        case check: return true
    }
    return false
    
    Tip
    titleTip

    The check attribute accepts the following types of objects which makes enforcing constraints easy:

    • Closures
    • Patterns
    • Classes
    • Numbers
    • Strings
    • Ranges
    • Collections
    Here's an example showing combinations of some of the above:
    Code Block
    borderStyledashed
    titleMetaBuilder Property Attributes in Action
    borderStyledashed
    mb.define {
    customer3(factory: Customer) {
        properties {
            name(req: true, min: 1)
            dob(property: 'dateOfBirth')
            ssn(check: ~/\d{3}-\d{2}-\d{4}/)
        }
    }
    
    def aCustomer3 = mb.build {
        customer4 {
            name = 'J. Doe'
            dob = '1/1/1900'
            ssn  = '555-55-5555'
        }
    }
    

...

Use the schema attribute to tell MetaBuilder that you want to reuse a schema. In the next example, we'll create a new Phone class and update our schema to use it:

Code Block
borderStyledashed
titleAdding a Phone Class and Schema
borderStyledashed
class Phone {
    def type
    def number
}

mb.define {
    phone (factory: Phone) {
        properties {
            type(check: ['home','cell','work'], def: 'home')
            number(req: true, check: ~/\d{3}-\d{3}-\d{4}/)
        }
    }
}

Now, let's make phone a required property on our customer schema:

Code Block
borderStyledashed
titleUsing the Phone Schema
borderStyledashed
mb.define {
    customer4(factory: Customer) {
        properties {
            name(req: true, min: 1)
            dob(property: 'dateOfBirth')
            ssn(check: ~/\d{3}-\d{2}-\d{4}/)
            phone(schema: 'phone', req: true)
        }
    }
}

def aCustomer4 = mb.build {
    customer4 {
        name = 'J. Doe'
        dob  = '1/1/1900'
        ssn  = '555-55-5555'
        phone {
            type = 'home'
            number = '123-456-7890'
        }
    }
}

...

So far, we've only looked at examples of properties, but MetaBuilder also supports collections. You define collections like you define properties, just provide a list of them and set attributes as needed.

Code Block
borderStyledashed
titleDefining Schema with Indexed and Non-Indexed Collections
borderStyledashed
class Customer2 extends Customer {
    def phoneList = []
    def addresses = [:]
}

mb.define {
    customer5(factory: Customer2) {
        properties {
            name(req: true, min: 1)
            dob(property: 'dateOfBirth')
            ssn(check: ~/\d{3}-\d{2}-\d{4}/)
            phone(schema: 'phone', req: true)
        }
        collections {
            phoneList(min: 1) {
                phone(schema: phone)
            }
            addresses (key: 'type', min: 1, max:2) {
                address() {
                    properties {
                        type(check: ['billto', 'shipto'], def: 'billto')
                        street()
                        city()
                        state()
                        zip()
                    }
                }
            }
        }
    }
}

...

Another thing to note is the use of the key attribute in the addresses collection. Presence of the key attribute tells MetaBuilder that the parent-child relationship is indexed. In the following example, you can see that customer5 has both a list of phone and map of addresses using the address's type
and the key.

Code Block
borderStyledashed
titleBuilding Objects with Indexed and Non-Indexed Collections
borderStyledashed
def aCustomer5 = mb.build {
    customer5 {
        name = 'J. Doe'
        dob  = '1/1/1900'
        ssn  = '555-55-5555'
        phone {
            type = 'home'
            number = '123-456-7890'
        }
        phoneList {
            phone(type: 'work', number: '111-222-3333')
            phone(type: 'cell', number: '444-555-6666')
        }
        address {
            type   = 'billto'
            street = '1234 Some St.'
            city   = 'Some City'
            zip    = '12345'
        }
        address {
            type   = 'shipto'
            street = '1234 Some Other St.'
            city   = 'Some Other City'
            zip    = '12345'
        }
    }
}

...