Vert.x for World Domination


In this article I am going to show how I put Vert.x on my Raspberry Pis and my Mindstorm EV3.

The whole thing is the product of a long night of hacking so expect some updates to this article.

The associated project is on GitHub.


The core of my presentation Vert.x for World Domination was the setup I used to demonstrate the distribution capabilities of Vert.x.

What I wanted to do was a demo using “real life” conditions. No VMs or other tricks. I got the basic idea from the setup Michael uses to demo Hazelcast (see some photos here)

The idea of a portable Raspberry Pi cluster stuck with me and I built my somewhat smaller version.

The Brain

And here it comes, the portable brain:

IMG_2113

 

Parts list

  • 2 x Raspberry Pi Model B (inside the red lego-thing)
  • 2 x Loglink WL0084A Wifi-adapters
  • TP-Link TL-WR702N (the small white/blue box on the left, a very compact Wifi-router)
  • EasyAcc 5A USB power adapter (the white box on the right, make sure to user the iPad-connectors to power the Pis)

Setup

Connect the Pis to the Wifi.

Slap Raspbian on the Pis, install the embedded JDK and put Vert.x on them.

The brain is good to go.

Robby

My fearsome battle robot Robby is up next.

It’s a custom EV3-bot. Go crazy on how you want it to look like but follow my instructions on how to get Vert.x up and running. That part took me a while.

 

 

IMG_2115

 

Parts list

  • The EV3 base package
  • TP-Link TL-WN725N USB-Wifi (Michael reported some issues on his EV3, works fine on mine)
  • A micro SD card

LejOS

With EV3 Lego provided everything to make it as easy as possible to get a custom OS on your mindstorm. The LejOS project is one of those. It is aimed at Java developers and provides everything needed to get going.

Simply follow the instructions from their Wiki to get everything set up. It’s that easy.

After connecting it to Wifi you should see something like this on the display:

IMG_2116

Vert.x

Here comes the tricky part. First of all: You will have to use a fat jar as LejOS doesn’t provide a Bash and renders the provided vert.x-startup-skripts useless.Copy the generated jar to your EV3 (ahem, the root-password is “”, so just hit enter when asked):

scp <fatjar>.jar root@<ip-of-ev3>:~

Copy the cluster.xml found in your local Vert.x-installation:

scp <vertx-dir>/conf/cluster.xml root@<ip-of-ev3>:~

Afterwards log into the EV3.

There’s two issues we will be solving:

  1. Vert.x won’t bind to the Wifi-address using the -cluster-host parameter
  2. Multicast is not working

Get a Vert.x instance running on your local machine, note down the IP-address and log into your EV3:

ssh root@<ip-of-ev3>

Now open the cluster.xml for editing (vi is installed and my weapon of choice).
Disable multicast and enable tcp-ip. Add the address of your local machine (192.168.2.20 in the example).
Uncomment the interfaces block and add the address of your EV3-Wifi (the one you used to log into it)

Afterwards the config should look something like this:


<join>
<multicast enabled=”false”>
<multicast-group>224.2.2.3</multicast-group>
<multicast-port>54327</multicast-port>
</multicast>
<tcp-ip enabled=”true”>
<interface>192.168.2.20</interface>
</tcp-ip>
<aws enabled=”false”>
<access-key>my-access-key</access-key>
<secret-key>my-secret-key</secret-key>
<region>us-east-1</region>
</aws>
</join>
<interfaces enabled=”true”>
<interface>192.168.2.101</interface>
</interfaces>

Now we need to modify the start skript. Add a new file called run.sh with the following content:

#! /bin/sh
. /etc/default/lejos
export LD_LIBRARY_PATH=${LEJOS_HOME}/libjna/usr/lib/arm-linux-gnueabi/:${LEJOS_HOME}/libjna/usr/lib/jni/
/home/root/lejos/ejre1.7.0_51/bin/java -jar <nameof-fat>.jar -cp . -cluster -cluster-host 192.168.2.101

Replace the IP-Adress with the one of your EV3-Wifi (the one you used to log into it) and replace <nameof-fat> with the name of your fat-jar.

Do a chmod +x run.sh

You are ready to go.

Use ./run.sh to start Vert.x and wait. It takes around 3 minutes for everything to come up so be patient.

This is the log-output I get when running everything ( => "Succeeded in deploying module"): 


root@EV3:~# ./run.sh
Apr 07, 2014 4:53:29 PM org.vertx.java.core.logging.impl.JULLogDelegate info
INFO: Starting clustering...
Apr 07, 2014 4:54:11 PM com.hazelcast.impl.AddressPicker
INFO: Interfaces is enabled, trying to pick one address matching to one of: [192.168.2.101]
Apr 07, 2014 4:54:12 PM com.hazelcast.impl.AddressPicker
INFO: Prefer IPv4 stack is true.
Apr 07, 2014 4:54:13 PM com.hazelcast.impl.AddressPicker
INFO: Picked Address[192.168.2.101]:5701, using socket ServerSocket[addr=/0.0.0.0,localport=5701], bind any local is true
Apr 07, 2014 4:54:35 PM com.hazelcast.system
INFO: [192.168.2.101]:5701 [dev] Hazelcast Community Edition 2.6.7 (20140210) starting at Address[192.168.2.101]:5701
Apr 07, 2014 4:54:35 PM com.hazelcast.system
INFO: [192.168.2.101]:5701 [dev] Copyright (C) 2008-2013 Hazelcast.com
Apr 07, 2014 4:54:36 PM com.hazelcast.impl.LifecycleServiceImpl
INFO: [192.168.2.101]:5701 [dev] Address[192.168.2.101]:5701 is STARTING
Apr 07, 2014 4:54:38 PM com.hazelcast.impl.TcpIpJoiner
INFO: [192.168.2.101]:5701 [dev] Connecting to possible member: Address[192.168.2.20]:5701
Apr 07, 2014 4:54:38 PM com.hazelcast.impl.TcpIpJoiner
INFO: [192.168.2.101]:5701 [dev] Connecting to possible member: Address[192.168.2.20]:5702
Apr 07, 2014 4:54:39 PM com.hazelcast.impl.TcpIpJoiner
INFO: [192.168.2.101]:5701 [dev] Connecting to possible member: Address[192.168.2.20]:5703
Apr 07, 2014 4:54:40 PM com.hazelcast.nio.ConnectionManager
INFO: [192.168.2.101]:5701 [dev] 59397 accepted socket connection from /192.168.2.20:5701
Apr 07, 2014 4:54:40 PM com.hazelcast.nio.ConnectionManager
INFO: [192.168.2.101]:5701 [dev] 44524 accepted socket connection from /192.168.2.20:5702
Apr 07, 2014 4:54:40 PM com.hazelcast.nio.ConnectionManager
INFO: [192.168.2.101]:5701 [dev] 33806 accepted socket connection from /192.168.2.20:5703
Apr 07, 2014 4:54:44 PM com.hazelcast.cluster.ClusterManager
INFO: [192.168.2.101]:5701 [dev]

Members [4] {
Member [192.168.2.20]:5701
Member [192.168.2.20]:5702
Member [192.168.2.20]:5703
Member [192.168.2.101]:5701 this
}

Apr 07, 2014 4:55:20 PM com.hazelcast.impl.LifecycleServiceImpl
INFO: [192.168.2.101]:5701 [dev] Address[192.168.2.101]:5701 is STARTED
Apr 07, 2014 4:55:36 PM com.hazelcast.util.HealthMonitor
INFO: [192.168.2.101]:5701 [dev] memory.used=5.6M, memory.free=3.3M, memory.total=8.9M, memory.max=29.0M, memory.used/total=63.02% memory.used/max=19.29% load.process=92.00%, load.system=100.00%, load.systemAverage=250.00% q.packet.size=2, q.processable.size=1, q.processablePriority.size=0, thread.count=19, thread.peakCount=19, q.query.size=0, q.mapLoader.size=0, q.defaultExecutor.size=0, q.asyncExecutor.size=0, q.eventExecutor.size=0, q.mapStoreExecutor.size=0
Apr 07, 2014 4:56:06 PM com.hazelcast.util.HealthMonitor
INFO: [192.168.2.101]:5701 [dev] memory.used=8.0M, memory.free=4.2M, memory.total=12.2M, memory.max=29.0M, memory.used/total=65.18% memory.used/max=27.42% load.process=93.00%, load.system=100.00%, load.systemAverage=281.00% q.packet.size=1, q.processable.size=1, q.processablePriority.size=0, thread.count=21, thread.peakCount=21, q.query.size=0, q.mapLoader.size=0, q.defaultExecutor.size=0, q.asyncExecutor.size=0, q.eventExecutor.size=0, q.mapStoreExecutor.size=0
Apr 07, 2014 4:56:37 PM com.hazelcast.util.HealthMonitor
INFO: [192.168.2.101]:5701 [dev] memory.used=9.9M, memory.free=2.3M, memory.total=12.2M, memory.max=29.0M, memory.used/total=81.41% memory.used/max=34.25% load.process=94.00%, load.system=100.00%, load.systemAverage=288.00% q.packet.size=0, q.processable.size=1, q.processablePriority.size=1, thread.count=21, thread.peakCount=21, q.query.size=0, q.mapLoader.size=0, q.defaultExecutor.size=0, q.asyncExecutor.size=0, q.eventExecutor.size=0, q.mapStoreExecutor.size=0
Apr 07, 2014 4:57:04 PM org.vertx.java.core.logging.impl.JULLogDelegate info
INFO: Succeeded in deploying module

 

MongoDB Media Server

Code available on GitHub

The included code is a show case, not a full media server! This is also a somewhat older project, if I missed something feel free to add an issue on GitHub.

So I’ve been going through playground folder on my hard disk. That’s the place I store all my projects I did for showcasing or simply to play around with new things.
And I found a little something I did about a year ago for the Senacor DevCon. It’s a small media server project based on Spring Data and MongoDB.

I am releasing it because I think it’s a great example for the simplicity of Spring Data combined with SpringMVC.
NOTE: There’s a a python script included to import your MP3s into the database. You will need Mutagen on your machine to use it.

All the interesting things happen in the LibraryRessource. Let’s take a look at the most important parts.
The things we nee to work with are the SongRepository (a simple Spring Data repository), MongoTemplate (here be CRUD) and the GridFsTemplate, which is required for documents >16 MB (see GridFS).

@Controller
public class LibraryRessource {

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private GridFsTemplate gridFsTemplate;

    @Autowired
    private SongRepository songRepository;

Most of the @RequestMapping-annotated classes should be self explaining so I will foxus on the “tricky” ones.
getSongs(..)_ is responsible for assembling the Song-JSON documents which contain a link to the GridFS-file to start streaming. To be able to assemble I have to git hold of the MD5-checksum to reference it.

    @RequestMapping(value = "/library/songs", method = RequestMethod.GET, produces = "application/json")
    public @ResponseBody Songs getSongs(HttpServletRequest request) {
        Songs songs = new Songs();
        for (Song song: songRepository.findAll()) {
            GridFSDBFile file = gridFsTemplate.findOne(query(where("_id").is(song.getFileId())));
            String requestBase = request.getRequestURL().toString().split("song")[0];
            song.setFileHref(requestBase + "files/" + file.getMD5());
            songs.addSong(song);
        }
        return songs;
    }

The actual streaming is done in getFile(..). Thanks to the fluent-API of GridFsTemplate it turned out that the trickiest part of my application could be done in a single line of code. The only thing to be aware of is that getting hold of the HttpServletResponse used by your request is by adding it to the method signature.


    @RequestMapping(value = "/library/files/{md5}", method = RequestMethod.GET)
    public void getFile(HttpServletResponse response, @PathVariable("md5") String md5) throws IOException {
        gridFsTemplate.findOne(query(where("md5").is(md5))).writeTo(response.getOutputStream());
    }

The generated URLs can be accessed using VLC.

Using Spring Security 3 with Wicket 6-AuthRoles and JavaConfig and a little Servlet 3

Spring Security is a great framework. It unifies a lot of different authentication mechanisms into a pretty decent package. The only part that wasn’t really up to current Spring-standards was the lack of full java config support. In July they finally released the first fully java config enabled version.

In this blog post I’d like to show how to integrate this new version with Wicket 6-AuthRoles.

The project

I put all the code up on GitHub, clone it and you are ready to go. It shouldn’t be a big surprise that you have to use a Servlet 3 container. I tested the application on Tomcat 7 and Jetty 9, worked without a hitch.

How Spring and Servlet 3 Interact

Servlet 3 provides one of the most fundamental changes. It made web.xml optional. To achieve this the container will scan each war for an implementation of javax.servlet.ServletContainerInitializer to get everything going. Spring provides such a implementation with the SpringServletContainerInitializer. When it is discovered by the container it will then scan the class path for implementations of WebApplicationInitializer. And that’s where we do our stuff.

Servlet 3 Bootstrap

The class AppInitializer contains the whole code required to bootstrap the application. The only really interesting part is how to actually register the Wicket-filter. That cost me some time to figure out but the following snipped does the trick:


        WicketFilter wicketFilter = new WicketFilter(new WicketApplication()) {
            @Override
            public void init(boolean isServlet, FilterConfig filterConfig) throws ServletException {
                setFilterPath("");
                super.init(isServlet, filterConfig);
            }
        };
        FilterRegistration.Dynamic wicketFilterReg = servletContext.addFilter("wicketFilter", wicketFilter);

Auth Roles

Wicket-AuthRoles provide a complete set of annotations to cover all scenarios of authorization. In my demo application I only use @AuthorizeInstantiation.

One important thing to note is that Spring Security prepends ROLE_ to all role names. So instead of writing @AuthorizeInstantiation(Roles.USER) you will have to use @AuthorizeInstantiation(“ROLE_USER”).

Getting AuthRoles to work you need to provide a custom WebSession-implementation:


public class UserAuthenticatedWebSession extends AuthenticatedWebSession {    
    public UserAuthenticatedWebSession(Request request) {
        super(request);
    }

    @Override
    public boolean authenticate(String username, String password) {
        throw new UnsupportedOperationException("You are supposed to use Spring-Security!!");
    }

    @Override
    public Roles getRoles() {
        Roles roles = new Roles();
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        for (GrantedAuthority authority : authentication.getAuthorities()) {
            roles.add(authority.getAuthority());
        }
        return roles;
    }

}

The authenticate method is not required as Spring Security is going to take care of that. The only thing we have to do in here is to provide a getRoles-implementation which uses the SecurityContextHolder

Now we have to change the WebApplication to extend AuthenticatedWebApplication and override init, getWebSessionClass and getSignInPageClass:


@Override
public void init() {
    super.init();
    getComponentInstantiationListeners().add(new SpringComponentInjector(this));
    getSecuritySettings().setAuthorizationStrategy(new AnnotationsRoleAuthorizationStrategy(this));
    mountPage("/home", HomePage.class);
    mountPage("/login", SignInPage.class);
}

We also have to specify the /login-mountpoint this should point to a Wicket-page with the following content:


<form method="POST">
    <input type="text" id="username" name="username"/>
    <input type="password" id="password" name="password"/>
    <input type="submit" value="submit"/>
</form>

The form-submit will NOT be handled by Wicket but by the Spring Security filter!

Spring Security

The final piece of the puzzle is the Spring Security configuration.

In AppInitializer I added the following code to bootstrap the Spring context and the security filter:


    AnnotationConfigWebApplicationContext root = new AnnotationConfigWebApplicationContext();
    root.register(SpringSecurityConfiguration.class);
    FilterRegistration.Dynamic springSecurityFilterChainReg = servletContext.addFilter("springSecurityFilterChain", DelegatingFilterProxy.class);
    springSecurityFilterChainReg.addMappingForUrlPatterns(EnumSet.of(DispatcherType.ERROR, DispatcherType.REQUEST), false, "/*");
    servletContext.addListener(new ContextLoaderListener(root));

The actual configuration happens in SpringSecurityConfiguration:


@Configuration
@EnableWebSecurity
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeUrls()
                .antMatchers("/favicon.ico")
                    .permitAll()
                .antMatchers("/**")
                    .hasRole("USER")
                    .and()
                    .formLogin()
                        .loginPage("/login")
                        .permitAll();
    }

    @Override
    protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("testuser")
                    .password("password")
                    .roles("USER");
    }
}

I use configure to tell spring security to allow unrestricted access to favicon.ico. Then I tell it ti restrict access to all other URLs to users with the role USER (reminder: this translates to ROLE_USER when used in AuthRoles-annotations). The last step is to add a custom formLogin to use the mount point from before.

In registerAuthentication I add a simple in-memory-authentication. Here you can now add all the other fancy stuff.

Get it while it's hot: https://github.com/codepitbull/wicket-servlet3-springsecurity

Have fun.

Vert.X 2.0.0 in IntelliJ IDEA

Well, that was fast. After posting my last post I saw that vert.x 2.0.0.final came out last night. Tough luck but sitting on the train I got time enough to provide an update.

Project setup

Follow these to create a basic gradle-project with an example verticle. After doing a gradle clean build idea you can now import the project into Idea.

Running

The examples provided only show how to run this project from the command line. I am a lazy ass and like to run my stuff from the IDE.

To achieve this add a new java-module using File>New Module. Open Open Module Settings, select the newly created module and add contents of the lib-directory from your freshly downloaded vert.x-2.0.0-final-archive in the Dependencies-tab.

Create a new Run-Config and:

  • set Main Class to org.vertx.java.platform.impl.cli.Starter
  • set Program arguments to runmod com.yourcompany~your-module~1.0 -cp <path-to-your-project>/out/production/<module-name>
  • set Use classpath of module to the module we just created and added the vert.x-libs to

Hit run.

Enjoy.

Vert.X in IntelliJ IDEA

Well, that was fast. Outdated within minutes of posting :)
This post is for vert.x 1.3.1. 

I just wrote an update on how to do this with vert.x 2.0.0.final


I took a look at Disruptor back when it was released. It was an interesting piece of software but I never got beyond playing around. Then came Node.js. I have been fighting with myself for quite a while to use it but I am kind of biased when it comes to JavaScript… Well, then came vert.x and I finally had no excuse left to get into this single-threade-thingy-stuff. Playing around with vert.x was quite a fun experience I will write about later. Today I want to show you how to get it running in my favorite IDE.

The Problem

Being polyglot apparently also means to abandon tested deployment strategies. So instead of dumping a JAR/WAR or whatever else into the vert.x-container you will have to do some special magic. And this special magic also involves some tinkering with Idea.

Basic Project

The following (very basic) build.gradle gives you a simple vert.x project.

apply plugin:'java'

configurations {
    provided
    provided.extendsFrom(compile)
}

repositories {
    mavenCentral()
    mavenLocal()
    mavenRepo url: "https://repository.apache.org/content/repositories/snapshots/"
    mavenRepo url: "http://source.mysema.com/maven2/releases/"
    mavenRepo url: "http://repo.maven.apache.org/maven2"
}

dependencies{
        compile "org.vert-x:vertx-core:1.3.1.final"
    compile "org.vert-x:vertx-lang-java:1.3.1.final"
    compile "org.vert-x:vertx-platform:1.3.1.final"
}

Import it and add a vertical like the following:

package de.codepitbull.vertx;

import org.vertx.java.core.Handler;
import org.vertx.java.core.http.HttpServerRequest;
import org.vertx.java.deploy.Verticle;

import java.util.Map;

/**
* @author Jochen Mader
*/
public class HttpVerticle extends Verticle{
@Override
public void start() throws Exception {
vertx.createHttpServer().requestHandler(new Handler() {
public void handle(HttpServerRequest req) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> header : req.headers().entrySet()) {
sb.append(header.getKey()).append(": ").append(header.getValue()).append("\n");
}
req.response.putHeader("content-type", "text/plain");
req.response.end(sb.toString());
}
}).listen(8087);
}
}

And, due to a bug in 1.3.1, you also need to add a langs.properties to your project with the following content:

java=org.vertx.java.deploy.impl.java.JavaVerticleFactory
class=org.vertx.java.deploy.impl.java.JavaVerticleFactory
js=org.vertx.java.deploy.impl.rhino.RhinoVerticleFactory
coffee=org.vertx.java.deploy.impl.rhino.RhinoVerticleFactory
rb=org.vertx.java.deploy.impl.jruby.JRubyVerticleFactory
groovy=org.vertx.groovy.deploy.impl.groovy.GroovyVerticleFactory
py=org.vertx.java.deploy.impl.jython.JythonVerticleFactory
default=org.vertx.java.deploy.impl.java.JavaVerticleFactory

Setup

To get this whole thing running you will need to download the vert.x tar and untag it to your file system. Next select File>New Module and create a new Java-Module using the wizard. After you are done open Open Module Settings and select your newly created module. Go to the Dependencies-tab and add the content of your vert.x-installations lib-directory. Next add the folder containing langs.properties. Almost there.

Shows the dependency screen in idea with all required deps added.

Start Config

Now that we got our little dummy-project up and going we need to create a start config. So create a new Application-Startconfig. - Set the Main-class to org.vertx.java.deploy.impl.cli.Starter - Set Program arguments to run de.codepitbull.vertx.HttpVerticle -cp where the path is the place where Idea puts your compiled classes from the gradle-project. - Set Use class path of module to the module we just configured. That’s it.

Shows the filled fields as described in the text.

Hit run and go to home sweet home

Serving static content with Jetty 9

I am currently (again) doing a lot of JavaScript related stuff. My favorite IDE (IntelliJ IDEA) has stellar support for this crappy language. The only thing I needed was a SMALL web server to play around with static html and some JavaScripts. The quickest way was to use Jetty 9 as it can be found on all of my development machines.

Just add a Jetty9-RunConfig in IDEA (or Eclipse, if you have to) and create static.xml with the following content in $JETTY_HOME/webapps.

<?xml version="1.0"  encoding="ISO-8859-1"?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.eclipse.org/configure.dtd">

<Configure class="org.eclipse.jetty.server.handler.ContextHandler">
  <Set name="contextPath">/static</Set>
  <Set name="resourceBase">/path/to/your/document/root/</Set>
  <Set name="handler">
    <New class="org.eclipse.jetty.server.handler.ResourceHandler">
      <Set name="cacheControl">no-cache</Set>
    </New>
  </Set>
</Configure>

Don’t forget to adjust resourceBase to your liking.

Note: I also disabled caching in this example.

Bootstrapping Neo4j With Spring-Data – Without XML

With the maturing of Spring-Data I started porting all my personal projects to use Spring Data for bootstrapping. I also wanted to get rid of XML, which proved a little more tricky as I expected.

Dependencies

Let’s start with the required dependencies:

Update: Added a missing validator-dependency

 <properties>
 <spring-core.version>3.2.0.RELEASE</spring-core.version>
 </properties>
 <dependencies>
 <dependency>
 <groupId>org.springframework.data</groupId>
 <artifactId>spring-data-neo4j</artifactId>
 <version>2.2.1.RELEASE</version>
 <exclusions>
 <exclusion>
 <groupId>org.springframework</groupId>
 <artifactId>spring-asm</artifactId>
 </exclusion>
 </exclusions>
 </dependency>
 <dependency>
 <groupId>org.neo4j.app</groupId>
 <artifactId>neo4j-server</artifactId>
 <version>1.8.1</version>
 </dependency>
 <dependency>
 <groupId>org.neo4j.app</groupId>
 <artifactId>neo4j-server</artifactId>
 <classifier>static-web</classifier>
 <version>1.8.1</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-core</artifactId>
 <version>${spring-core.version}</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-context</artifactId>
 <version>${spring-core.version}</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-aop</artifactId>
 <version>${spring-core.version}</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-aspects</artifactId>
 <version>${spring-core.version}</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-beans</artifactId>
 <version>${spring-core.version}</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-expression</artifactId>
 <version>${spring-core.version}</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-tx</artifactId>
 <version>${spring-core.version}</version>
 </dependency>
 <dependency>
 <groupId>cglib</groupId>
 <artifactId>cglib</artifactId>
 <version>2.2.2</version>
 </dependency>
 </dependencies>
 <dependency>
 <groupId>org.hibernate</groupId>
 <artifactId>hibernate-validator</artifactId>
 <version>4.3.0.Final</version>
 </dependency>

We need/want to override the spring dependencies pulled in via Spring Data with a newer one. We also need to explicitly remove spring-asm as newer versions of Spring don’t need it.

The neo4j-dependencies are required to give us the Neo4j-WebServer.

Repositories

I created a small demo-Entity based on the Neo4j-Annotation provided by Spring-Data.

 @NodeEntity
 public class Component {
    @GraphId Long id;
    @Indexed(unique = true) String simpleName;
    @RelatedTo(type = “Component”) Set<Component> relatedTo = new HashSet<Component>();
 …
 …
 }

Next I added a repository to handle the class (oh, the beauty of Spring-Data-Repositories).

public interface ComponentRepository extends GraphRepository<Component>{
}

That’s it.

Configuration

To get it all up and running the only thing required is @Configuration-Annotated class which extends Neo4jConfiguration.

@EnableTransactionManagement
@Configuration
@EnableNeo4jRepositories(basePackages = “de.codepitbull.neo4j”)
public class CustomNeo4jConfig extends Neo4jConfiguration {
    private static final String DB_PATH = "target/neo4j";
    @Bean
    public EmbeddedGraphDatabase graphDatabaseService() {
        return new EmbeddedGraphDatabase(DB_PATH);
    }
    @Bean
    public WrappingNeoServerBootstrapper neo4jWebServer() {
        WrappingNeoServerBootstrapper server = new WrappingNeoServerBootstrapper(graphDatabaseService());
        server.start();
        return server;
    }
}

Using this config we are getting not only an embedded Neo4J instance but also a nice query-interface (including the Neo4J-Shell) running at localhost:7474.
Nice, that’s it :)

Create a main-Class, fire up the context and see the magic happen.

public class Main {
     public static void main(String[] args) {
         AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CustomNeo4jConfig.class);
         context.start();
         context.getBean(WrappingNeoServerBootstrapper.class);
         context.registerShutdownHook();
     }
}

Using Converters

After playing around with Neo4J you will run into the need for using converters (everything that goes into the graph has to be converted to a String). This proved to be a little more tricky than I had expected.

Normally you only have to register a new ConversionServiceFactoryBean to provide additional back and forth conversions. Registering this been in the same @Configuration we just created will be followed by a “circular dependency”-problem.

I am not sure why this is happening but I simply moved it into a separate @Configuration _ which uses @Import_ to include the Neo4J-Configuration and things started working.

Obviously you will also have to replace CustomNeo4jConfig.class in your Main-class with MainConfiguration.class.

@Configuration
@Import(CustomNeo4jConfig.class)
public class MainConfiguration {

@Bean
public ConversionServiceFactoryBean conversionService() {
    Set converters = Sets.newHashSet();

    converters.add(new ConstructorListConverter());

    ConversionServiceFactoryBean bean = new ConversionServiceFactoryBean();
    bean.setConverters(converters);
    return bean;
}
}

Conclusion

I love it, nothing more to say.

Follow

Get every new post delivered to your Inbox.