Vert.x using Groovy - Simplifying non-blocking code

Post on 10-May-2015

1.298 views 0 download

description

The possibilities and advantages of non-blocking IO are great. But as you have to hassle with callbacks all over the place you have to think differently. Sometimes simple constructs we are used to are getting ugly or really hard to realize. A little bit of Groovy-magic can help out to simplify life and make your code more look like you are used to. This session wants to show experiences creating a vert.x-based application and the solutions we used to smooth up our code.

Transcript of Vert.x using Groovy - Simplifying non-blocking code

Alexander (Sascha) Kleincodecentric AG

vert.x with GroovySimplifying non-blocking code

codecentric AG

Alexander Klein, 2014-06-03

vert.x with Groovy – Simpliyfing non-blocking code

codecentric AG

Why using vert.x ?

CC BY 2.0 > http://www.flickr.com/photos/girliemac/6509400997

codecentric AG

Alexander (Sascha) Klein

Principal Consultant

codecentric AG in Stuttgart

Germany

Groovy, JavaFX, UI / UX

Griffon committer

alexander.klein@codecentric.de

@saschaklein

http://gplus.to/karfunkel

codecentric AG

vert.x

Framework to write polyglot, highly concurrent applications

Similar to Node.js

Asynchronous, non-blocking API

Polyglot (Java, JavaScript, Groovy, Ruby, Python and others)

codecentric AG

Architecture

Client Background ThreadpoolWorker-Verticle

Worker-Verticle

Worker-Verticle

Event Loop

Verticle

Verticle

Verticle

Event Bus

Request

Response

delegating

long-running tasks

non-blocking blocking

codecentric AG

Yoke

Middleware framework for vert.x

Currently only Java, JavaScript and Groovy supported

Many helpful implementations

Request body and Cookie parser

Static file server

Request Router

Virtual host support

Templateengines

and more ...

codecentric AG

Calculating CRC32's for a directory

Read directory entries

Read file properties for each entry

Determine if entry is a directory

Handle directories recursively

Read file

Calculate CRC32 via worker verticle

codecentric AG

Classic vert.x/yoke code

container.deployWorkerVerticle 'CRC.groovy', [:]

GRouter router = new GRouter()

router.get("/crc") { GYokeRequest request ->

request.response.chunked = true

request.response.contentType = 'text/plain'

this.crc('/home/aklein/tmp/ConfigParser', request)

}

router.get("/") { GYokeRequest request, Handler next ->

request.response.render 'web/index.gsp', next

}

codecentric AG

Classic vert.x/yoke code

def yoke = new GYoke(vertx, container)

yoke.engine new GroovyTemplateEngine()

yoke.use(router)

yoke.use new Static("web", 24 * 60 * 60 * 1000, true, false)

yoke.use { request ->

request.response.statusCode = 404

request.response.statusMessage = 'Not Found'

request.response.contentType = 'text/plain'

request.response.end('404 - Not Found')

}

yoke.listen(8080)

codecentric AG

Classic vert.x/yoke code

def crc(String baseDir, GYokeRequest request) {

EventBus bus = vertx.eventBus

FileSystem fs = vertx.fileSystem

fs.readDir(baseDir) { AsyncResult<String[]> rs ->

if (rs.succeeded) {

String[] paths = rs.result

paths.each { String path ->

fs.props(path) { AsyncResult<FileProps> rs1 ->

if (rs1.succeeded) {

FileProps props = rs1.result

if (props.directory) {

crc(path, request)

} else {

fs.readFile(path) { AsyncResult<Buffer> rs2 ->

if (rs2.succeeded) {

Buffer content = rs2.result

bus.send("create.crc", content) { Message result ->

if (result.body().status == 'error') {

request.response.statusCode = 500

request.response.statusMessage = "Error processing file " +

"$path: ${result.body().message}: ${result.body().error} \n" +

"${result.body().stacktrace}"

request.response.end()

} else {

request.response.write "$path = ${result.body().message}\n"

}

}

} else {

request.response.statusCode = 500

request.response.statusMessage = "Failed to read file $path"

request.response.end()

}

}

}

} else {

request.response.statusCode = 500

request.response.statusMessage = "Failed to read properties for $path"

request.response.end()

}

}

}

} else {

request.response.statusCode = 500

request.response.statusMessage = "Failed to read $baseDir"

request.response.end()

}

}

}

codecentric AG

Preparing gradle build

Download from: http://github.com/vert-x/vertx-gradle-template

build.gradle

provided "com.jetdrone:yoke:$yokeVersion@jar" // (optional for using yoke)

gradle.properties

groovyVersion=2.2.1

yokeVersion=1.0.13 // (optional for using yoke)

codecentric AG

Preparing gradle build

gradle/vertx.gradle

task startMod(dependsOn: copyMod, description: 'Run the module', type: JavaExec) {

classpath = sourceSets.main.compileClasspath + sourceSets.main.runtimeClasspath

main = 'org.vertx.java.platform.impl.cli.Starter'

args(['runmod', moduleName])

args runModArgs.split("\\s+")

// jvmArgs "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"

systemProperties([

"vertx.clusterManagerFactory": "org.vertx.java.spi.cluster.impl.hazelcast.HazelcastClusterManagerFactory",

"vertx.mods" : "$projectDir/build/mods"

])

}

codecentric AG

Classic vert.x/yoke code

def crc(String baseDir, GYokeRequest request) {

EventBus bus = vertx.eventBus

FileSystem fs = vertx.fileSystem

fs.readDir(baseDir) { AsyncResult<String[]> rs ->

if (rs.succeeded) {

String[] paths = rs.result

paths.each { String path ->

fs.props(path) { AsyncResult<FileProps> rs1 ->

if (rs1.succeeded) {

FileProps props = rs1.result

if (props.directory) {

crc(path, request)

} else {

fs.readFile(path) { AsyncResult<Buffer> rs2 ->

if (rs2.succeeded) {

Buffer content = rs2.result

bus.send("create.crc", content) { Message result ->

if (result.body().status == 'error') {

request.response.statusCode = 500

request.response.statusMessage = "Error processing file " +

"$path: ${result.body().message}: ${result.body().error} \n" +

"${result.body().stacktrace}"

request.response.end()

} else {

request.response.write "$path = ${result.body().message}\n"

}

}

} else {

request.response.statusCode = 500

request.response.statusMessage = "Failed to read file $path"

request.response.end()

}

}

}

} else {

request.response.statusCode = 500

request.response.statusMessage = "Failed to read properties for $path"

request.response.end()

}

}

}

} else {

request.response.statusCode = 500

request.response.statusMessage = "Failed to read $baseDir"

request.response.end()

}

}

}

codecentric AG

Compress Errorhandling - Method

request.response.statusCode = 500

request.response.statusMessage = "Failed to read file $path"

request.response.end()

------------------------------------------------------------------------------------------------------------------------------------------------------------------

def end(YokeResponse response, int statusCode, String statusMessage = null) {

response.statusCode = statusCode

if(statusMessage)

response.statusMessage = statusMessage

response.end()

}

------------------------------------------------------------------------------------------------------------------------------------------------------------------

end request.response, 500, "Failed to read file $path"

codecentric AG

Compress Errorhandling - Dynamic Mixins

class YokeExtension {

static String end(YokeResponse self, Integer statusCode, String statusMessage = null) {

self.statusCode = statusCode

if (statusMessage)

self.statusMessage = statusMessage

self.end()

}

}

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

YokeResponse.mixin(YokeExtension)

request.response.end 500, "Failed to read file $path"

codecentric AG

Compress Errorhandling - Static Mixins (vert.x 2.1)

class YokeExtension {

static String end(YokeResponse self, Integer statusCode, String statusMessage = null) {

...

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

compilerConfiguration.groovy:

customizer = { org.codehaus.groovy.control.CompilerConfiguration config ->

config.addCompilationCustomizers(

new ASTTransformationCustomizer(Mixin, value: YokeExtension) )

return config

}

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

request.response.end 500, "Failed to read file $path"

codecentric AG

Compress Errorhandling - Module Extension (vert.x 2.1)

class YokeExtension {

static String end(YokeResponse self, Integer statusCode, String statusMessage = null) {

...

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

META-INF/services/org.codehaus.groovy.runtime.ExtensionModule:

moduleName = vertx-module

moduleVersion = 1.0

extensionClasses = de.codecentric.vertx.YokeExtension

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

build.gradle:

repositories {

mavenLocal()

}

dependencies {

compile "de.codecentric:vertx-extension:1.0.0-SNAPSHOT@jar"

}

codecentric AG

After YokeResponse enhancement

def crc(String baseDir, GYokeRequest request) {

EventBus bus = vertx.eventBus

FileSystem fs = vertx.fileSystem

fs.readDir(baseDir) { AsyncResult<String[]> rs ->

if (rs.succeeded) {

String[] paths = rs.result

paths.each { String path ->

fs.props(path) { AsyncResult<FileProps> rs1 ->

if (rs1.succeeded) {

FileProps props = rs1.result

if (props.directory) {

crc(path, request)

} else {

fs.readFile(path) { AsyncResult<Buffer> rs2 ->

if (rs2.succeeded) {

Buffer content = rs2.result

bus.send("create.crc", content) { Message result ->

if (result.body().status == 'error') {

request.response.end 500, "Error processing file " +

"$path: ${result.body().message}: ${result.body().error} \n" +

"${result.body().stacktrace}"

} else

request.response.write "$path = ${result.body().message}\n"

}

} else

request.response.end 500, "Failed to read file $path"

}

}

} else

request.response.end 500, "Failed to read properties for $path"

}

}

} else

request.response.end 500, "Failed to read $baseDir“

}

}

codecentric AG

Bus communication

if (rs2.succeeded) {

Buffer content = rs2.result

bus.send("create.crc", content) { Message result ->

if (result.body().status == 'error') {

request.response.end 500, "Error processing file " +

"$path: ${result.body().message}: ${result.body().error} \n" +

"${result.body().stacktrace}"

} else

request.response.write "$path = ${result.body().message}\n"

}

} else

request.response.end 500, "Failed to read file $path"

codecentric AG

Worker Module

EventBus bus = vertx.eventBus

bus.registerHandler('create.crc') { Message msg ->

try {

Buffer buffer = new Buffer(msg.body())

CRC32 crc = new CRC32()

int start = 0, end, length = buffer.length

while (start < length) {

end = Math.min(start + 1024, length)

crc.update(buffer.getBytes(start, end))

start = end

}

msg.reply([status: 'ok', message: crc.value ])

} catch (e) {

StringWriter sw = new StringWriter()

e.printStackTrace(sw.newPrintWriter())

msg.reply([status: 'error', message: 'Failure creating crc', error: e.message, stacktrace: sw.toString()])

}

}

codecentric AG

Standardizing bus communication – Worker

bus.registerHandler('create.crc') { Message msg ->

try { ...

msg.reply([status: 'ok', message: crc.value ])

} catch (e) {

StringWriter sw = new StringWriter()

e.printStackTrace(sw.newPrintWriter())

msg.reply([status: 'error', message: 'Failure creating crc', error: e.message, stacktrace: sw.toString()])

}

}

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

bus.registerHandler('create.crc') { Message msg ->

try { ...

msg.replySuccess(crc.value)

} catch (e) {

msg.replyFailure('Failure creating crc', e)

}

}

codecentric AG

Standardizing bus communication - Module

class MessageExtension {

static final String OK = 'ok'

static final String ERROR = 'error'

static void replySuccess(Message self, message) {

self.reply([status: OK, message: message])

}

static void replyFailure(Message self, Throwable e) {

replyFailure(self, null, e)

}

static void replyFailure(Message self, String msg, Throwable e = null) {

def message = [status: ERROR]

if (msg)

message.message = msg

if (e) {

message.error = e.message

StringWriter sw = new StringWriter()

e.printStackTrace(sw.newPrintWriter())

message.stacktrace = sw.toString()

}

self.reply(message)

}

codecentric AG

Standardizing bus communication - Module

static String getStacktrace(Message self) {

self.body().stacktrace

}

static String getError(Message self) {

self.body().error

}

static def getMessage(Message self) {

return self.body().message

}

...

codecentric AG

Standardizing bus communication – Caller

bus.send("create.crc", content) { Message result ->

if (result.body().status == 'error')

request.response.end 500,

"Error processing file $path: ${result.body().message}: ${result.body().error} \n $result.body().stacktrace}"

else

request.response.write "$path = ${result.body().message}\n"

}

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

bus.send("create.crc", content) { Message result ->

if (result)

request.response.write "$path = ${result.message}\n"

else

request.response.end 500, "Error processing file $path: $result.logMessage"

}

codecentric AG

Standardizing bus communication - Module

static boolean isSucceeded(Message self) {

def result = self.body()

if (result instanceof Map) {

return result.status == OK

} else

return false

}

static boolean asBoolean(Message self) {

return self.isSucceeded()

}

static String getLogMessage(Message self) {

return self.getError() ? "${self.getMessage()}: ${self.getError()} \n${self.getStacktrace()}" : self.getMessage()

}

...

codecentric AG

Streamlining AsyncResult API

static boolean asBoolean(AsyncResult self) {

return self.isSucceeded()

}

static String getStacktrace(AsyncResult self) {

if (!self.cause)

return ''

StringWriter sw = new StringWriter()

PrintWriter pw = sw.newPrintWriter()

self.cause.printStackTrace(pw)

return sw.toString()

}

static String getError(AsyncResult self) {

return self.cause ? self.cause.message : ''

}

static def getMessage(AsyncResult self) {

return self.result

}

static String getLogMessage(AsyncResult self) {

return self.getError() ? self.getMessage() +

": ${self.getError()} \n${self.getStacktrace()}" :

self.getMessage()

}

codecentric AG

With standardized bus communication

def crc(String baseDir, GYokeRequest request) {

EventBus bus = vertx.eventBus

FileSystem fs = vertx.fileSystem

fs.readDir(baseDir) { AsyncResult<String[]> rs ->

if (rs) {

String[] paths = rs.result

paths.each { String path ->

fs.props(path) { AsyncResult<FileProps> rs1 ->

if (rs1) {

FileProps props = rs1.result

if (props.directory) {

crc(path, request)

} else {

fs.readFile(path) { AsyncResult<Buffer> rs2 ->

if (rs2) {

Buffer content = rs2.result

bus.send("create.crc", content) { Message result ->

if (result) {

request.response.write "$path = ${result.message}\n"

} else

request.response.end 500, "Error processing file $path:" + result.logMessage

} else

request.response.end 500, "Failed to read file $path"

}

}

} else

request.response.end 500, "Failed to read properties for $path"

}

}

} else

request.response.end 500, "Failed to read $baseDir“

}

}

codecentric AG

Handler chains

Event-based programming often results in multiple, stacked Handlers / Closures

Difficult to read

Order of commands from left to right / outside to inside

Horizontal scrolling because of indentation

Hard to find the begining of a logical part

Difficult to test

Loops are difficult or impossible to implement

When is the for loop finished to send the .end()?

codecentric AG

Closure Chaining - Syntax

chain { next -> next() }, { next -> next(10) }, { input, next -> println input }

chain ( 10, { input, next -> next(input) }, { input, next -> println input } )

chain 10, { input, next -> next(input) }, { input, next -> println input }

------------------------------------------------------------------------------------------------------------------------------------------------------------------

chain { next -> next() } { next -> next(10) } { input, next -> println input }

chain (10) { input, next -> next(input) } { input, next -> println input }

chain (10) { input, next ->

next(input)

} {

input, next -> println input

}

codecentric AG

Closure Chaining – Module

class StructureExtension {

static void chain(final Object self, def arguments, Closure... actions) {

if (arguments instanceof Closure) {

actions = [arguments, *actions] as Closure[]

arguments = null

}

if (!actions)

throw new IllegalArgumentException("One or more arguments of type groovy.lang.Closure required")

_chain(arguments, actions.iterator())

}

...chain{} …chain(arg)chain(arg) {} ...

codecentric AG

Closure Chaining – Module

static void chain(final Object self, Object... arguments) {

if (!arguments.any { it instanceof Closure })

throw new IllegalArgumentException("One or more arguments of type groovy.lang.Closure required")

int i; def actions = []

for (i = arguments.size() - 1; i >= 0; i--) {

if (arguments[i] instanceof Closure)

actions.add(0, arguments[i])

else

break

}

_chain(arguments[0..i], actions.iterator())

}

...

chain()chain(arg1, arg2, ...)chain(arg1, arg2, ...) {} …

codecentric AG

Closure Chaining – Module

private static void _chain(final Object arguments, final Iterator<Closure> actions) {

if (actions) {

def action = actions.next()

if (arguments != null) {

action = action.curry(arguments as Object[])

}

action.call { Object[] args ->

_chain(args, actions)

}

}

}

...

codecentric AG

Looping - Syntax

[1,2,3].loop { element, next -> next() }

[a:1, b:2, c:3].loop { key, value, next -> next() }

[1,2,3].loop { element, next ->

next()

} {

// called after the last iteration

}

codecentric AG

Looping – Module

static void loop(final Object[] array, final Closure action) { loop(array, action, {} }

static void loop(final Object[] array, final Closure action, final Closure next) { _loop(array?.iterator(), action, next) }

static void loop(final Collection collection, final Closure action) { loop(collection, action, {} }

static void loop(final Collection collection, final Closure action, final Closure next) {

_loop(collection.iterator(), action, next)

}

static void loop(final Map map, final Closure action) { loop(map, action, {} }

static void loop(final Map map, final Closure action, final Closure next) { _loop(map.iterator(), action, next) }

...

codecentric AG

Looping – Module

private static void _loop(final Iterator<?> iterator, final Closure action, Closure next = {}) {

if(iterator) {

def element = iterator.next()

def nextAction

if (iterator)

nextAction = StructureExtension.&_loop.curry(iterator, action, next)

else

nextAction = next

if (element instanceof Map.Entry)

action.call(element.key, element.value, nextAction)

else

action.call(element, nextAction)

} else next.call()

}

codecentric AG

With chaining and looping

def crc(String baseDir, GYokeRequest request, Closure nextCrc = null) {

FileSystem fs = vertx.fileSystem

chain { nextChain -> // Read directory

fs.readDir(baseDir) { AsyncResult<String[]> rs ->

if (rs) nextChain(rs.result as List)

else request.response.end 500, "Failed to read $baseDir"

}

} { List paths, nextChain -> // Loop over files

paths.loop { String path, nextLoop ->

chain { next -> // Read file properties

fs.props(path) { AsyncResult<FileProps> rs ->

if (rs) next(rs.result)

else request.response.end 500, "Failed to read properties for $path"

}

} { FileProps props, next -> // Check for directory

if (props.directory) crc(path, request, nextLoop)

else next()

}

{ next -> // Read file

fs.readFile(path) { AsyncResult<Buffer> rs ->

if (rs) next(rs.result)

else request.response.end 500, "Failed to read file $path"

}

} { Buffer content, next -> // Call module to calculate crc

bus.send("create.crc", content) { Message result ->

if (result) {

request.response.write "$path = ${result.message}\n"

nextLoop()

} else request.response.end 500, "Error processing file $path"

}

}

}

}

}

codecentric AG

Adding .end() after the loop

{ Buffer content, next -> // Call module to calculate crc

Vertx.eventBus.send("create.crc", content) { Message result ->

if (result) {

request.response.write "$path = ${result.message}\n"

nextLoop()

} else request.response.end 500, "Error processing file $path"

}

}

} { // finish everything up after loop

if (nextCrc) nextCrc()

else request.response.end()

}

}

}

codecentric AG

Using a template engine

router.get("/crc") { GYokeRequest request ->

request.context.files = [:]

...

def crc(String baseDir, GYokeRequest request, Closure nextCrc = null) {

request.context.files[baseDir] = null

...

{ Buffer content, next -> // Call module to calculate crc

Vertx.eventBus.send("create.crc", content) { Message result ->

if (result) {

request.context.files[path] = result.message

nextLoop()

} else request.response.end 500, "Error processing file $path"

}

}

}

...

} { // finish everything up after loop

if (nextCrc) nextCrc()

else

request.response.render('web/crc.gsp',

files: request.context.files)

}

}

}

codecentric AG

Accessing the context - !!! Hack Alert !!!

class YokeExtension {

...

static Context getContext(YokeRequest self) {

// Reflection because context is a private field of the super class for GYokeRequest

Field field = YokeRequest.getDeclaredField('context')

field.accessible = true

return (Context) field.get(self)

}

static Context getContext(YokeResponse self) {

// Reflection because context is a private field of the super class for GYokeResponse

Field field = YokeResponse.getDeclaredField('context')

field.accessible = true

return (Context) field.get(self)

}

codecentric AG

Adding custom context for rendering

static void render(GYokeResponse self, Map<String, Object> context, String template) { render(self, context, template, null, null) }

static void render(GYokeResponse self, Map<String, Object> context, String template, Closure next) {

render(self, context, template, null, next)

}

static void render(GYokeResponse self, Map<String, Object> context, String template, String layoutTemplate) {

render(self, context, template, layoutTemplate, null)

}

static void render(GYokeResponse self, Map<String, Object> context, String template, String layoutTemplate, Closure next) {

Map<String, Object> oldContext = getContext(self).clone()

getContext(self).clear()

getContext(self).putAll(context)

if (next)

self.render(template, layoutTemplate, next)

else

self.render(template, layoutTemplate)

getContext(self).clear()

getContext(self).putAll(oldContext)

}

codecentric AG

The template

<html>

<head>

<title>CRC</title>

</head>

<body>

<ul>

<% def data = files.sort { a, b -> a.key <=> b.key }

data.each { k, v ->

if (v != null) { %>

<li>${k} = ${v}</li>

<% } else { %>

<li>${k}</li>

<% } %>

<% } %>

</ul>

</body>

</html>

codecentric AG

Smoothen all up with a custom BaseScriptClass

abstract class VerticleScript extends Script {

Vertx getVertx() {

return binding.vertx

}

void setVertx(Vertx vertx) {

binding.vertx = vertx

}

Container getContainer() {

return binding.container

}

void setContainer(Container container) {

binding.container = container

}

EventBus getBus() {

vertx.eventBus

}

SharedData getSharedData() {

vertx.sharedData

}

Logger getLog() {

container.logger

}

Map<String, Object> getConfig() {

container.config

}

Map<String, String> getEnv() {

container.env

}

codecentric AG

Using the BaseScriptClass (vert.x 2.1)

Global usage:

compilerConfiguration.groovy:

customizer = { org.codehaus.groovy.control.CompilerConfiguration config ->

config.scriptBaseClass = 'de.codecentric.vertx.VerticleScript'

return config

}

------------------------------------------------------------------------------------------------------------------------------------------------------------------

Local usage per Script:

@groovy.transform.BaseScript de.codecentric.vertx.VerticleScript verticleScript

codecentric AG

API to smoothen MongoDB usage

def db(String address, Map message, Closure success = null, Closure failure = null) {

bus.send(address, message) { Message result ->

Map reply = result.body()

if (reply.status == 'ok') {

if (success) {

if (success.maximumNumberOfParameters == 2) success(reply, result)

else success(reply)

}

} else {

if (failure) {

if (failure.maximumNumberOfParameters == 2) failure(reply, result)

else failure(result)

}

}

}

}

codecentric AG

API to smoothen MongoDB usage

def save(String address, String collection, Map document, Closure success = null, Closure failure = null) {

db(address,

[action: 'save', collection: collection, document: document, write_concern: 'SAFE'],

success, failure)

}

def update(String address, String collection, Map criteria, Map update, Closure success=null, Closure failure=null) {

db(address, [action: 'update', collection: collection, criteria: criteria, objNew: update, write_concern: 'SAFE'],

success, failure)

}

def delete(String address, String collection, Map matcher, Closure success = null, Closure failure = null) {

db(address, [action: 'delete', collection: collection, matcher: matcher], success, failure)

}

def read(String address, String collection, Map matcher, Closure success = null, Closure failure = null) {

db(address, [action: 'findone', collection: collection, matcher: matcher,], success, failure)

}

codecentric AG

API to smoothen MongoDB usage

def exists(String address, String collection, Map matcher, Closure success = null, Closure failure = null) {

def command = [action: 'find', collection: collection, matcher: matcher, batch_size: 100]

db(address, command, success) { Map reply, Message result ->

if (reply.status == 'more-exist') {

if (success.maximumNumberOfParameters == 2)

success(reply, result)

else

success(result)

} else {

if (failure.maximumNumberOfParameters == 2)

failure(reply, result)

else

failure(result)

}

}

}

codecentric AG

API to smoothen MongoDB usage

def query(String address, String collection, Map matcher,

Map options, Closure success, Closure failure) {

int max = options.max ?: -1

int offset = options.offset ?: -1

Map orderby = options.orderby ?: null

Map keys = options.keys ?: null

def data = []

def queryHandler

queryHandler = { Map reply, Message result ->

if (reply.status == 'more-exist') {

data.addAll reply.results

result.reply([:], queryHandler)

} else if (reply.status == 'ok') {

data.addAll reply.results

success(data)

} else if (reply.status == 'ok') {

data.addAll reply.results

success(data)

} else if (failure.maximumNumberOfParameters == 2) {

failure(reply, result)

} else failure(result)

}

def command = [ action: 'find', collection: collection, matcher : matcher, batch_size: 100]

if (max >= 0) command.max = max

if (offset >= 0) command.offset = offset

if (orderby) command.orderby = orderby

if (keys) command.keys = keys

db(address, command, queryHandler, queryHandler)

}

codecentric AG

API to smoothen MongoDB usage

def query(String address, String collection, Map matcher, Closure success) {

query(address, collection, matcher, [:], success, null)

}

def query(String address, String collection, Map matcher, Closure success, Closure failure) {

query(address, collection, matcher, [:], success, failure)

}

def query(String address, String collection, Map matcher, Map options, Closure success) {

query(address, collection, matcher, options, success, null)

}

codecentric AG

Questions?

Alexander (Sascha) Klein

codecentric AGCuriestr. 270563 Stuttgart

tel +49 (0) 711.674 00 - 328fax +49 (0) 172.529 40 20alexander.klein@codecentric.de

@saschaklein

www.codecentric.de

blog.codecentric.de

03.06.14 50