Componentix logo

Componentix blog

Here we write some random thoughts and articles about software development: Java, Grails, Node.js, iPhone, iPad and more.

Save page to Internet Archive using simple bookmarklet (avoid broken links)

Just drag this link to your browser’s bookmark panel:

Archive

This would allow you to save any page to Internet Archive and cite it using the resulting link, without fear of content changing or disappearing altogether.

Let’s make sure world wide web stays web.

Disappearing apostrophes in localized messages of Grails application

We have witnessed a strange phenomenon recently. Well, when I say ‘strange’ I mean it looked very strange when the mystery wasn’t resolved, but when you come to understand it, it is not strange at all, and has perfect explanation, but first things first.

What happened?

Initially, we had a controller that would send an e-mail message and then inform the user that the message was sent:


class EmailSendingController {

    def sendSomeMail(EmailRequestCommand cmd) {
        // .... checking validity of the command's parameters here
        sendMail {
            to cmd.emailAddress // E-mail address is a request parameter. It is properly validated of course
            // .... doing some e-mail sending work here - standard Mail Plugin stuff
        }

        flash.message = message(code: "success.email.sent.label", default: "E-mail sent")
        redirect(mapping: "somewhere")
    }
}
Read more...

NoClassDefFoundError in Grails app using XWiki with Markdown syntax

If you are trying to run Grails application you are developing and have an exception that says something on the order of: BeanCreationException: Error creating bean with name 'fooService': Initialization of bean failed; nested exception is java.lang.NoClassDefFoundError: net/sf/cglib/core/DebuggingClassWriter (where fooService is obviously a placeholder for a service from your application, or even from a plugin that your application uses), then odds are you have a JAR-file conflict. But simply knowing that you have a JAR-file conflict isn’t enough, the important part is resolving the issue of course, and in this blog post we’ll talk about the issue we were having the other day and ways to fix it.

In our case, the JAR-conflict was caused by the use of XWiki Rendering Plugin in our Grails 2.2.3 application (although it also might happen if you simply use the XWiki Rendering Framework itself, without the Grails plugin). It will only happen though if you install the JAR that supports Markdown syntax — of all the rendering syntaxes supported by the framework, only Markdown (indirectly) relies on the ASM library. The bad thing about this is that Grails also relies on ASM library (more specifically, cglib framework relies on ASM, hence the message of the original exception mentions a class from cglib). Why is this bad? Well, the fact that Grails and XWiki both rely on ASM is not bad at all by itself. What is really bad about it is that they both rely on completely different, incompatible versions of the same library. So when the application is being run, both JARs are included into classpath, and that really confuses cglib (XWiki renderer works fine though).

Read more...
Tags: grails

Hosted CI: new features and pricing update

Hosted CI is a hosted continuous integration service (developed by me) which is specifically tailored for iOS and Mac developers.

Since the launch of Hosted CI private beta we’ve added some useful features:

As you may have noticed – Hosted CI now has own blog where I post feature updates and some useful tips for developers.

Note that we also have updated our pricing plans.
There is a new Indie plan which is only $19/month + Max plan now costs only $199/month.

BTW, if you don’t have access to private beta yet or have any problems with it – write to ask@hosted-ci.com

Debug usage of Objective-C weak properties with KVO

I’ve recently stumbled upon Mike Abdullah’s blog post which has raised an interesting issue. When ARC is used, weak properties can be set to nil by the runtime. However if you register for KVO notifications using addObserver method, you won’t get notified of it. This is interesting, as it basically breaks KVO compliance. It is also worth noting that other updates of such properties still result in notifications.

Mike proposed that debugging code can be injected to check for the weak properties used with addObserver. I’ve decided to implement such code:

#import <objc/runtime.h>

void MethodSwizzle(Class c, SEL origSEL, SEL overrideSEL)
{
    Method origMethod = class_getInstanceMethod(c, origSEL);
    Method overrideMethod = class_getInstanceMethod(c, overrideSEL);
    if (class_addMethod(c, origSEL, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
        class_replaceMethod(c, overrideSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    } else {
        method_exchangeImplementations(origMethod, overrideMethod);
    }
}

@implementation NSObject (KVOWeakPropertyDebug)

+ (void)load
{
    MethodSwizzle(self, @selector(addObserver:forKeyPath:options:context:), @selector(_addObserver:forKeyPath:options:context:));
}

- (BOOL)_isWeak:(NSString *)keyPath
{
    // TODO: Support complex keyPath variants
    objc_property_t property = class_getProperty(self.class, keyPath.UTF8String);
    if (property) {
        return property_getAttributes(property)[3] == 'W';
    }

    return NO;
}

- (void)_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
    if ([self _isWeak:keyPath]) {
        NSLog(@"WARNING: observing '%@' property, which is weak and so isn't fully KVO-compliant", keyPath);
    }

    [self _addObserver:observer forKeyPath:keyPath options:options context:context];
}

@end

Basically it is a category on NSObject that replaces addObserver method with own implementation using method swizzling. It checks whether observed key corresponds to weak property and logs a warning in such case, then just calls original implementation.

For convenience grab complete code as Gist.

Shameless plug

Check out our new service for iOS developers – hosted continuous integration.

App.net bookmarklet to share the current webpage

Recently I've joined the App.net service, which is basically developer-friendly Twitter.

I couldn’t find any bookmarklet to use for sharing the links on it, so I decided to make one.

So here it is (drag following link to bookmarks panel of your browser). Share on App.net.

Enjoy responsibly :)

UPD: A variant that opens new window (thanks to Amr Eldib) – Share on App.net.

Tags: javascript web

Customize UITableViewCell background for grouped table view using UIBezierPath

When using UITableView in iOS apps quite often you’d like to customize its design. In particular it is typical to change background color of cells and color of separators. It is usually trivial enough with plain style table views, however may get tricky when you have grouped style table view.

The problem is that if you change backgroundColor property of a cell in a grouped table view you don’t get expected result. So the solution is to change backgroundView of a cell instead. Quite common technique is to use images and so UIImageView for the purpose. However if you want to keep standard look of cells but just change background color and border color it isn’t convenient.

So I’ve createad a reusable background view for this purpose. Thanks to UIBeizierPath class its implementation is trivial, basically main code is drawRect: method:

- (void)drawRect:(CGRect)rect
{
    CGRect bounds = self.bounds;
    UIBezierPath *path;
    if (position == CellPositionSingle) {
        path = [UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:kCornerRadius];
    } else if (position == CellPositionTop) {
        bounds.size.height += 1;
        path = [UIBezierPath bezierPathWithRoundedRect:bounds
                                     byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight
                                           cornerRadii:CGSizeMake(kCornerRadius, kCornerRadius)];
    } else if (position == CellPositionBottom) {
        path = [UIBezierPath bezierPathWithRoundedRect:bounds
                                     byRoundingCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight
                                           cornerRadii:CGSizeMake(kCornerRadius, kCornerRadius)];
    } else {
        bounds.size.height += 1;
        path = [UIBezierPath bezierPathWithRect:bounds];
    }

    [self.fillColor setFill];
    [self.borderColor setStroke];
    [path fill];
    [path stroke];
}

Using the code is simple, it goes like this:

- (void)drawRect:(CGRect)rect
{
    CGRect bounds = CGRectInset(self.bounds,
                                0.5 / [UIScreen mainScreen].scale,
                                0.5 / [UIScreen mainScreen].scale);
    UIBezierPath *path;
    if (position == CellPositionSingle) {
        path = [UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:kCornerRadius];
    } else if (position == CellPositionTop) {
        bounds.size.height += 1;
        path = [UIBezierPath bezierPathWithRoundedRect:bounds
                                     byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight
                                           cornerRadii:CGSizeMake(kCornerRadius, kCornerRadius)];
    } else if (position == CellPositionBottom) {
        path = [UIBezierPath bezierPathWithRoundedRect:bounds
                                     byRoundingCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight
                                           cornerRadii:CGSizeMake(kCornerRadius, kCornerRadius)];
    } else {
        bounds.size.height += 1;
        path = [UIBezierPath bezierPathWithRect:bounds];
    }

    [self.fillColor setFill];
    [self.borderColor setStroke];
    [path fill];
    [path stroke];
}

As a result you’ll get something like here:

Note that by using [UIColor colorWithPatternImage:] you can use not only solid colors but textures or gradients as cell background.

Full code is available as Gist, feel free to fork it.

Work with libxmljs DOM as with plain Javascript objects, similar to E4X

Overview

libxmljs-easy is a Node.js module which simplifies XML traversing, similar to E4X.

Installation

npm install libxmljs-easy

Usage

Use module

var easy = require("libxmljs-easy");

Parse XML

var xml = easy.parse('<books><book name="Lord of the Rings">' +
                         '<author name="J. R. R. Tolkien" />' +
                         '<language>English</language>' +
                      '</book></books>');

Select elements from collections explicitly

assert.equal(xml.book[0].$name, "Lord of the Rings");
assert.equal(xml.book[0].author[0].$name, "J. R. R. Tolkien");

Use shorthands (works well for case when there is single child element with given name)

assert.equal(xml.book.$name, "Lord of the Rings");
assert.equal(xml.book.author.$name, "J. R. R. Tolkien");

Basically the idea is that you construct a path from tag names, which can optionally end with attribute name prefixed with “$”.

When index is ommited – the array of elements is matched. When attribute is accessed on such array, its value is concatenated string of attribute values for each of elements in the array.

There is also original DOM element available as “$” property
of individual converted elements.

assert.equal(xml.book.language[0].$.text(), "English");

Further info

Git aliases inspired by GUM, a better UI for Git

Some of the commands provided by Git are quite confusing and GUM project tries to address it.

However it looks like most useful stuff can also be achieved through use of Git aliases:

[alias]
    stage = !sh -c '[[ -z "$@" ]] && git add -u || git add "$@" && git add -u "$@" ' -
    unstage = reset HEAD --
    rewind = ![[ -z "$@" ]] && git reset --hard HEAD || git checkout HEAD

These aliases provide such new commands:

  • git stage – stages all changes to already tracked files (including deletions)
  • git stage FILES – stages changes to given files only (including deletions and additions of new files)
  • git unstage – removes everything from staging area (working copy remains unchanged)
  • git unstage FILES – removes given files from staging area (working copy remains unchanged)
  • git rewind – resets all changes done to the working copy (including staging area)
  • git rewind FILES – resets all changes done to the given files (including staging area)

Code available as Gist: https://gist.github.com/1707519

Tags: bash git

Scary Colors – coloring book app for iPad

We’ve been long developing iOS apps for various clients and now finally released something own in the AppStore. It’s a coloring book for children, dedicated to Halloween theme.

There is a lot of similar apps, however we have some unique feature, that I wanted since I was child – color stays inside of lines when painting :)

See it here: http://happycolorsapp.com

Tags: ipad ios kids

Custom calendar view for iPhone

When developing MTS Artguide app I needed to use calendar view. While there are existing implementations (Kal and TKCalendarMonthView from Tapku Library, they are quite limited. In particular they both replicate styling and functionality of iPhone Calendar app and there is basically no easy way to adjust them. In addition to this – only portrait view is supported in them. And I absolutely had to support landscape mode.

So I developed my own control. ios-calendar is a stylable month calendar view for use in iPhone applications. It uses Three20 framework heavily, especially for styling.

It’s default design is heavily inspired by MoMa iPhone app.

Info on usage and source code are available on GitHub: http://github.com/vgrichina/ios-calendar

Source code is provided under MIT license, feel free to fork it and use as you wish.

Shameless plug

Check out our new service for iOS developers – hosted continuous integration.

Using JSONP with Three20

We use excellent Three20 framework in most of our iPhone projects. One of the great things provided by it is convenient framework for making HTTP requests to web services. There are plugins available to parse both JSON and XML responses. However for one of the projects I needed to parse JSONP response and I haven’t found any ready to use solution.

So I decided to make my own implementation of JSONP response parser, based on available TTURLJSONResponse class. Here it goes:

@implementation JSONPResponse

// This method is based on the same-named method in TTURLJSONResponse
// Note, that only SBJSON suport is left
- (NSError *) request: (TTURLRequest *) request processResponse: (NSHTTPURLResponse *) response
                 data: (id) data {

    // This response is designed for NSData objects, so if we get anything else it's probably a
    // mistake.
    TTDASSERT([data isKindOfClass: [NSData class]]);
    TTDASSERT(nil == _rootObject);
    NSError *err = nil;
    if ([data isKindOfClass: [NSData class]]) {
        NSString *json = [[[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding] autorelease];

        // Remove JSONP wrapper

        json = [json stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
        NSRange openingBracket = [json rangeOfString: @"("];
        NSRange closingBracket = [json rangeOfString: @")" options: NSBackwardsSearch];

        if (openingBracket.location != NSNotFound && closingBracket.location != NSNotFound) {
            json = [json substringWithRange:
                    NSMakeRange(openingBracket.location + 1, closingBracket.location - openingBracket.location - 1)];

            // Parse JSON
            _rootObject = [[json JSONValue] retain];
        }

        // Report error if failed to parse
        if (!_rootObject) {
            err = [NSError errorWithDomain: kTTExtJSONErrorDomain
                                      code: kTTExtJSONErrorCodeInvalidJSON
                                  userInfo: nil];
        }
    }

    return err;
}

@end

Full source code is available as Gist: https://gist.github.com/1052330

Read more...

Change tooltip display delay in Cocoa application

In most cases default tooltip delay of OS X (around 1000 ms) isn’t an issue, as in general you rarely would like to see tooltips all the time you move mouse around.

However while developing one application I had requirement to make tooltip appear faster than that, i.e. decrease tooltip display delay. Hopefully there is a way to do it in OS X.

Tooltip display delay is a property configurable through NSUserDefaults mechanism. So to set it to 50 ms, you can do following:

defaults write -g NSInitialToolTipDelay -int 10000

However that would change preference for whole system, which isn’t something that is desired. But it isn’t a problem – you can set it just for single application:

defaults write com.appdomain.AppName NSInitialToolTipDelay -int 10000

com.appdomain.AppName should be the bundle identifier of the application to change preference for. Of course you can also just automate it in your application:

// Alter tooltip delay setting
[[NSUserDefaults standardUserDefaults] setObject: [NSNumber numberWithInt: 50]
                                          forKey: @"NSInitialToolTipDelay"];

That’s all, hopefully you find it useful.

Resolving problem with SSL certificates in OpenJDK

When configuring new server at linode hosting I decided to try to use OpenJDK to run Tomcat. The system I installed was Debian Lenny.

It all went smoothly and I didn’t have any problems, until I tried to deploy application using Facebook API. I immediately have faced a problem when trying to login using Facebook – javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated.

Application was based on Grails and used HTTPBuilder to do requests. It uses Apache HttpClient under the hood.

Googling revealed some workarounds for the problem, but they all required hacking some additional code to override SSL certificates validity checks. The matter was that the same exception appeared when one tried to connect to server with self-signed certificate.

However it wasn’t the case for me as Facebook’s certificate wasn’t self-sgned of course :) So I dug further. It turned out that for some reason OpenJDK in Debian Lenny was installed without any CA (certificate authorities). Also it looked like Debian Squeeze solves this problem.

So the solution for Debian Lenny is simple – download and install appropriate packages from Squeeze . Good news is that certificates come in their own packages:

wget "http://ftp.us.debian.org/debian/pool/main/c/ca-certificates/ca-certificates_20090814+nmu2_all.deb"
sudo dpkg -i ca-certificates_20090814+nmu2_all.deb
wget "http://ftp.us.debian.org/debian/pool/main/c/ca-certificates-java/ca-certificates-java_20100412_all.deb"
sudo dpkg -i ca-certificates-java_20100412_all.deb

ca-certificates is available in Lenny, but it needs to be updated as a dependency to ca-certificates-java

Hosting Grails web applications using Tomcat and Nginx (our configuration)

Intro

To host Grails web application Apache Tomcat is quite a natural choice. However a big part of what is served are just static files (images, CSS, javascript code) or rarely changed pages. Using Tomcat to serve all that stuff would be a waste of resources, especially when running on small VPS like we do.
Nginx is a small efficient web server that helps with this task. It can be setup to both serve static files and act as reverse proxy to Tomcat running the Grails application. Even more, it can cache some of the web pages so that it sends less requests to Tomcat.
In this post I’ll highlight important parts of Nginx and Tomcat configuration files needed to achieve setup like this.

Nginx configuration

For Nginx configuration you can start from its default config file. Let’s add following to http section:

# Enable GZip compression
gzip                on;
gzip_http_version   1.1;
gzip_min_length     1000;
gzip_buffers        16 8k;
gzip_disable        "MSIE [1-6] \.";
gzip_types          text/html text/css text/xml application/x-javascript application/atom+xml text/plain
gzip_vary           on;

# Set proxy cache path
proxy_cache_path /var/nginx/cache keys_zone=one:10m;

# Main website Tomcat instance
upstream main {
    server localhost:8080;
}

This will enable GZip compression, set the path which we want to use for cache and set the Tomcat instance to proxy requests into.

Then it is needed to specify the configuration for the particular server. For this example virtual domain names will be used to define server (as it is most common case).

# iPad Sketchbook website server
server {
    listen       80;
    server_name  www.ipadsketchbook.com;

    # Redirect to appropriate language
    location = / {
        set_from_accept_language $lang en ru;
        rewrite ^/$ /$lang redirect;
    }

    location / {
        # Rewrite the URL for logged-in users
        if ($http_cookie ~* "JSESSIONID=([A-Z0-9]*)") {
            rewrite ^(.*)$ /loggedIn$1 last;
        }

        # Proxy all the requests to Tomcat
        proxy_pass	http://main;
        proxy_set_header  Host $http_host;

        proxy_cache one;
        proxy_cache_min_uses 1;
        proxy_cache_valid  200 302 1m;
        proxy_cache_valid  404 1m;
        proxy_cache_use_stale error timeout invalid_header http_500 http_502 http_503 http_504;
    }

    # Content for logged-in users should not be cached
    location /loggedIn {
        # Rewrite URL back before forwarding request to backend
        rewrite ^/loggedIn(.*)$ $1 break;

        proxy_pass	http://main;
        proxy_set_header  Host $http_host;

        if ($http_cookie !~* "JSESSIONID") {
            return 404;
        }
    }

    # Admin panel should not be cached
    location /admin {
        proxy_pass	http://main/admin;
        proxy_set_header  Host $http_host;
    }

    # Serve static resources
    location ~ ^/(images|css|js|google|yandex|y_key) {
        root /var/www/vhosts/ipadsketchbook.com/httpdocs;
    }

    error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }
}

There are several tricks in the aforementioned config:

  • it redirects to appropriate language version based on request headers. It is only possible if you install nginx_accept_language_module.
  • it rewrites request path for users with existing session (based on JSESSIONID cookie), so that the pages served for them aren’t cached
  • it then rewrites back request path (for logged in users), when issuing request to Tomcat
  • there is a list of path prefixes for which static files are served

Of course most likely there should also be redirect from other domains set up. It is quite easy to do:

# Redirect all the iPhone Sketchbook website domains to main domain
server {
    listen          80;
    server_name     ipadsketchbook.com ipad-sketchbook.com www.ipad-sketchbook.com;
    rewrite ^(.*)   http://www.ipadsketchbook.com$1 permanent;
}

Tomcat configuration

Of course in addition to configuring Nginx, Tomcat itself need to be configured. This is quite straightforward though. Note that only single domain name is configured, as other ones are redirected by Nginx.

So in server.xml we have such host configuration:

<Host name="www.ipadsketchbook.com" appBase="vhosts/ipadsketchbook.com"
    unpackWARs="true" autoDeploy="false"
    xmlValidation="false" xmlNamespaceAware="false">
</Host>

Note that autoDeploy is disabled, it is not needed on production server. appBase specifies the path to directory which contains applications' WAR files. Root of the domain would be served by ROOT.WAR.

Further reading

Making your development webserver publicly accessible using localtunnel

Sometimes when testing web applications it is needed to have them available from outside (i.e. in public Internet). For example some APIs you use may need to send callbacks.

A solution that helps in such case is creating SSH reverse tunnel to make your local server publicly acessible. However to do this you’ll need publicly acessible box which you can control, with SSH on it and needed ports opened on firewall.

Sometimes you just don’t have one and would like to avoid hassle with purchasing some VPS, etc. In such case localtunnel utility comes in help.

localtunnel is a tool built with specific purpose to temporarily expose local web servers to public Internet. It acts as a client for a public and open source reverse tunneling service. It’s usage is simple:

localtunnel 8080

This command makes local service at port 8080 acessible on the port 80 on the domain it outputs.

Note that of course at first you need to install and configure it.
Installation (for all users) goes like:

sudo gem install localtunnel

Then also on the first run you have to specify your SSH public key to use for authentication, like:

localtunnel -k ~/.ssh/id_rsa.pub 8080

If you don’t have SSH keypair, you can generate the keys using ssh-keygen:

ssh-keygen -t rsa

See more info and source code on Github: http://github.com/progrium/localtunnel

Tags: console ssh

Choose Grails version and configure GRAILS_HOME automatically, updated

It is quite a pain to set up GRAILS_HOME each time when you use different Grails versions and I already wrote a post about it with very simple bash script to help.

However script was oversimplified and so not working in many corner cases. After my post Yuri has come up with his version of script for Windows, using .BAT file.
Yuri’s version was more complicated and took corner cases into account.

When I have found some time to educate myself about bash, I wrote enhanced version of my script. Its usage follows additional functionality of Windows version.

Its usage is simple, there are such scenarios:

1. Run script in Grails project root directory. In such case it parses application.properties file and launches appropriate version of Grails.

Example: grails run-app

2. Run script anywhere with explicitly specified Grails version. It is needed for some tasks like grails upgrade or grails create-app. This just needs to pass version in parameter like -ver:1.3.1.

Example: grails create-app -ver:1.3.1

As in older version, to use the aforementioned script you need to set GRAILS_PREFIX variable to a value appropriate for your system. And of course your Grails distributions should be installed side-by-side in one common folder.
For example I have it set to /home/vg/tools/grails- on my Linux box.

The script itself:

#!/bin/bash

# Check if GRAILS_PREFIX is set
if [ -z $GRAILS_PREFIX ]; then
    echo "You must define environment variable GRAILS_PREFIX before running Automatic Grails Version Selector"
    exit 1
fi

# Define script params array
declare -a PARAMS

# Process script parameters
for PARAM in $*; do
    if [ ${PARAM:0:5} == "-ver:" ]; then
        # Extract version from -ver parameter
        GRAILS_VERSION=${PARAM:5}
    else
        # Add parameter to array, so that it is passed to original script
        PARAMS=( "${PARAMS[@]}" "$PARAM" )
    fi
done

# If version to use is not specified, try to detect it
if [ -z $GRAILS_VERSION ]; then
    # Check if application.properties file exists
    if [ -e application.properties ]; then
        # Get required Grails version
        # Note that CR characters are removed at first
        GRAILS_VERSION=`tr -d '\015' <  application.properties | sed -n 's/app.grails.version=\(.*\)$/\1/p'`
    else
        echo "Current directory doesn't represent existing Grails project, specify version as -ver:1.3.1"
        exit 1
    fi
fi

# Set Grails home using configured prefix and determined version
export GRAILS_HOME=$GRAILS_PREFIX$GRAILS_VERSION

# Check if GRAILS_HOME directory exists
if [ -d  $GRAILS_HOME ]; then
    # Run original Grails script
    $GRAILS_HOME/bin/grails ${PARAMS[@]}
else
    echo Grails home directory for this project does not exist: $GRAILS_HOME
    echo The current project might have updated to a newer Grails version.
    echo Make sure you have downloaded and installed the version of Grails required: $GRAILS_VERSION
    exit 1
fi

Note that the script doesn’t have the default Grails version for the case when not launched in Grails project root. Adding such configuration parameter is left as an exercise to reader :)

Tags: grails java bash

Execute Groovy script within Grails context, updated for Grails 1.3.1

Quite often it is needed to execute Groovy script within a Grails application context (including access to domain objects, controllers, services, etc). Unfortunately there is no such built-in functionality in Grails.

However Ted Naleid has written run-script Gant script to do this and published on his blog.

We used it in our project and it worked flawlessly. However it stopped working with upgrade to Grails 1.3.1 and even grabbing the latest version from bitbucket haven’t helped.

So I looked into the sources of built-in Grails scripts, including run-test and hacked run-script a little bit to make it run fine.

You can grab/fork the resulting script on github.

Also, please vote for inclusion of run-script functionality into main Grails distribution in JIRA

Using the script is easy. Just store it in src/scripts/ folder of your Grails app with RunScript.groovy filename.

Then you’ll be able to run your scripts as following:

grails run-script path/to/script1/Script1.groovy path/to/script2/Script2.groovy ...

The script itself is:

import org.codehaus.groovy.grails.commons.GrailsClassUtils as GCU
import org.springframework.orm.hibernate3.SessionFactoryUtils
import org.springframework.orm.hibernate3.SessionHolder
import org.springframework.transaction.support.TransactionSynchronizationManager

includeTargets << grailsScript("_GrailsBootstrap")
includeTargets << grailsScript("_GrailsRun")
includeTargets << grailsScript("_GrailsSettings")
includeTargets << grailsScript("_GrailsClean")

target('default': "Execute the specified script after starting up the application environment") {
    depends(checkVersion, configureProxy, packageApp, classpath)
    runScript()
}

target(runScript: "Main implementation that executes the specified script after starting up the application environment") {
    parseArguments()
    if (argsMap["params"].size() == 0) {
        event("StatusError", ["Required script name parameter is missing"])
        System.exit 1
    }
    compile()
    loadApp()
    configureApp()
    configureHibernateSession()
    argsMap["params"].each { scriptFile ->
        executeScript(scriptFile, classLoader)
    }
}

def configureHibernateSession() {
    // without this you'll get a lazy initialization exception when using a many-to-many relationship
    def sessionFactory = appCtx.getBean("sessionFactory")
    def session = SessionFactoryUtils.getSession(sessionFactory, true)
    TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session))
}

def executeScript(scriptFile, classLoader) {
    File script = new File(scriptFile)
    if (script.exists()) {
        def shell = new GroovyShell(classLoader, new Binding(ctx: appCtx, grailsApplication: grailsApp))
        shell.evaluate(script.text)
    } else {
        event("StatusError", ["Designated script doesn't exist: $scriptFile"])
    }
}
 

Mocking configuration in Grails unit tests

While testing some of the classes in Grails app, I had problem that the tests failed because Grails config (ConfigurationHolder.config) isn’t populated (is null) when unit tests are executed. Some Googling found me issue in JIRA which explained it.

Graeme Rocher commented that there is mockConfig method which allows to specify mocked config to be used for tests. It can be called in setUp method to do necessary initialization.

It is convenient to use it with multi-line strings. Its usage goes as following:

mockConfig '''
    foo.bar = "good"
'''

It is discussed in more details in mrhaki's post.

File uploads using Node.js: once again

I’ve already wrote an article about file uploads in Node.js before. However Node.js is in rapid development and so many things have changed since then.

So, I decided to do an updated version, which accommodates for such changes:

  • events module was removed
  • posix module was replaced by fs
  • various changes into HTTP request/response API
  • multipart module refactored into separate project
  • convenient fs.createWriteStream instead of low-level API

See the code snippet under the cut, or on github. Special thanks goes to Felix Geisendörfer for some useful suggestions.

Please note that you’ll need to install multipart module for the code to work.

To make it clear, Node.js version used was v. 0.1.91.

UPD: Fixed stream.onData handler. It was writing corrupted data (UTF-8 character were translated) because of binary mode not being used. Thanks to Ron Ward for suggesting solution.

Read more...
12Next
Following e-mail is only for robots (never send anything to it, or you would be blacklisted): botsonly@componentix.com