Skip to content

Instantly share code, notes, and snippets.

@nwwells
Created October 24, 2012 14:23
Show Gist options
  • Save nwwells/3946349 to your computer and use it in GitHub Desktop.
Save nwwells/3946349 to your computer and use it in GitHub Desktop.
Grails ExportService DSL

This gist is the product of some work I did to simplify my usage of the great ExportService by Andreas Schmitt (he uses Gmail and his username is andreas.schmitt.mi). Here's the contents:

  • ExampleUsage.groovy - shows how I use this DSL in my own code
  • ExportBuilder.groovy - handles DSL parsing
  • ExportBuilderTests.groovy - JUnit tests for ExportBuilder
  • NewBuildMethodInExportService.groovy - suggested addition to ExportService, to make it easier to use the DSL
exportService.build {
format 'excel'
out response
filename 'facilities'
extension 'xls'
objects Contact.list(params)
parameters([title: 'Facilities'])
fields {
name ('Name') {obj, val -> obj?.facility?.name}
abbreviation ('Abbr') {obj, val -> obj?.facility?.abbreviation}
otherNames ('Other Names') {obj, val -> obj?.facility?.otherNames}
contactName ('Contact Name') {obj, val -> obj?.name}
contactTitle ('Title') {obj, val -> obj?.title}
contactEmail ('Email') {obj, val -> obj?.email}
contactPhone ('Phone') {obj, val -> obj?.phone}
contactFax ('Fax') {obj, val -> obj?.fax}
streetAddress ('Street Address') {obj, val -> obj?.facility?.streetAddress?.replace('\n', ', ')}
mailingAddress ('Mailing Address') {obj, val -> obj?.facility?.mailingAddress?.replace('\n', ', ')}
entity ('Entity') {obj, val -> obj?.facility?.entity}
}
}
package de.andreasschmitt.export.exporter
import javax.servlet.http.HttpServletResponse
class ExportBuilder {
def format
def out
def filename
def extension
def objects
def parameters
def fields
def labels
def formatters
def build(Closure closure) throws ExportingException {
closure.setDelegate(this)
closure()
verify()
}
def verify() throws ExportingException {
if (!format)
fail "An export format must be specified!"
if (!(out instanceof OutputStream || out instanceof HttpServletResponse))
fail "An OutputStream or HttpServletResponse must be specified!"
if (out instanceof HttpServletResponse && (!filename || !extension))
fail "A filename and extension must be specified for response exports"
if (null == objects)
fail "Objects to export must be specified!"
formatters.each { fieldName, formatter ->
if (formatter.maximumNumberOfParameters < 2)
fail "Formatters must specify two parameters: object and value"
}
}
def fail(message) { throw new ExportingException(message) }
def format(String format) { this.format = format }
def out(out) { this.out = out }
def filename(String filename) { this.filename = filename }
def extension(String extension) { this.extension = extension }
def objects(objects) { this.objects = objects }
def parameters(Map parameters) { this.parameters = parameters }
def isOutputStream() { return this.out instanceof OutputStream }
def isResponse() { return this.out instanceof HttpServletResponse }
def fields(closure) {
def delegate = new FieldDelegate()
closure.setDelegate(delegate)
closure();
this.fields = delegate.fields
this.labels = delegate.labels
this.formatters = delegate.formatters
}
}
class FieldDelegate {
def fields = []
def labels = [:]
def formatters = [:]
def methodMissing(String name, args) {
fields.add(name)
args.each {
if (it instanceof String) labels.put(name, it)
else if (it instanceof Closure) formatters.put(name, it)
}
}
}
package de.andreasschmitt.export.exporter
import grails.test.mixin.TestFor
import org.codehaus.groovy.grails.plugins.testing.GrailsMockHttpServletResponse
import org.junit.Test
import static junit.framework.Assert.assertEquals
class ExportBuilderTests {
def builder = new ExportBuilder()
def mockResponse = new GrailsMockHttpServletResponse()
def mockObjects = ['test', 'data']
def mockOutputStream = new OutputStream() {
@Override
void write(int i) {
}
}
@Test(expected = ExportingException)
void testNoInput() {
builder.build {}
}
@Test(expected = ExportingException)
void testNoOut() {
builder.build {
format 'xls'
objects mockObjects
}
}
@Test(expected = ExportingException)
void testNoObjects() {
builder.build {
format 'xls'
out mockOutputStream
}
}
@Test(expected = ExportingException)
void testResponseWithoutFileInfo() {
builder.build {
format 'xls'
objects mockObjects
out mockResponse
}
}
@Test(expected = ExportingException)
void testResponseWithoutFilename() {
builder.build {
format 'xls'
objects mockObjects
out mockResponse
extension 'xls'
}
}
@Test(expected = ExportingException)
void testResponseWithoutExtension() {
builder.build {
format 'xls'
objects mockObjects
out mockResponse
filename 'test'
}
}
@Test
void testResponse() {
builder.build {
format 'xls'
objects mockObjects
out mockResponse
filename 'test'
extension 'xls'
}
assertEquals("Format should be xls", 'xls', builder.format)
assertEquals("out should be mockResponse", mockResponse, builder.out)
assertEquals("filename should be test", 'test', builder.filename)
assertEquals("extension should be xls", 'xls', builder.extension)
assertEquals("objects should be mockObjects", mockObjects, builder.objects)
}
@Test
void testOutputStream() {
builder.build {
format 'xls'
objects mockObjects
out mockOutputStream
}
assertEquals("Format should be xls", 'xls', builder.format)
assertEquals("out should be mockOutputStream", mockOutputStream, builder.out)
assertEquals("objects should be mockObjects", mockObjects, builder.objects)
}
@Test
void testParameters() {
def testMap = [
test: 'parameters',
doesIt: 'work?'
]
builder.build {
format 'xls'
objects mockObjects
out mockOutputStream
parameters testMap
}
assertEquals("parameters should be testMap", testMap, builder.parameters)
}
@Test
void testFields() {
def nameLabel = 'Name',
nameFormatter = {obj, val -> obj?.name},
abbrLabel = 'Abbr',
abbrFormatter = {obj, val -> obj?.abbreviation},
otherNamesLabel = 'Other Names',
otherNamesFormatter = {obj, val -> obj?.otherNames}
builder.build {
format 'xls'
objects mockObjects
out mockOutputStream
fields {
name(nameLabel, nameFormatter)
abbreviation(abbrLabel, abbrFormatter)
otherNames(otherNamesLabel, otherNamesFormatter)
}
}
assertEquals("fields should be correct", builder.fields, [
'name',
'abbreviation',
'otherNames'
])
assertEquals("labels should be correct", builder.labels, [
name: nameLabel,
abbreviation: abbrLabel,
otherNames: otherNamesLabel
])
assertEquals("formatters should be correct", builder.formatters, [
name: nameFormatter,
abbreviation: abbrFormatter,
otherNames: otherNamesFormatter
])
}
@Test(expected=ExportingException)
void testFormattersWithNotEnoughArgs() {
def nameLabel = 'Name',
nameFormatter = {obj -> obj?.name}
builder.build {
format 'xls'
objects mockObjects
out mockOutputStream
fields {
name(nameLabel, nameFormatter)
}
}
}
}
def build(Closure closure) {
def export = new ExportBuilder()
export.build(closure)
if (export.isOutputStream()) {
exportService.export(
export.format,
export.out,
export.objects,
export.fields,
export.labels,
export.formatters,
export.parameters
)
} else if (export.isResponse()) {
exportService.export(
export.format,
export.out,
export.filename,
export.extension,
export.objects,
export.fields,
export.labels,
export.formatters,
export.parameters
)
} else {
throw new ExportException('Attempted to export to an unknown type of OutputStream')
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment