Loading...

Friday, September 24, 2010

Gradle Goodness: Define A Short Plugin Id For Custom Plugins

We can extend Gradle easily with the Gradle plugin system. We can write plugins and apply them on our build scripts with the apply() method. We can use the complete classname of the plugin as an argument, but also a short plugin id. For example if we want to apply the Java plugin we can write apply plugin: 'java'. 'java' is the short plugin id for the class org.gradle.api.plugins.JavaPlugin.

We can define a short plugin id for our own custom plugins as well, so it is much easier to apply them in our scripts. In the classpath of our plugin we must add the directory META-INF/gradle-plugins. In this directory we create a properties file where the name is equal to the short plugin id we want to use. Inside the property file we must define a single property implementation-class that points to our plugin class.

Let's see how we can do this in the following sample. We first create a very simple Gradle plugin which only adds a task demo to the project. The task prints out the message MrhakiPlugin says hi!.

// File: buildSrc/src/main/groovy/com/mrhaki/gradle/MrHakiPlugin.groovy
package com.mrhaki.gradle

import org.gradle.api.*

class MrHakiPlugin implements Plugin<Project> {
    def void apply(Project project) {
        project.task('demo') << {
            println "MrhakiPlugin says hi!"
        }
    }
}

Next we create the properties file mrhaki.properties, because we want to use mrhaki as the short plugin id.

# File: buildSrc/src/main/resources/META-INF/gradle-plugins/mrhaki.properties
implementation-class=com.mrhaki.gradle.MrHakiPlugin

And that is all we need to do. Let's create a build.gradle script and execute our plugin:

// File: build.gradle
apply plugin: 'mrhaki'
$ gradle -q demo
MrhakiPlugin says hi!

Gradle Goodness: Run Java Application From Build Script

Gradle has a special task to run a Java class from the build script: org.gradle.api.tasks.JavaExec. We can for example create a new task of type JavaExec and use a closure to configure the task. We can set the main class, classpath, arguments, JVM arguments and more to run the application.

Gradle also has the javaexec() method available as part of a Gradle project. This means we can invoke javaexec() directly from the build script and use the same closure to configure the Java application that we want to invoke.

Suppose we have a simple Java application:

// File: src/main/java/com/mrhaki/java/Simple.java
package com.mrhaki.java;

public class Simple {

    public static void main(String[] args) {
        System.out.println(System.getProperty("simple.message") + args[0] + " from Simple.");
    }

}

And we have the following Gradle build file to run Simple:

// File: build.gradle
apply plugin: 'java'

task(runSimple, dependsOn: 'classes', type: JavaExec) {
    main = 'com.mrhaki.java.Simple'
    classpath = sourceSets.main.runtimeClasspath
    args 'mrhaki'
    systemProperty 'simple.message', 'Hello '
}

defaultTasks 'runSimple'

// javaexec() method also available for direct invocation
// javaexec {
//    main = 'com.mrhaki.java.Simple'
//    classpath = sourceSets.main.runtimeClasspath
//    args 'mrhaki'
//    systemProperty 'simple.message', 'Hello '
// }

We can execute our Gradle build script and get the following output:

$ gradle
:compileJava
:processResources
:classes
:runSimple
Hello mrhaki from Simple.

BUILD SUCCESSFUL

Total time: 4.525 secs

Thursday, September 23, 2010

Gradle Goodness: Get User Input Values From Console

Gradle scripts are Groovy scripts. So we can use all the functionality of Groovy in our Gradle scripts. And because we can use Java classes as well, we can simply get user input values from the console using java.io.Console. With the Console class we can for example ask for a value to be used in our script. We can even ask for a password without the password being echoed on the console.

In the following sample build script we have a ask task that uses Console to let the user enter a username and password value. In the print task we use the values that are entered to print them to the console.

def username
def password

task ask << {
    def console = System.console()
    if (console) {
        username = console.readLine('> Please enter your username: ')
        password = console.readPassword('> Please enter your password: ')
    } else {
        logger.error "Cannot get console."
    }
}

task print << {
    println "Hello $username! Your password is ${password.size()} characters."
}

We can run the script (suppose we use help as password):

$ gradle -q ask print
> Please enter your username: mrhaki
> Please enter your password: 
Hello mrhaki! Your password is 4 characters.

Wednesday, September 22, 2010

Grassroots Groovy: Configure Logback with Groovy

Sometimes we have to work on a project with only Java source files. But we can introduce some small Groovy stuff at grassroots level if we use Logback as a logging framework. Logback supports configuration by a Groovy script file. This means we can define the configuration with Groovy code instead of the default XML.

The following sample is a very small Maven project for a Java application. We use the Maven exec plugin so we can run the application directly from the command-line. We add the necessary dependencies to the pom.xml so we can write the Logback configuration in Groovy code. We must add the Groovy JAR file so Logback can execute the configuration file.

<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.mrhaki</groupId>
    <artifactId>simple-app</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>java</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <mainClass>com.mrhaki.java.Simple</mainClass>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>0.9.24</version>
        </dependency>
        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>1.7.5</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
</project>

Our Java class is simple, just as the name suggests. We only want to demonstrate the different log levels that are used and how we can use that in our Logback configuration.

// File: src/main/java/com/mrhaki/java/Simple.java
package com.mrhaki.java;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Simple {
    private static final Logger LOGGER = LoggerFactory.getLogger(Simple.class);

    public Simple() {}

    public String sayHelloTo(String name) {
        return "Hello, " + name;
    }

    public static void main(String[] args) {
        LOGGER.info("Starting application.");
        Simple app = new Simple();
        String hello = app.sayHelloTo("mrhaki");
        LOGGER.debug("Saying: " + hello);
        LOGGER.info("Application ends.");
    }
}

Now it is time to look at the Groovy Logback configuration. Logback provides a simple DSL to configure loggers, appenders and other Logback configuration elements.

// File: src/main/resources/logback.groovy
import static ch.qos.logback.classic.Level.*
import ch.qos.logback.core.ConsoleAppender
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
import ch.qos.logback.core.status.OnConsoleStatusListener
import ch.qos.logback.core.FileAppender
import com.mrhaki.java.logsupport.SmileyConverter

displayStatusOnConsole()
scan('5 minutes')  // Scan for changes every 5 minutes.
setupAppenders()
setupLoggers()

def displayStatusOnConsole() {
    statusListener OnConsoleStatusListener 
}

def setupAppenders() {
    def logfileDate = timestamp('yyyy-MM-dd') // Formatted current date.
    // hostname is a binding variable injected by Logback.
    def filePatternFormat = "%d{HH:mm:ss.SSS} %-5level [${hostname}] %logger - %msg%n"
    appender('logfile', FileAppender) {
        file = "simple.${logfileDate}.log"
        encoder(PatternLayoutEncoder) {
            pattern = filePatternFormat
        }
    }

    // Add custom converter for %smiley pattern.
    conversionRule 'smiley', SmileyConverter

    appender('systemOut', ConsoleAppender) {
        encoder(PatternLayoutEncoder) {
            pattern = "%-5level %-12logger{12} %smiley - %msg%n"
        }
    }
}

def setupLoggers() {
    logger 'com.mrhaki.java.Simple', getLogLevel(), ['logfile']
    root DEBUG, ['systemOut']
}

def getLogLevel() {
    (isDevelopmentEnv() ? DEBUG : INFO)
}

def isDevelopmentEnv() {
    def env =  System.properties['app.env'] ?: 'DEV'
    env == 'DEV'
}

Logback looks for a file named logback.groovy in the classpath of the application. If the file is found it is parsed by a GroovyShell instance and Logback is configured.

We start by using the statusListener() method (line 14) so Logback logs the configuration details to the console.

To add a new appender, we use the appender() method. We use a closure to configure the appender. Loggers are configured with the logger() and root() methods. We can define the loglevel and a list of appenders that need to be used.

At line 9 we use the scan() method to define the scan interval used by Logback to check for changes in logback.groovy. If the file has changed, the new configuration is used.

At the bottom of the configuration file we define the method isDevelopmentEnv() to see if the application is running in development mode. If so we set the log level to DEBUG, otherwise it is set to INFO (see methods getLogLevel() and setupLoggers()). And because the configuration is a Groovy script this is very easy to achieve.

With the conversionRule() method at line 29 we assign the SmileyConverter class to the %smiley variable in the encoding pattern. The code for the SmileyConverter is here:

// File: src/main/java/com/mrhaki/java/logsupport/SmileyConverter.java
package com.mrhaki.java.logsupport;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.pattern.ClassicConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;

import static ch.qos.logback.classic.Level.*;

public class SmileyConverter extends ClassicConverter {
    @Override
    public String convert(ILoggingEvent event) {
        return getSmiley(event.getLevel());
    }

    // Return a different smiley for different log levels.
    private String getSmiley(Level level) {
        String result = "";
        switch (level.toInt()) {
            case DEBUG_INT:
                result = ";)";
                break;
            case INFO_INT:
                result = ":)";
                break;
            default:
                break;
        }
        return result;
    }
}

We are ready to run the application and see our logging configuration at work:

$ mvn -q compile exec:java

11:01:52,087 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@6fa8bd74 - Added status listener of type [ch.qos.logback.core.status.OnConsoleStatusListener]
11:01:52,107 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@6fa8bd74 - Setting ReconfigureOnChangeFilter scanning period to 5 minutes
11:01:52,108 |-INFO in ch.qos.logback.classic.turbo.ReconfigureOnChangeFilter@14a97f68 - Will scan for changes in file [/Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/logback/target/classes/logback.groovy] every 300 seconds. 
11:01:52,109 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@6fa8bd74 - Adding ReconfigureOnChangeFilter as a turbo filter
11:01:52,170 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@6fa8bd74 - About to instantiate appender of type [ch.qos.logback.core.FileAppender]
11:01:52,171 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@6fa8bd74 - Naming appender as [logfile]
11:01:52,311 |-INFO in ch.qos.logback.core.FileAppender[logfile] - File property is set to [simple.2010-09-21.log]
11:01:52,329 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@6fa8bd74 - registering conversion word smiley with class [com.mrhaki.java.logsupport.SmileyConverter]
11:01:52,380 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@6fa8bd74 - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]
11:01:52,382 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@6fa8bd74 - Naming appender as [systemOut]
INFO  c.m.j.Simple :) - Starting application.
DEBUG c.m.j.Simple ;) - Saying: Hello, mrhaki
INFO  c.m.j.Simple :) - Application ends.

First we see the results from Logback, because we used the statusListener() method. Then at the bottom we see three output lines from our Simple java code. Now let's run the application again, but this time we use a system property to set the environment to a non-development one:

$ mvn -q -Dapp.env=TEST compile exec:java

11:04:01,588 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@563b100c - Added status listener of type [ch.qos.logback.core.status.OnConsoleStatusListener]
11:04:01,607 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@563b100c - Setting ReconfigureOnChangeFilter scanning period to 5 minutes
11:04:01,609 |-INFO in ch.qos.logback.classic.turbo.ReconfigureOnChangeFilter@364e50ee - Will scan for changes in file [/Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/logback/target/classes/logback.groovy] every 300 seconds. 
11:04:01,610 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@563b100c - Adding ReconfigureOnChangeFilter as a turbo filter
11:04:01,655 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@563b100c - About to instantiate appender of type [ch.qos.logback.core.FileAppender]
11:04:01,656 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@563b100c - Naming appender as [logfile]
11:04:01,811 |-INFO in ch.qos.logback.core.FileAppender[logfile] - File property is set to [simple.2010-09-21.log]
11:04:01,832 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@563b100c - registering conversion word smiley with class [com.mrhaki.java.logsupport.SmileyConverter]
11:04:01,837 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@563b100c - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]
11:04:01,841 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@563b100c - Naming appender as [systemOut]
INFO  c.m.j.Simple :) - Starting application.
INFO  c.m.j.Simple :) - Application ends.

No if we look at the last two lines we see the DEBUG message is not shown anymore. That is because we changed the log level accordingly to the sytem property app.env..

As we can see introducing Groovy into a Java project is easy if we use Logback. We only have to add the Groovy JAR as a dependency and we are ready to go. And once that Groovy JAR dependency is in the project definition... (grin)

Tuesday, September 21, 2010

Mount a SMB Share from the Command-Line

After reading http://www.hardcode.nl/archives_172/article_422-mount-smb-share-from-command-line.htm I learned how easy it is to mount a SMB share from a Terminal window:

$ mkdir /Volumes/name
$ mount -t smbfs //userName@serverName/nameOfShare /Volumes/name

Gradle Goodness: Different Ways to Set Project Properties

We can add properties to our Gradle build script in several different ways.

  • First of all we can define the properties in the script itself.
  • Or we can use the -P command-line argument to pass a property to the build script.
  • We can define a gradle.properties file and set the property in this file. We can place the file in our project directory or in the <USER_HOME>/.gradle directory. The properties defined in the property file in our home directory take precedence over the properties defined in the file in our project directory. As a bonus we can also define system properties in a gradle.properties file, we only have to prefix the property name with systemProp..
  • We can use an environment variable of which the name starts with ORG_GRADLE_PROJECT_ followed by the property name.
  • Or we use the Java system property that starts with org.gradle.project. followed by the property name.

 

The following sample Gradle build file uses all these techniques to get the value of properties:

description = '''\
To run this build script we must first 
set an environment variable 
ORG_GRADLE_PROJECT_property5=environment property

Run as: 
gradle -Property4="argument property" -Dorg.gradle.project.property6="system property"
'''

property1 = 'Project property'

task assertProps << {
 description: 'Print different properties'
 assert 'Project property' == property1
 assert 'gradle.properties property' == property2
 assert 'argument property' == property4
 assert 'environment property' == property5
 assert 'system property' == property6
 assert 'gradle.properties system property' == System.properties['property3']
}

defaultTasks 'assertProps'

We use the following gradle.properties file:

property2 = gradle.properties property
systemProp.property3 = gradle.properties system property

Wednesday, September 15, 2010

Private Spring Dependency Injections for Unit Testing

If we want to unit test a Spring managed bean with a private field that is annotated with for example the @Autowired annotation, we must do something special. Normally we cannot access the private field to assign for example a stub implementation for testing, because we have to use the public setter method. But Spring allows the use of the @Autowired annotation on a private field. And that means we don't have a public setter method. We must use org.springframework.test.util.ReflectionTestUtils.setField() to assign a new value to the field. We pass the object which contains the private field, the field name and value to the method and our value is assigned to the private variable. So we can use this method to assign a stub implementation to the field and use it for testing.

// Class to test: src/main/java/com/mrhaki/spring/MyService.java
package com.mrhaki.spring;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MyService {
    @Autowired
    private MessageService messageService;

    public String say(String name) {
        return messageService.getMessage() + name;
    }
}
// Support class used in MyService: src/main/java/com/mrhaki/spring/MessageService.java
package com.mrhaki.spring;

import org.springframework.stereotype.Component;

@Component
public class MessageService {
    public String getMessage() {
        return "Hello, ";
    }
}

We want to unit test MyService and provide a mock implementation for MessageService, so we only test the code in MyService. We use Mockito to provide the mock functionality. And because messageService is a private field we must use ReflectionTestUtils.setField() method.

// Test class for testing MyService: src/test/java/com/mrhaki/spring/MyServiceTest.java
package com.mrhaki.spring;

import org.junit.Test;
import org.springframework.test.util.ReflectionTestUtils;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class MyServiceTest {
    @Test
    public void sayHi() {
        MessageService messageService = mock(MessageService.class);
        when(messageService.getMessage()).thenReturn("Hi, ");

        MyService myService = new MyService();
        // Inject mock into private field:
        ReflectionTestUtils.setField(myService, "messageService", messageService);

        assertEquals("Hi, mrhaki", myService.say("mrhaki"));
    }
}

As a bonus we can use the following Gradle build script to compile and test these classes:

apply plugin: 'java'

repositories {
  mavenCentral()
}

dependencies {
  compile 'org.springframework:spring-context:3.0.4.RELEASE'
  testCompile 'junit:junit:4.8.1', 'org.mockito:mockito-all:1.8.5', 'org.springframework:spring-test:3.0.4.RELEASE'
}

Project is also available on GitHub: BlogSamples/SpringTestInjection.

Monday, September 13, 2010

Finding JAVA_HOME on Mac OSX

Today I had to built a legacy Java project on my MacBook, but my JAVA_HOME environment variable had to be defined as a Maven property. So I had to find my JAVA_HOME directory on my Mac OSX, which turned out to be very easy.

We can simply use /usr/libexec/java_home and the result is the location of our JAVA_HOME on our computer.

$ /usr/libexec/java_home
/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home

Wednesday, September 8, 2010

Force Maven Dependencies to IntelliJ IDEA Libraries

IntelliJ IDEA has great support for importing Maven projects. All dependencies defined in a Maven POM file are also available as libraries in the IntelliJ IDEA project. But today I got a big Maven project with a lot of submodules and not all dependencies ended up in the libraries section of the IntelliJ IDEA project. Luckily I only had to do Force Reimport All Maven Projects from the Maven Projects window. And afterwards all the IntelliJ IDEA projects had a complete libraries section with all dependencies.

Gradle Goodness: Changing the Gradle User Home Directory

We can change the Gradle user home directory in several ways. Gradle uses the directory .gradle in our home directory as the default Gradle user home directory. Here we can find for example the directory caches with downloaded dependencies. To change the Gradle user home directory we can set the environment variable GRADLE_USER_HOME and point it to another directory. The Gradle build script will look for this environment variable and then use the specified directory as the Gradle home directory.

$ export GRADLE_USER_HOME=/Users/mrhaki/dev/gradle
$ gradle -d -r
07:36:40.020 [main] INFO  org.gradle.launcher.Main - Starting Builder
07:36:40.120 [main] DEBUG org.gradle.launcher.Main - Gradle user home: /Users/mrhaki/dev/gradle
...

But this is not the only way to change the Gradle user home directory. We can also pass the Java system property gradle.user.home to Gradle and specify a new directory. In the following sample we use the environment variable GRADLE_OPTS to pass the Java system property, but we could also use JAVA_OPTS.

$ export GRADLE_OPTS=-Dgradle.user.home=/Users/mrhaki/dev/gradle
$ gradle -d
07:36:40.020 [main] INFO  org.gradle.launcher.Main - Starting Builder
07:36:40.120 [main] DEBUG org.gradle.launcher.Main - Gradle user home: /Users/mrhaki/dev/gradle
...

Or we can use the command-line options -g and --gradle-user-home when we run Gradle and use a different directory as a Gradle user home directory.

$ gradle -g /Users/mrhaki/dev/gradle -d
07:36:40.020 [main] INFO  org.gradle.launcher.Main - Starting Builder
07:36:40.120 [main] DEBUG org.gradle.launcher.Main - Gradle user home: /Users/mrhaki/dev/gradle
...
$ gradle --gradle-user-home=/Users/mrhaki/dev/gradle -d
07:36:40.020 [main] INFO  org.gradle.launcher.Main - Starting Builder
07:36:40.120 [main] DEBUG org.gradle.launcher.Main - Gradle user home: /Users/mrhaki/dev/gradle
...