Monday, February 27, 2012

Scala Source Code and Density

I love Scala. It enables me to write concise / dense and clear code at the same time.

Take for example the piece of code below.
Had I written it in Java, it would be much longer, while at the same time less dense, and with much less clarity of what it is doing.

val site = config.sites.find(_.name == siteName).getOrElse(
    throw new IllegalArgumentException("Cannot find site " + siteName))
val group = config.groups.find(_.name == groupName).getOrElse(
    throw new IllegalArgumentException("Cannot find group " + groupName))
group.repositories.foreach { repo =>
  val repoDir = new File(group.path, repo.name)
  println("Backing up to repository %s at %s starting..." format (repo.name, repoDir))
  if (repoDir.mkdir())
    println("Created directory %s" format repoDir)
  repo.facets.foreach { facet =>
    println("Exporting facet %s..." format facet.name)
    val source = site.databases.find(_.name == facet.source).getOrElse(
        throw new IllegalArgumentException(
          "Facet %s references non-existing database %s" format (facet.name, facet.source)))
    println("Source database is %s:%s at %s" format (source.kind, source.name, source.url))
    (source.kind, facet) match {
      case ("neo4j", Facet(name, sourceName, "node", typeName, "json")) =>
        val outFileName = new File(name + ".json")
        println("Exporting %s nodes to %s" format (typeName, outFileName))
        val dbDir = new File(new URI(source.url))
        println("Neo4j database directory: %s" format dbDir)
        val graphDb = new EmbeddedReadOnlyGraphDatabase(dbDir.getPath)
        val (rowMeta, rows) = try {
          val exporter = new Neo4jNodeExporter(graphDb, typeName)
          println("Fetching meta...")
          val rowMeta = exporter.fetchMeta()
          println("Columns are: " + rowMeta.columns.mkString(", "))
          val rows = exporter.export(rowMeta)
          (rowMeta, rows)
        } finally {
          graphDb.shutdown()
        }
        
        println("Reading rows...")
        val mapper = new ObjectMapper
        mapper.getSerializationConfig.set(Feature.INDENT_OUTPUT, true)
        val jsonRows = rows.map { row =>
          val obj = mapper.createObjectNode()
          for ((value, i) <- row.values.view.zipWithIndex) {
            if (value != null)
              obj.put(rowMeta.columns(i), value)
          }
          obj
        }
        val jsonArray = mapper.createArrayNode().addAll(jsonRows.toList)
        val jsonData = mapper.createObjectNode()
        jsonData.put("data", jsonArray)
        val outFile = new File(repoDir, outFileName.toString)
        
        print("Writing %s..." format outFile)
        mapper.writeValue(outFile, jsonData)
        println(" [OK]")
        
      case x: Any => println("Skipping unrecognized facet: " + x)
    }
  }
  println("Backing up to repository %s finished." format repo.name)
}

Scala is also fantastic at storing ad-hoc object graph / trees, check a look a this :

  sites = List(
    Site(name = "dev", databases = List(
      Database(name = "graph", kind = "neo4j", url = "file:///together/project/SatukanCinta/satukancinta-neo4j-db_dev_1.6/"))),
    Site(name = "test",
      databases = List(Database(name = "graph", kind = "neo4j", url = "file:///together/project/SatukanCinta/dumptest/graph")))),
  groups = List(Group(name = "main", path = "/together/project/SatukanCinta/dump_main",
    repositories = List(
      Repository(name = "like", kind = "*", facets = List(
        Facet(name = "user", source = "graph", primitive = "node", typeName = "com.satukancinta.domain.User", format = "json"),
        Facet(name = "topic", source = "graph", primitive = "node", typeName = "com.satukancinta.domain.Interest", format = "json"),
        Facet(name = "like", source = "graph", primitive = "relationship", typeName = "LIKE", format = "graphml"))))))) {

Beat that, Java!

Still don't believe me? How about processing a bunch of collections, and sprinkle built-in parallel capability :

    val indexHits = graphDb.getAllNodes.par.filter(_.getProperty("__type__") == typeName)
    log.info("Index for {} returned {} nodes", typeName, indexHits.size)
    val columnNames = indexHits.par.flatMap( node =>
      node.getPropertyKeys.filter( _ != "__type__" ) ).toSet
    val sortedColumns = columnNames.toList.sorted
    log.info("Columns for {}: {}", typeName, sortedColumns.mkString(", "))
    RowMeta(columns = sortedColumns)

I really can't imagine doing that (including the concurrency) in Java. Phew.

Verbose code == easy to read ? Not always. This one is much easier on the eyes.

Tip: To learn more about Scala programming, I recommend Programming in Scala: A Comprehensive Step-by-Step Guide, 2nd Edition.

Wednesday, February 15, 2012

UnproxyableResolutionException Workaround when using Scala Closures and javax.inject CDI Beans Together

Some powerful Scala programming language features like closures, pattern matching, and lazy vals don't work well with dependency injection frameworks like javax.inject aka CDI. This is due to stricter class structure requirements to enable proxying.

For example, this "innocent" code will not work:

  @Inject private var fbPhotoImporterFactory: Instance[FacebookPhotoImporter] = _
  @Produces @Named("facebookPhotoImporter") private var fbPhotoImporter: ActorRef = _

  @PostConstruct
  def init() {
    logger.debug("Starting FacebookPhotoImporter actor")
    fbPhotoImporter = Actor.actorOf(fbPhotoImporterFactory.get())
    fbPhotoImporter.start()
  }

It will throw:

Caused by: org.jboss.weld.exceptions.UnproxyableResolutionException: WELD-001437 Normal scoped bean class com.satukancinta.web.Persistence is not proxyable because the type is final or it contains a final method public final javax.enterprise.inject.Instance com.satukancinta.web.Persistence.com$satukancinta$web$Persistence$$fbPhotoImporterFactory() - Managed Bean [class com.satukancinta.web.Persistence] with qualifiers [@Any @Default].
    at org.jboss.weld.util.Proxies.getUnproxyableClassException(Proxies.java:225)
    at org.jboss.weld.util.Proxies.getUnproxyableTypeException(Proxies.java:178)
    at org.jboss.weld.util.Proxies.getUnproxyableTypesExceptionInt(Proxies.java:193)
    at org.jboss.weld.util.Proxies.getUnproxyableTypesException(Proxies.java:167)
    at org.jboss.weld.bootstrap.Validator.validateBean(Validator.java:110)
    at org.jboss.weld.bootstrap.Validator.validateRIBean(Validator.java:126)
    at org.jboss.weld.bootstrap.Validator.validateBeans(Validator.java:345)
    at org.jboss.weld.bootstrap.Validator.validateDeployment(Validator.java:330)
    at org.jboss.weld.bootstrap.WeldBootstrap.validateBeans(WeldBootstrap.java:366)
    at org.jboss.as.weld.WeldContainer.start(WeldContainer.java:82)
    at org.jboss.as.weld.services.WeldService.start(WeldService.java:89)
    ... 5 more

As you can see, my use code isn't exactly "edge cases".

It's actually a pretty common use case: create a Akka actor and pass a factory function to it, as a closure.
The code above doesn't look like it's using a closure, but it actually is when written like this: (same functionality, but still breaks CDI)

    fbPhotoImporter = Actor.actorOf { fbPhotoImporterFactory.get() }

I can see why CDI has a strict requirement, and I can also understand why Scala implements it the way it is (Scala developers definitely already has a lot of problems working around powerful Scala features into a very restrictive JVM bytecode requirements). This is the price we pay for having a somewhat inferior language (Java, please don't get offended) in the first place.

But I as an application developer want to have a quick fix for this issue. Re-coding the class in plain Java is one option, but it turns I don't need to. There is a workaround, by creating a helper method then using it:

  @Inject private var fbPhotoImporterFactory: Instance[FacebookPhotoImporter] = _
  @Produces @Named("facebookPhotoImporter") private var fbPhotoImporter: ActorRef = _
 
  def createFbPhotoImporter() = fbPhotoImporterFactory.get()
 
  @PostConstruct
  def init() {
    logger.debug("Starting FacebookPhotoImporter actor")
    fbPhotoImporter = Actor.actorOf(createFbPhotoImporter)
    fbPhotoImporter.start()
  }

Now Scala is happy and CDI is also happy. Yes it's a bit more verbose but not too bad. And I guess the code is now somewhat more understandable for Java guys. :)

Tip: To learn more about Scala programming, I recommend Programming in Scala: A Comprehensive Step-by-Step Guide, 2nd Edition.

Sunday, January 22, 2012

Scala "Bug" with CDI Dependency Injection

“Sometimes”, Scala creates public final methods, although the .scala source defines
no public final method at all.
There are two things that CDI doesn’t like (which Scala “sometimes” generates):
1. public final methods
2. public fields
To investigate and reproduce these problems I created a scala-cdi project at GitHub.

public final method: The Bug

Referencing a parent field from a closure / inner class triggers this behavior:
@RequestScoped @Named class IndexBean { private lazy val log = LoggerFactory.getLogger(classOf[IndexBean]) def testExecutor() = { val executor = Executors.newFixedThreadPool(4); executor.submit(new Runnable() { override def run(): Unit = log.debug("Executor is running") }) } }
Compiles to:
$ javap -p IndexBean Compiled from "IndexBean.scala" public class com.soluvas.scalacdi.IndexBean extends java.lang.Object implements scala.ScalaObject{ private org.slf4j.Logger com$soluvas$scalacdi$IndexBean$$log; ... public final org.slf4j.Logger com$soluvas$scalacdi$IndexBean$$log();
Deploying this app in Weld will throw:
org.jboss.weld.exceptions.UnproxyableResolutionException: WELD-001437 Normal scoped bean class com.soluvas.scalacdi.IndexBean is not proxyable because the type is final or it contains a final method public final org.slf4j.Logger com.soluvas.scalacdi.IndexBean.com$soluvas$scalacdi$IndexBean$$log() - Managed Bean [class com.soluvas.scalacdi.IndexBean] with qualifiers [@Any @Default @Named].         at org.jboss.weld.util.Proxies.getUnproxyableClassException(Proxies.java:225)         at org.jboss.weld.util.Proxies.getUnproxyableTypeException(Proxies.java:178)         at org.jboss.weld.util.Proxies.getUnproxyableTypesExceptionInt(Proxies.java:193)         at org.jboss.weld.util.Proxies.getUnproxyableTypesException(Proxies.java:167)         at org.jboss.weld.bootstrap.Validator.validateBean(Validator.java:110)

public final method: Workaround

Create a final local variable to hold the parent instance’s value:
def testExecutor() = { val executor = Executors.newFixedThreadPool(4); // this avoids 'log' becoming 'final' like: // private org.slf4j.Logger com$soluvas$scalacdi$IndexBean$$log; // public final org.slf4j.Logger com$soluvas$scalacdi$IndexBean$$log(); val log = this.log; executor.submit(new Runnable() { override def run(): Unit = log.debug("Executor is running") }) }
Which now compiles to:
$ javap -p IndexBean Compiled from "IndexBean.scala" public class com.soluvas.scalacdi.IndexBean extends java.lang.Object implements scala.ScalaObject{ private org.slf4j.Logger log; private org.slf4j.Logger log();

public field

I’m not able to reproduce this yet...
Tip: To learn more about Scala programming, I recommend Programming in Scala: A Comprehensive Step-by-Step Guide, 2nd Edition.

Sunday, January 1, 2012

Implementing RichFaces ExtendedDataModel for JSF Paging with Spring Data Neo4j and Scala

Satukancinta-datatable

JSF 2.1 and JBoss RichFaces 4.1.0 make it easy to do server-side paging and sorting, which improves performance of Java EE web applications significantly compared to returning list of all rows from the database and filtering it later in the application.

By implementing ExtendedDataModel, the data model can be used directly by rich:dataTable and rich:dataScroller JSF components.

ExtendedDataModel for Spring Data Neo4j Finder/Query Methods

To implement this on Neo4j graph database, by using Spring Data Neo4j finder methods we can implement ExtendedDataModel like below: (in Scala programming language)

package com.satukancinta.web

import collection.JavaConversions._
import org.ajax4jsf.model.ExtendedDataModel
import javax.faces.context.FacesContext
import org.springframework.data.domain.Page
import org.ajax4jsf.model.DataVisitor
import org.springframework.data.neo4j.repository.GraphRepository
import org.springframework.data.domain.PageRequest
import org.springframework.data.neo4j.aspects.core.NodeBacked
import org.ajax4jsf.model.Range
import org.ajax4jsf.model.SequenceRange
import org.slf4j.LoggerFactory
import org.springframework.data.domain.Sort
import org.springframework.data.domain.Sort.Direction
import org.springframework.data.domain.Pageable

abstract class FinderModel[E]() extends ExtendedDataModel[E] {
  private lazy val log = LoggerFactory.getLogger(classOf[FinderModel[E]])
 
  private lazy val rowCount: Int = {
    val result= getRowCountLazy
    log.debug("Total rows: {}", result)
    result
  }
 
  private var rowIndex: Int = _
  private var page: Page[E] = _
  private var pageData: List[E] = _
  private var lastRange: (Int, Int) = _

  log.trace("Created {}", this.getClass)
 
  def getRowCountLazy: Int
  def find(pageable: Pageable): Page[E]

  def setRowKey(key: Object): Unit = setRowIndex(key.asInstanceOf[Int])
  def getRowKey: Object = getRowIndex: java.lang.Integer
 
  private def loadData(range: Range): Unit = {
    val seqRange = range.asInstanceOf[SequenceRange]
    val curRange = (seqRange.getFirstRow, seqRange.getRows)
    if (lastRange == curRange) {
      log.debug("loadData returning cached")
      return
    }
    lastRange = curRange
   
    val pageNum = seqRange.getFirstRow / seqRange.getRows
    // ORDER BY name is painfully slow: https://groups.google.com/group/neo4j/t/f2219df41f5500a9
    val pageReq = new PageRequest(pageNum, seqRange.getRows/*, Direction.ASC, "y.name"*/)
    log.debug("loadData({}, {}) -> PageRequest({}, {})",
        Array[Object](seqRange.getFirstRow: java.lang.Long, seqRange.getRows: java.lang.Long,
            pageNum: java.lang.Long, seqRange.getRows: java.lang.Long))
    val startTime = System.currentTimeMillis
    page = find(pageReq)
    val findTime = System.currentTimeMillis - startTime
    log.debug("Page has {} rows of {} total in {} pages, took {}ms",
        Array[Object](page.getSize: java.lang.Long, page.getTotalElements: java.lang.Long,
            page.getTotalPages: java.lang.Long, findTime: java.lang.Long))
    pageData = page.toList
//    val pageIds = pageData.map( _.asInstanceOf[NodeBacked].getNodeId )
//    log.debug("Node IDs: {}", pageIds);
  }

  def walk(context: FacesContext, visitor: DataVisitor, range: Range, argument: Object): Unit = {
    loadData(range)
    for (val index <- 0 to pageData.size - 1) {
      visitor.process(context, index, argument)
    }
  }

  def isRowAvailable: Boolean = rowIndex < pageData.length

  def getRowCount: Int = rowCount

  def getRowData: E = {
    val result = pageData(rowIndex) // repository.findOne(rowKey.asInstanceOf[Long])
    val node = result.asInstanceOf[NodeBacked]
    log.trace("getRowData({}) = #{}: {}",
        Array[Object](rowIndex: java.lang.Long, node.getNodeId: java.lang.Long, node))
    result
  }

  def getRowIndex: Int = rowIndex
  def setRowIndex(index: Int): Unit = rowIndex = index

  def getWrappedData: Object = { null }
  def setWrappedData(wrappedData: Object): Unit = { /* dummy */ }

}

To use the FinderModel, it's much easier if we create a repository first and add some finder/query methods returning count and Page:

public interface InterestRepository extends GraphRepository<Interest> {

    @Query("START u=node({userId}) MATCH u-[:LIKE]->y RETURN COUNT(y)")
    public Long findUserLikeCount(@Param("userId") long userId);

    // ORDER BY name is still slow: https://groups.google.com/group/neo4j/t/f2219df41f5500a9
//    @Query("START u=node({userId}) MATCH u-[:LIKE]->y RETURN y ORDER BY y.name")
    @Query("START u=node({userId}) MATCH u-[:LIKE]->y RETURN y")
    public Page<Interest> findUserLikes(@Param("userId") long userId, Pageable pageable);

}

How to create a FinderModel instance from Java :

    @Inject InterestRepository interestRepo;
    private FinderModel<Interest> userLikesModel;

    @PostConstruct public void init() {
        userLikesModel = new FinderModel<Interest>() {

            @Override
            public int getRowCountLazy() {
                return interestRepo.findUserLikeCount(user.getNodeId()).intValue();
            }

            @Override
            public Page<Interest> find(Pageable pageable) {
                return interestRepo.findUserLikes(user.getNodeId(), pageable);
            }
        };
    }

And how to use this data model from a JSF Template .xhtml file:

<rich:dataTable id="interestTable" var="interest" value="#{userLikes.userLikesModel}" rows="20">
    <rich:column>
        <f:facet name="header">Name</f:facet>
        <h:link outcome="/interests/show?id=#{interest.nodeId}" value="#{interest.name}"/>
    </rich:column>
    <f:facet name="footer"><rich:dataScroller/></f:facet>
</rich:dataTable>

Quite practical, isn't it?


ExtendedDataModel for Spring Data Neo4j Repository

FinderModel can then be further subclassed to handle any Spring Data Neo4j repository:

class GraphRepositoryModel[E]() extends FinderModel[E] {
  private var repository: GraphRepository[E] = _
 
  def getRowCountLazy: Int = repository.count.toInt
  def find(pageable: Pageable): Page[E] = repository.findAll(pageable)

  override def getWrappedData: Object = repository
  override def setWrappedData(wrappedData: Object): Unit =
    repository = wrappedData.asInstanceOf[GraphRepository[E]]

}

And use it like this:

@Inject InterestRepository interestRepo;
private GraphRepositoryModel<Interest> interestModel;
   
@PostConstruct public void init() {
    interestModel = new GraphRepositoryModel<Interest>();
    interestModel.setWrappedData(interestRepo);
}

Hope this helps.

To learn more about Scala programming, I recommend Programming in Scala: A Comprehensive Step-by-Step Guide, 2nd Edition.

Saturday, December 31, 2011

Scala closures vs Guava collection functions

Let me show you the same method using list transformations (aka "map" in functional programming speak), one in Java programming language:

    @GET @Path("user/{userId}/likes") @Produces(MediaType.APPLICATION_JSON)
    public Payload<Iterable<Map<String, Object>>> getUserLikes(@PathParam("userId") long userId) {
        User user = neo4j.findOne(userId, User.class);
        Iterable<Interest> likeInterests = user.getLikeInterests();
        Iterable<Map<String, Object>> likes = Iterables.transform(likeInterests, new Function<Interest, Map<String, Object>>() {
            @Override
            public Map<String, Object> apply(Interest interest) {
                HashMap<String, Object> row = new HashMap<String, Object>();
                row.put("id", interest.getNodeId());
                row.put("name", interest.getName());
                return  row;
            }
        });
        return new Payload<Iterable<Map<String, Object>>>(likes);
    }


And one in Scala programming language :

    @GET @Path("user/{userId}/likes") @Produces(Array(MediaType.APPLICATION_JSON))
    def getUserLikes(@PathParam("userId") userId: Long): Payload[Iterable[Map[String, Object]]] = {
        val user = neo4j.findOne(userId, classOf[User])
        val likeInterests = user.getLikeInterests
        val likes = likeInterests.map(interest =>
          Map("id"->interest.getNodeId, "name"->interest.getName) )
        new Payload(likes)
    }

Is Scala hard to read? I'll leave it to you to decide. :-)

To learn more about Scala programming, I recommend Programming in Scala: A Comprehensive Step-by-Step Guide, 2nd Edition.

Coding JSON REST JAX-RS Service Application to Access Neo4j Database in Scala

Spring Data Neo4j makes it easy to accessing graph data in Neo4j graph database.

After separating the AspectJ-enhanced Spring Data Neo4j node entities, relationship entities, and repositories, it's very fun to access the entities from a Scala web application. In this case, I'll show you a JAX-RS application written in Scala programming language, consuming and producing JSON in REST-style. The application exposes Spring Data Neo4j through HTTP, which can be accessed via curl or any other web client.

Java programming language version:

@Path("node") @Stateless
public class NodeResource {

    private transient Logger logger = LoggerFactory.getLogger(NodeResource.class);
    @Inject Neo4jTemplate neo4j;
    @Inject InterestRepository interestRepo;
   
    @GET @Path("interest") @Produces(MediaType.APPLICATION_JSON)
    public Iterable<Interest> interest() {
        ClosableIterable<Interest> records = neo4j.findAll(Interest.class);
        return records;
    }

    @GET @Path("user") @Produces(MediaType.APPLICATION_JSON)
    public Payload<User> getUser() {
        return new Payload<User>(neo4j.findOne(5L, User.class));
    }

    @POST @Path("interest")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response createInterest(Payload<Interest> payload) {
        logger.debug("createInterest {}", payload.data);
        Interest interest = payload.data;
        interest.persist();
        return Response.created(URI.create(String.format("interest/%d", interest.getNodeId())))
                .entity(new Payload<Interest>(interest)).build();
    }

    @DELETE @Path("interest/{id}")
    public Response deleteInterest(@PathParam("id") long id) {
        try {
            Interest node = neo4j.findOne(id,  Interest.class);
            node.remove();
            return Response.ok("deleted").build();
        } catch (DataRetrievalFailureException e) {
            return Response.status(Status.NOT_FOUND).entity(e.getMessage()).build();
        }
    }

    @GET @Path("interest/by/facebookId/{facebookId}")
    @Produces(MediaType.APPLICATION_JSON)
    public Payload<Interest> findInterestByFacebookId(@PathParam("facebookId") long facebookId) {
        logger.debug("findInterestByFacebookId {}", facebookId);
        Interest interest = interestRepo.findByFacebookId(facebookId);
        if (interest == null)
            throw new WebApplicationException(Response.status(Status.NOT_FOUND).entity("Interest with facebookId="+ facebookId +" not found").build());
        return new Payload<Interest>(interest);
    }

}


Scala programming language version:

@Path("node") @Stateless
class NodeResource {

    private lazy val logger = LoggerFactory.getLogger(classOf[NodeResource])
    @Inject var neo4j: Neo4jTemplate = _
    @Inject var interestRepo: InterestRepository = _
    
    @GET @Path("interest") @Produces(Array(MediaType.APPLICATION_JSON))
    def interest: Iterable[Interest] = neo4j.findAll(classOf[Interest])

    @GET @Path("user") @Produces(Array(MediaType.APPLICATION_JSON))
    def getUser: Payload[User] = new Payload[User](neo4j.findOne(5L, classOf[User]))

    @POST @Path("interest")
    @Consumes(Array(MediaType.APPLICATION_JSON))
    @Produces(Array(MediaType.APPLICATION_JSON))
    def createInterest(payload: Payload[Interest]): Response = {
        logger.debug("createInterest {}", payload.data)
        val interest = payload.data
        interest.persist
        Response.created(URI.create(String.format("interest/%d", interest.getNodeId)))
                .entity(new Payload[Interest](interest)).build
    }

    @DELETE @Path("interest/{id}")
    def deleteInterest(@PathParam("id") id: Long): Response = {
        try {
            val node = neo4j.findOne(id, classOf[Interest])
            node.remove
            Response.ok("deleted").build
        } catch {
          case e: DataRetrievalFailureException =>
            Response.status(Status.NOT_FOUND).entity(e.getMessage).build
        }
    }

    @GET @Path("interest/by/facebookId/{facebookId}")
    @Produces(Array(MediaType.APPLICATION_JSON))
    def findInterestByFacebookId(@PathParam("facebookId") facebookId: Long): Payload[Interest] = {
        logger.debug("findInterestByFacebookId {}", facebookId)
        val interest = interestRepo.findByFacebookId(facebookId)
        if (interest == null)
            throw new WebApplicationException(Response.status(Status.NOT_FOUND).entity("Interest with facebookId="+ facebookId +" not found").build
        new Payload[Interest](interest)
    }

}


Apart from less code and cruft, Scala code is not much different in structure.

If there are list processing functions or closures, then Scala code will read much easier, while the Java code will use Guava library and clunky syntax (at least until Java 8 arrives).

To learn more about Scala programming, I recommend Programming in Scala: A Comprehensive Step-by-Step Guide, 2nd Edition.

Graph Analysis with Scala and Spring Data Neo4j

Neo4j graph database is the an awesome technology for graph data persistence. With Spring Data Neo4j accessing graph data becomes even easier.

The easiest way to use Spring Data Neo4j is by enabling its AspectJ weaving. Because I wanted to use both Scala and Spring Data Neo4j in my web application, using both languages (AspectJ and Scala) in the same project isn't possible for now. A tip for you, separate the AspectJ-enhanced classes (@NodeEntity, @RelationshipEntity, and repositories) into a separate AspectJ project, then depend on the AspectJ from the Scala application.

The result is I'm able to fully utilize the versatile Scala and the excellent Spring Data libraries with Neo4j.

Now for mandatory code comparison between Java and Scala :-)

Java programming language version:

package com.satukancinta.web;

import java.util.List;
import java.util.Map;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.inject.Named;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.neo4j.conversion.Result;
import org.springframework.data.neo4j.support.Neo4jTemplate;

import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.satukancinta.domain.User;

/**
 * @author ceefour
 * Various analysis of the social graph.
 */
@Named @ApplicationScoped
public class GraphAnalysis {
   
    private transient Logger logger = LoggerFactory.getLogger(GraphAnalysis.class);
    @Inject Neo4jTemplate neo4j;
   
    public List<MutualLikeRow> getMutualLikesLookup(User user) {
        logger.debug("getMutualLikesLookup {}/{}", user.getNodeId(), user);
        Result<Map<String, Object>> rows = neo4j.query("START x=node("+ user.getNodeId() +") MATCH x-[:LIKE]->i<-[:LIKE]-y RETURN id(y) AS id, y.name AS name, COUNT(*) AS mutualLikeCount", null);
        Iterable<MutualLikeRow> result = Iterables.transform(rows, new Function<Map<String, Object>, MutualLikeRow>() {
            @Override
            public MutualLikeRow apply(Map<String, Object> arg) {
                return new MutualLikeRow((Long)arg.get("id"), (Integer)arg.get("mutualLikeCount"),
                        (String)arg.get("name"));
            }
        });
        return Lists.newArrayList(result);
    }

}

Scala programming language version:

package com.satukancinta.web
import collection.JavaConversions._
import org.slf4j._
import javax.inject.Inject
import org.springframework.data.neo4j.support.Neo4jTemplate
import com.satukancinta.domain.User
import javax.enterprise.context.ApplicationScoped
import javax.inject.Named

/**
 * @author ceefour
 * Analysis functions of friend network graph.
 */
@Named @ApplicationScoped
class GraphAnalysis {

  private lazy val logger = LoggerFactory.getLogger(classOf[GraphAnalysis])
  @Inject private var neo4j: Neo4jTemplate = _
 
  def getMutualLikesLookup(user: User): java.util.List[MutualLikeRow] = {
    logger.debug("getMutualLikesLookup {}/{}", user.getNodeId, user)
    val rows = neo4j.query(
        "START x=node("+ user.getNodeId() +") MATCH x-[:LIKE]->i<-[:LIKE]-y RETURN id(y) AS id, y.name AS name, COUNT(*) AS mutualLikeCount", null)
    val result = rows.map( r =>
          new MutualLikeRow(r("id").asInstanceOf[Long],
            r("mutualLikeCount").asInstanceOf[Integer].longValue,
            r("name").asInstanceOf[String]) )
        .toList
    result.sortBy(-_.mutualLikeCount)
  }
 
}


As you can see, the Scala version is not only much more concise, easier to understand, but actually has added functionality (sorted using .sortBy) with less code. Thanks to collection functions and closure support.

To learn more about Scala programming, I recommend Programming in Scala: A Comprehensive Step-by-Step Guide, 2nd Edition.