code-small.jpg

Cake Team Blogs

Enabling Neo4j Web Admin Tool on the Embedded Server using Spring Data

Posted by Mark Harrison

23/05/12 15:20

As you've probably gathered from Jan's recent blog posts we're currently working on a project which makes use of Spring Data and Neo4j. For the time being we're running the embedded version of Neo4j, which by default doesn't have the Neo4j Management Tool enabled. This post will guide you through the hoops we navigated to get this enabled and configured using Spring.

The first thing to sort is the various Maven dependencies:

<properties>
    <spring.framework.version>3.1.1.RELEASE</spring.framework.version>
    <spring-data-neo4j.version>2.1.0.RC1</spring-data-neo4j.version>
    <spring-data-commons-core.version>1.3.0.RELEASE</spring-data-commons-core.version>
    <neo4j.version>1.7</neo4j.version>
</properties>

<repositories>
    <repository>
        <id>neo4j-release-repository</id>
        <name>Neo4j Maven 2 release repository</name>
        <url>http://m2.neo4j.org/releases</url>
        <releases>
            <enabled>true</enabled>
        </releases>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>core</artifactId>
        <version>${spring.framework.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>beans</artifactId>
        <version>${spring.framework.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>context</artifactId>
        <version>${spring.framework.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>aop</artifactId>
        <version>${spring.framework.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>data-neo4j</artifactId>
        <version>${data-neo4j.version}</version>
        <exclusions>
            <exclusion>
                <artifactId>slf4j-log4j12</artifactId>
                <groupId>org.slf4j</groupId>
            </exclusion>
        </exclusions>
    </dependency>

    <dependency>
        <groupId>org.neo4j.app</groupId>
        <artifactId>neo4j-server</artifactId>
        <version>${neo4j.version}</version>
        <exclusions>
            <exclusion>
                <artifactId>slf4j-jdk14</artifactId>
                <groupId>org.slf4j</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.neo4j.app</groupId>
        <artifactId>neo4j-server</artifactId>
        <version>${neo4j.version}</version>
        <classifier>static-web</classifier>
        <exclusions>
            <exclusion>
                <artifactId>slf4j-jdk14</artifactId>
                <groupId>org.slf4j</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>data-commons-core</artifactId>
        <version>${data-commons-core.version}</version>
    </dependency>
</dependencies>

This was more problematic than we expected, we encountered various issues caused by duplicate SLF4J bindings on the classpath (hence the exclusions in the dependencies).

LifecycleException: Failed to transition org.neo4j.kernel.logging.LogbackService from NONE to STOPPED

...

java.lang.ClassCastException: org.slf4j.impl.Log4jLoggerFactory cannot be cast to ch.qos.logback.classic.LoggerContext

...

This error was caused by the following line (67) in org.neo4j.kernel.logging.LogbackService which doesn't take into consideration the possibility of different loggers being bound.

LoggerContext loggerContext = (LoggerContext) StaticLoggerBinder.getSingleton().getLoggerFactory()

Configuring the module-context.xml

Now that's out of the way we can move onto the interesting stuff...

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:neo4j="http://www.springframework.org/schema/data/neo4j"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/data/neo4j http://www.springframework.org/schema/data/neo4j/spring-neo4j-2.0.xsd">

    <context:spring-configured/>
    <context:annotation-config/>

    <!--If we're not bothered about the management console we can just use this -->
    <!--<neo4j:config storeDirectory="target/neo4j-db"/>-->

    <!-- This additional config lets us use the Neo4j admin console -->
    <bean id="graphDatabaseService" class="org.neo4j.kernel.EmbeddedGraphDatabase">
        <constructor-arg value="target/neo4j-db"/>
    </bean>
    <neo4j:config graphDatabaseService="graphDatabaseService"/>
    <bean id="config" class="com.my.repository.Neo4jServerConfigurator">
        <constructor-arg ref="graphDatabaseService"/>
        <constructor-arg>
            <map>
                <entry key="enable_remote_shell" value="true"/>
            </map>
        </constructor-arg>
    </bean>
    <bean id="serverWrapper" class="org.neo4j.server.WrappingNeoServerBootstrapper" init-method="start"
          destroy-method="stop">
        <constructor-arg ref="graphDatabaseService"/>
        <constructor-arg ref="config"/>
    </bean>

    <neo4j:repositories base-package="com.my.repository"/>

</beans>

There are definitely few things here that need explaining. We're ultimately creating a WrappingNeoServerBootstrapper containing a custom configuration (to enable the Cypher shell) and the actual EmbeddedGraphDatabase. The tricky bit comes from the custom configuration. WrappingNeoServerBootstrapper accepts an instance of Configurator via its constructor, however the default implementation of this (EmbeddedServerConfigurator) isn't very Spring friendly. To get around this we implemented a quick wrapper for it, Neo4jServerConfigurator which accepts a Map and also exposes getters.

public class Neo4jServerConfigurator implements Configurator {
	private final EmbeddedServerConfigurator configurator;

	public Neo4jServerConfigurator(GraphDatabaseAPI db, Map<String, Object> initialProperties) {
		configurator = new EmbeddedServerConfigurator(db);
		for (Map.Entry<String, Object> entry : initialProperties.entrySet()) {
			configurator.configuration().addProperty(entry.getKey(), entry.getValue());
		}
	}

	public Object getProperty(String property) {
		return configurator.configuration().getProperty(property);
	}

	public void setProperty(String property, Object value) {
		configurator.configuration().setProperty(property, value);
	}

	public void addProperty(String property, Object value) {
		configurator.configuration().addProperty(property, value);
	}

	@Override
	public Configuration configuration() {
		return configurator.configuration();
	}

	@Override
	public Map<String, String> getDatabaseTuningProperties() {
		return configurator.getDatabaseTuningProperties();
	}

	@Override
	public Set<ThirdPartyJaxRsPackage> getThirdpartyJaxRsClasses() {
		return configurator.getThirdpartyJaxRsClasses();
	}
}

That's it! Load the context as normal and your Neo4j admin console should be available at http://127.0.0.1:7474/.

Any questions or feedback please comment, fire me an email or tweet @markglh.

Topics: spring

Subscribe to Email Updates