These are my best photos yet, from incredible India.
2012-12-30
2012-11-03
Spring Data MongoDB on Cloud Foundry tutorial Part 1
Cloud Foundry allows you to run many instances of your application, add and remove services, when needed, and so on. You may want to read some info on getting started.
Register on Cloud Foundry
The first step is to register on Cloud Foundry. You will either manage your cloud app using a command line VMC tool, or using Cloud Foundry extension for Spring Tool Suite/Eclipse. I chose to use the IDE independent VMC.The Spring Data MongoDB app
The Spring app I am using is available to download from my GitHub (specifically, the version for this article is tagged as BlogPost1 and you can also get it from the tags view). Below I will show you some of the steps you need to make to get your Spring MongoDB app up and running - locally for now. More information is available from the Cloud Foundry Spring Application Development page.Things to remember:
- Cloud Foundry technology support is limited, so you have to figure out beforehand whether your stack will even work.
- At this moment, Java 7 is not supported on Cloud Foundry, so you need to downgrade your project to Java 6.
- Similarly, only certain versions of Spring and Spring Data MongoDB are supported.
- Only Tomcat 6 is supported at the moment. Locally I'm developing against Tomcat 7, but due to Cloud Foundry limitations it has to work on Tomcat 6 as well (no problems with that).
- Servlet Spec 3.0 is not supported - back to 2.5.
apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'war' apply plugin: 'eclipse-wtp' group = 'me.m1key.audiolicious' version = '0.0.1-SNAPSHOT' sourceCompatibility = 1.6 targetCompatibility = 1.6 repositories { mavenLocal() mavenCentral() mavenRepo name: "spring-test-mvc", url: 'http://repo.springsource.org/libs-milestone/' mavenRepo url:'http://maven.springframework.org/milestone/' } project.ext.finalName = 'audiolicious-cloud.war' war { archiveName = finalName } task deployment(type: Copy) { from('build/libs/' + finalName) into("$System.env.TOMCAT_HOME/webapps/") } project.ext.springVersion = '3.1.2.RELEASE' dependencies { compile group: 'org.springframework', name: 'spring-beans', version: springVersion, force: true compile group: 'org.springframework', name: 'spring-context', version: springVersion, force: true compile group: 'org.springframework', name: 'spring-core', version: springVersion, force: true compile group: 'org.springframework', name: 'spring-webmvc', version: springVersion, force: true compile group: 'org.springframework.data', name: 'spring-data-mongodb', version: '1.0.4.RELEASE' compile group: 'org.mongodb', name: 'mongo-java-driver', version: '2.9.1' compile group: 'org.slf4j', name: 'slf4j-api', version: '1.6.6' compile group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.6.6' compile group: 'org.apache.velocity', name: 'velocity', version: '1.7' compile group: 'org.cloudfoundry', name: 'cloudfoundry-runtime', version: '0.8.2' testCompile group: 'org.springframework', name: 'spring-test', version: springVersion testCompile group: 'junit', name: 'junit', version: '4.+' testCompile 'org.hamcrest:hamcrest-all:1.3' testCompile 'javax.servlet:servlet-api:2.5' testCompile 'org.springframework:spring-test-mvc:1.0.0.M2' } configurations.all { resolutionStrategy { force group: 'org.springframework', name: 'spring-aop', version: springVersion force group: 'org.springframework', name: 'spring-expression', version: springVersion force group: 'org.springframework', name: 'spring-tx', version: springVersion } }Meanwhile, in your src/main/webapp/WEB-INF/web.xml...
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="AudioliciousCloud" version="2.5" metadata-complete="true"> <display-name>Spring MVC tutorial</display-name> <servlet> <servlet-name>audiolicious</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:audiolicious-servlet.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>audiolicious</servlet-name> <url-pattern>*.go</url-pattern> </servlet-mapping> </web-app>And the Spring servlet file (src/main/resources/audiolicious-servlet.xml).
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:mongo="http://www.springframework.org/schema/data/mongo" 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-3.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd"> <!-- Enabling Spring beans auto-discovery --> <context:component-scan base-package="me.m1key.audiolicious.cloud" /> <!-- Enabling Spring MVC configuration through annotations --> <mvc:annotation-driven /> <bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> <property name="resourceLoaderPath"> <value>/</value> </property> </bean> <!-- Defining which view resolver to use --> <bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver"> <property name="prefix"> <value>/velocity/</value> </property> <property name="suffix"> <value>.vm</value> </property> </bean> <mongo:db-factory id="mongoDbFactory" dbname="adlcs_cloud" host="127.0.0.1" port="27017" /> <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate"> <constructor-arg name="mongoDbFactory" ref="mongoDbFactory" /> </bean> <mongo:repositories base-package="me.m1key.audiolicious.cloud.repositories" /> </beans>What's important here is MongoDB configuration. If you configure it this way, it will allow Cloud Foundry to inject its own values for host name and port, so that it also works on the cloud.
MongoDB repository
One of the nice features of Spring Data MongoDB is that you can (just like in Grails) ask it to create simple repositories for you - you just define interfaces, Spring will provide the implementation.package me.m1key.audiolicious.cloud.repositories; import me.m1key.audiolicious.cloud.entities.Song; import org.springframework.data.repository.CrudRepository; public interface SongRepository extends CrudRepository<Song, Long> { }This gives me a CRUD repo that I don't have to implement myself. You can add methods to this interface, such as findByLastName, and Spring will know how to implement them. For more information on this, see the repositories reference. You can extend this behaviour by implementing your own, more sophisticated methods.
Entity. Notice no annotations:
package me.m1key.audiolicious.cloud.entities; public class Song { private String name; private String albumName; private String artistName; private String songKey; public Song(String name, String albumName, String artistName, String songKey) { super(); this.name = name; this.albumName = albumName; this.artistName = artistName; this.songKey = songKey; } public String getName() { return name; } // Omitted for brevity. }
In part 2 of this tutorial I will show you how to deploy this on Cloud Foundry.
PS. Tomcat debugging
This is the stuff I added in my catalina.sh (this is Linux syntax then) file to get remote debugging from my IDE to work.JAVA_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n $JAVA_OPTS" JPDA_TRANSPORT=dt_socket JPDA_ADDRESS=8000
2012-10-26
2012-09-30
CDI @Produces not working?
This does not work
@ApplicationScoped public class TrackMappersFactory { @EJB private AudiobookMapper audiobookMapper; @EJB private PodcastMapper podcastMapper; @EJB private SongMapper songMapper; @EJB private VideoMapper videoMapper; @Produces @AggregateMapper public Map<Class<? extends TrackTo>, TrackMapper<? extends TrackTo>> getAggregateTrackMapper() {The reason why is simple, this class is not detected by the container because it's not an EJB. The solution is to make it a @Singleton bean instead.
This works
@Singleton public class TrackMappersFactory { @EJB private AudiobookMapper audiobookMapper; @EJB private PodcastMapper podcastMapper; @EJB private SongMapper songMapper; @EJB private VideoMapper videoMapper; @Produces @AggregateMapper public Map<Class<? extends TrackTo>, TrackMapper<? extends TrackTo>> getAggregateTrackMapper() {Here is the diff for this change.
2012-09-29
Arquillian and @Asynchronous
Original situation
The bean.@Stateless @Asynchronous @Local(LibraryImporter.class) public class CacheAwareLibraryImporter implements LibraryImporter { // … omitted for brevity @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public FutureThe test.importLibrary(File libraryFile) { // code... } }
@RunWith(Arquillian.class) public class DefaultSongServiceMacOsIT { @EJB private LibraryService libraryService; // … omitted for brevityThis doesn't work with Arquillian.
Workaround
Here's my workaround. You may want to look at the diff file instead. The new bean (in src/test/java).@Stateless @Local({ LibraryImporter.class, SynchronousLibraryImporter.class }) public class SynchronousCacheAwareLibraryImporter extends CacheAwareLibraryImporter implements SynchronousLibraryImporter { @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public FutureThe new interface (in src/test/java).importLibrary(File libraryFile) { return super.importLibrary(libraryFile); } }
public interface SynchronousLibraryImporter extends LibraryImporter { }The test.
@RunWith(Arquillian.class) public class DefaultSongServiceMacOsIT { @EJB private SynchronousLibraryService libraryService; // … omitted for brevityNice...-ty. This is a known issue and they haven't done much about it so far.
Morphia and semicolons
db.MongoDbSong.group( {key: {artistName: 1, albumName: 1}, initial: {totalPlays: 0}, reduce: function(obj, prev) { for(var i=0; i < obj.statsList.length; i++) { if(obj.statsList[i].libraryUuid == 'bf25ff51-da26-4c76-82dc-ada0e4a68c18'){ prev.totalPlays += obj.statsList[i].playCount}}} })This code is valid and runs fine inside MongoDb console. Not so in Morphia, it returns a null result.
Workaround
I had to change my query into this, so essentially iterate over the array without using semicolons:db.MongoDbSong.group(" + {key: {artistName: 1, albumName: 1}, initial: {totalPlays: 0}, reduce: function(obj, prev) {for each (var item in obj.statsList) if(item.libraryUuid == 'bf25ff51-da26-4c76-82dc-ada0e4a68c18'){ prev.totalPlays += item.playCount}}" + "});It could be that I'm wrong. Anyway, this works and I have submitted this as a defect.
Search in Git
- search in git commits (the actual code committed), and
- search in commit messages.
Search in committed code
git log -S "whatever you are looking for"
Search in commit messages
git log --grep="whatever you are looking for"
2012-09-28
MongoDB with Morphia (and Scala)
build.gradle
Dependencies...// ... repositories { mavenLocal() mavenCentral() mavenRepo name: "EclipseLink", url: "http://download.eclipse.org/rt/eclipselink/maven.repo/" mavenRepo name: "MongoDB", url: "http://morphia.googlecode.com/svn/mavenrepo" mavenRepo name: "Audiolicious", url: "https://github.com/m1key/repo/raw/master/releases" } dependencies { compile group: 'org.eclipse.persistence', name: 'eclipselink', version: '2.3.0' compile group: 'org.eclipse.persistence', name: 'javax.persistence', version: '2.0.0' compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.18' compile group: 'org.mongodb', name: 'mongo-java-driver', version: '2.7.3' compile group: 'com.google.code.morphia', name: 'morphia', version: '0.99' // … } // ...
Persistence provider
This is how you expose Mongo to your repositories (or DAOs).package me.m1key.audioliciousmigration.persistence.mongodb import com.google.code.morphia.Datastore import com.mongodb.Mongo import com.google.code.morphia.Morphia import me.m1key.audioliciousmigration.entities.mongodb.MongoDbSong class ProductionMorphiaMongoDbPersistenceProvider extends MorphiaMongoDbPersistenceProvider { private var datastore: Datastore = null private var mongo: Mongo = null def initialise(): Unit = { mongo = new Mongo("localhost", 27017) datastore = new Morphia().map(classOf[MongoDbSong]).createDatastore(mongo, "audiolicious") datastore.ensureIndexes() } def getDatastore(): Datastore = { return datastore } def getMongo(): Mongo = { return mongo } }
Repository using getDatastore
This is a repository which uses the MongoDB datastore.package me.m1key.audioliciousmigration.repository import com.google.inject.Inject import me.m1key.audioliciousmigration.persistence.mongodb.MorphiaMongoDbPersistenceProvider import scalaj.collection.Imports._ import me.m1key.audioliciousmigration.entities.mongodb.MongoDbSong import com.google.code.morphia.query.UpdateOperations import com.google.code.morphia.query.Query class MorphiaMongoDbRepository @Inject() (private val persistenceProvider: MorphiaMongoDbPersistenceProvider) { def save(song: MongoDbSong): Unit = { val datastore = persistenceProvider.getDatastore() val selectQuery = datastore.createQuery(classOf[MongoDbSong]) .field("name").equal(song.name).field("albumName").equal(song.albumName) .field("artistName").equal(song.artistName).field("songKey").equal(song.songKey); val existingSong = selectQuery.get() if (existingSong != null) { var ops = datastore.createUpdateOperations(classOf[MongoDbSong]) if (song.genre != null) { ops = ops.set("genre", song.genre) } if (song.year != 0) { ops = ops.set("year", song.year) } if (song.songArtistName != null) { ops = ops.set("songArtistName", song.songArtistName) } val stat = song.statsList.get(0) existingSong.addOrEditStats(stat.libraryUuid, stat.percentage, stat.playCount, stat.skipCount) ops.set("statsList", existingSong.statsList) val updatedCount = datastore.update(selectQuery, ops).getUpdatedCount() if (updatedCount != 1) { println("Warning. Song updated [%d] times while expected once: [%s]".format(updatedCount, song)) } } else { datastore.save(List(song).asJava) } } }
getMongo
Here's code that uses getMongo for executing a raw query.def mine(): Option[Int] = { val mongo = persistenceProvider.getMongo val result = mongo.getDB("audiolicious").eval(query) result match { case double: java.lang.Double => return Some(double.intValue()) case _ => println("Error while obtaining stats. Result of unknown type [%s].".format(result)) return None; } }
Putting it all together...
Before code using datastore or mongo can be executed, the following call has to be made.val mongoDbPersistence = injector.getInstance(classOf[MorphiaMongoDbPersistenceProvider]) mongoDbPersistence.initialise(This example is using Guice) I don't know if this is the best way of doing things, but there's little documentation available. This way works. If you have a better idea - definitely let me know!
2012-09-25
No-args constructor in Scala for Morphia
import com.google.code.morphia.annotations.Entity import com.google.code.morphia.annotations.Indexes import com.google.code.morphia.annotations.Index @Entity @Indexes(Array(new Index(value = "name, albumName, artistName, songKey", unique = true))) class MongoDbSong(val name: String, val albumName: String, val artistName: String, val songKey: String) { // No-args constructor to be used by Morphia. def this() { this("name to be set", "albumName to be set", "artistName to be set", "songKey to be set") }
2012-09-23
2012-08-29
GROUP BY with MongoDB (and Scala and Morphia)
This is an example of how to run a query similar to GROUP BY from the SQL world, but in MongoDB. MongoDB doesn't have GROUP BY, but it has a very similar group command.
In my example I want to return the number of songs per artist, therefore I'm grouping by artist name.
db.MongoDbSong.group( {key: {artistName: true}, initial: {totalSongs: 0}, reduce: function(obj, prev) { prev.totalSongs++;} })
This example is from my MongoDB + Scala + Gradle + Morphia app. It turns out that Morphia does not help with that at the moment, so we have to run the query raw and then process the results.
class SongPerArtistMining @Inject() (private val persistenceProvider: MorphiaMongoDbPersistenceProvider) { private val query = "db.MongoDbSong.group("+ "{key: {artistName: true},"+ "initial: {totalSongs: 0},"+ "reduce: function(obj, prev) { prev.totalSongs++;}"+ "})"; private val formatter = NumberFormat.getInstance(Locale.ENGLISH) def mine(maxResults: Int): Option[List[(String, Int)]] = { val mongo = persistenceProvider.getMongo val result = mongo.getDB("audiolicious").eval(query) result match { case list: BasicDBList => return Some(processAndRetrieveResults(list, maxResults)) case _ => println("Error while obtaining stats. Result of unknown type [%s].".format(result)) return None; } }
Essentially, this does it:
val result = mongo.getDB("audiolicious").eval(query)
Processing results
And here's how to process results.
private def processAndRetrieveResults(list: BasicDBList, maxResults: Int): List[(String, Int)] = { return processResults(list).sortWith(compareSecondValueInteger).slice(0, maxResults) } def compareSecondValueInteger(e1: (String, Int), e2: (String, Int)) = e1._2 > e2._2 private def processResults(list: BasicDBList): List[(String, Int)] = { var results : List[(String, Int)] = List() for (i <- 0 until list.size()) { val item = list.get(i) item match { case dbObject: BasicDBObject => results ::= processResult(dbObject) case _ => println("Error while obtaining stats. Result item of unknown type [%s].".format(item.getClass())) } } return results } private def processResult(dbObject: BasicDBObject): (String, Int) = { val artistName = dbObject.get("artistName").toString() val totalSongs = parseDouble(dbObject.get("totalSongs").toString(), formatter).intValue() return (artistName, totalSongs) } // http://stackoverflow.com/a/9542323 def parseDouble(s: String, nf: NumberFormat) = { val pp = new ParsePosition(0) val d = nf.parse(s, pp) if (pp.getErrorIndex == -1) d.doubleValue else 0 }
Abandoned Sanatorium - Beelitz-Heilstätten (my pictures)
Hi, these pictures are from Beelitz-Heilstätten, the abandoned sanatorium in Germany, where Adolf Hitler once was a patient.
2012-08-25
Unique compound index with Morphia (MongoDb) and Scala
Hi, here's how to create a unique compound index with Morphia and Scala. The example is from my Gradle + Scala + MongoDb application Also, here's Part 1 of my tutorial on building a Scala + Gradle + Guice applications.
package me.m1key.audioliciousmigration.entities.mongodb import com.google.code.morphia.annotations.Id import com.google.code.morphia.annotations.Entity import org.bson.types.ObjectId import com.google.code.morphia.annotations.Indexes import com.google.code.morphia.annotations.Index @Entity @Indexes(Array(new Index(value = "name, albumName", unique = true))) class MongoDbSong(val name: String, val albumName: String) { @Id var id: ObjectId = _ override def equals(that: Any) = { that match { case that: MongoDbSong => that.name == this.name && that.albumName == this.albumName case _ => false } } override def hashCode(): Int = name.hashCode() }
Remember to run this code in your app:
datastore.ensureIndexes()
Also, my final note here is this: If you make any such changes to your MongoDb schema, you have to drop the collection from the Mongo client.
db.MongoDbSong.drop()
2012-07-29
2012-07-01
Abney Park Cemetery in Infrared
I have just uploaded infrared pictures of the Abney Park Cemetery in London.
2012-06-10
2012-05-13
Petra: Part 2 (my photos)
This completes my Middle East photo albums. Please check out my photos from Petra - part 2!
2012-05-06
2012-05-05
2012-04-23
2012-04-15
2012-04-14
Jerusalem - My Impressions (my photos)
2012-04-13
Jerusalem - the City (my pictures)
2012-04-03
Scala + Guice + Gradle: Integration tests (Part 5)
build.gradle
Below are the relevant part of your build.gradle file.test { exclude "**/*IT.class" } task integrationTest(type: Test, dependsOn: testClasses) { include "**/*IT.class" } check.dependsOn integrationTest dependencies { testCompile group: 'org.scala-tools.testing', name: 'specs_2.9.1', version: '1.6.9' testCompile group: 'junit', name: 'junit', version: '4.+' }(This obviously is not the complete file.)
We specified a new task called integrationTest which will run our integration tests. It will run during gradle build as well. gradle test alone will NOT run them.
Step by step - PersistenceLibraryRepositorySpecIT.scala
package me.m1key.audioliciousmigration.repository import org.specs._ import org.specs.runner._ import org.junit.runner.RunWith import me.m1key.audioliciousmigration.persistence.JpaPersistenceProvider import me.m1key.audiolicious.domain.entities.Library import java.util.Date @RunWith(classOf[JUnitSuiteRunner]) class PersistenceLibraryRepositorySpecIT extends Specification with JUnit { val jpaPersistenceProvider = new JpaPersistenceProvider jpaPersistenceProvider.initialise val repository = new PersistenceLibraryRepository(jpaPersistenceProvider) val entityManager = jpaPersistenceProvider.getEntityManager //...This is the test declaration. It allows us to run the test as a JUnit test from the IDE. Notice that file name ends with ...IT.scala. This is how we differentiate integration tests.
Step by step - before
// ... doBeforeSpec { deleteLibraries } //...This will run before the test (just once).
Step by step - test
"Fetching latest library with no libraries" should { var library: Library = null doFirst { println("Preparing test 1...") deleteLibraries println("Test prepared. Libraries: %d".format(librariesCount)) } "return None." in { entityManager.getTransaction().begin() repository.getLatestLibrary() mustBe None entityManager.getTransaction().commit() } doLast { println("Cleaning up...") deleteLibraries println("Cleaned up. Libraries: %d".format(librariesCount)) } }This is a test. Notice the BDD style. The assertion is this line: repository.getLatestLibrary() mustBe None. doFirst and doLast allow us to prepare the test and clean up.
Step by step - another test
"Fetching library by UUID with three libraries" should { setSequential() var olderLibrary: Library = null var anotherOlderLibrary: Library = null var newerLibrary: Library = null doFirst { println("Preparing test 3...") deleteLibraries olderLibrary = insertLibrary Thread.sleep(1000) anotherOlderLibrary = insertLibrary Thread.sleep(1000) newerLibrary = insertLibrary println("Test prepared. Libraries: %d".format(librariesCount)) } "return correct 1st library." in { entityManager.getTransaction().begin() val library = repository.getLibrary(olderLibrary.getUuid).get entityManager.getTransaction().commit() library mustBe olderLibrary } "return correct 2nd library." in { entityManager.getTransaction().begin() val library = repository.getLibrary(anotherOlderLibrary.getUuid).get entityManager.getTransaction().commit() library mustBe anotherOlderLibrary } "return correct 3rd library." in { entityManager.getTransaction().begin() val library = repository.getLibrary(newerLibrary.getUuid).get entityManager.getTransaction().commit() library mustBe newerLibrary } doLast { println("Cleaning up...") deleteLibraries println("Cleaned up. Libraries: %d".format(librariesCount)) } }setSequential allows us to keep variables (olderLibrary, anotherOlderLibrary, newerLibrary) in scope for all tests.
Step by step - final clean up
//... doAfterSpec { deleteLibraries } //...
You can see the whole source for this file here.
Final note
gradle build will NOT run tests that have already run since the last code change! If you want them to run again, use gradle clean build.Download source code
Source code for this article can be obtained via GitHub. Please see the README file for building and running.Scala + Guice + Gradle: JPA 2 & EclipseLink (Part 4)
This sample app is a console app, so we have to handle entity management ourselves.
persistence.xml
<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> <persistence-unit name="audioliciousPu"> <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> <class>me.m1key.audiolicious.domain.entities.Library</class> <class>me.m1key.audiolicious.domain.entities.Artist</class> <class>me.m1key.audiolicious.domain.entities.Album</class> <class>me.m1key.audiolicious.domain.entities.Song</class> <class>me.m1key.audiolicious.domain.entities.Stat</class> <class>me.m1key.audiolicious.domain.entities.Rating</class> <properties> <property name="eclipselink.jdbc.driver" value="com.mysql.jdbc.Driver" /> <property name="eclipselink.jdbc.url" value="jdbc:mysql://localhost:3306/audiolicious_test" /> <property name="eclipselink.jdbc.user" value="root" /> <property name="eclipselink.jdbc.password" value="" /> <property name="eclipselink.target-database" value="MYSQL" /> <property name="eclipselink.logging.level" value="OFF" /> <property name="eclipselink.orm.throw.exceptions" value="true" /> </properties> </persistence-unit> </persistence>
EntityManager
package me.m1key.audioliciousmigration.persistence import javax.persistence.EntityManager import javax.persistence.EntityManagerFactory import javax.persistence.Persistence class JpaPersistenceProvider extends PersistenceProvider { private var factory: EntityManagerFactory = _ private var entityManager: EntityManager = _ @Override def initialise: Unit = { factory = Persistence.createEntityManagerFactory("audioliciousPu") entityManager = factory.createEntityManager(); } @Override def getEntityManager: EntityManager = { return entityManager } @Override def close: Unit = { entityManager.close(); factory.close(); } }
Usage
val persistenceProvider = injector.getInstance(classOf[PersistenceProvider]) persistenceProvider.initialise val entityManager = persistenceProvider.getEntityManager entityManager.getTransaction().begin() //... entityManager.getTransaction().commit() persistenceProvider.close
build.gradle
repositories { // ... mavenRepo name: "EclipseLink", url: "http://download.eclipse.org/rt/eclipselink/maven.repo/" } dependencies { compile group: 'me.m1key.audiolicious', name: 'audiolicious-domain-objects', version: '0.1.0-SNAPSHOT' compile group: 'org.eclipse.persistence', name: 'eclipselink', version: '2.3.0' compile group: 'org.eclipse.persistence', name: 'javax.persistence', version: '2.0.0' compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.18' // … }We needed to explicitly add EclipseLink specific repository.
Conclusion
I did not present any entities here because in my app they come from another Java powered project. In the next article I will show you how to integration test this code.Download source code
Source code for this article can be obtained via GitHub. Please see the README file for building and running.2012-04-02
Scala + Guice + Gradle: Singleton (Part 3)
Singleton
This is the singleton to be class:class PersistenceLibraryRepository @Inject() (private val persistenceProvider: PersistenceProvider) extends LibraryRepository { // ... }Nothing special here!
As far as I know, you cannot use the @Singleton annotation, so this is how you make this class a singleton:
class AudioliciousMigrationModule extends AbstractModule { @Override protected def configure() { bind(classOf[LibraryRepository]) .to(classOf[PersistenceLibraryRepository]) .in(Scopes.SINGLETON) } }Voila!
In Part 4 I will show you how to connect to a MySQL database using JPA 2 implemented by EclipseLink.
Download source code
Source code for this article can be obtained via GitHub.2012-04-01
Scala + Guice + Gradle (Part 2)
Basic example
The abstraction that defines the contract:package me.m1key.audioliciousmigration trait AudioliciousImporter { def importLibrary(libraryUuid: String): Unit }The implementation:
package me.m1key.audioliciousmigration.importer import me.m1key.audioliciousmigration.AudioliciousImporter private[audioliciousmigration] class RelativeDataImporter extends AudioliciousImporter { def importLibrary(libraryUuid: String): Unit = { println("Importing library [%s]...".format(libraryUuid)); println("Library [%s] imported.".format(libraryUuid)); } }Note package level private access - the implementation is not visible from the outside.
Here we bind implementations to abstractions:
package me.m1key.audioliciousmigration import com.google.inject.AbstractModule import com.google.inject.Provides import me.m1key.audioliciousmigration.importer.RelativeDataImporter class AudioliciousMigrationModule extends AbstractModule { @Override protected def configure() { bind(classOf[AudioliciousImporter]).to(classOf[RelativeDataImporter]) } }Finally, the bootstrap:
package me.m1key.audioliciousmigration import com.google.inject.Guice object Launcher { def main(args: Array[String]): Unit = { println("Audiolicious Importer") val injector = Guice.createInjector(new AudioliciousMigrationModule) val importer = injector.getInstance(classOf[AudioliciousImporter]) importer.importLibrary("UUID") println("Bye.") } }If you're new to Guice and you're confused, you can read more about it here.
In Part 3 I will show you how to create a singleton.
Download source code
Source code for this article can be obtained via GitHub.2012-03-31
Scala + Guice + Gradle Tutorial (Part 1)
Create Gradle project in IDE from existing Gradle sources
If you already have a Gradle project, you can easily generate Eclipse or IntelliJ project files using available plugins (Eclipse plugin, IntelliJ plugin). I haven't found anything like that for NetBeans but I'm not a NetBeans user.Create Gradle project from scratch
If you do not have Gradle sources and are starting a project from scratch, it's a bit more tiresome, because at this moment neither Eclipse nor IntelliJ support creating Gradle projects. NetBeans has a Gradle plugin, but I haven't tried it out.(Option 1 - any IDE) In that case you can create a Gradle project skeleton by hand and then import it into your IDE using methods above (there seems to be no way to create a skeleton project automatically at this moment).
(Option 2 - just Eclipse) You can create a new Scala project in your IDE and add Gradle nature - but that at the moment only seems to work in Eclipse. Again, I haven't tried that with NetBeans.
Here's how to do it with SpringSource Tool Suite:- Download and install STS if you haven't already.
- Launch STS.
- Install these extensions: Scala, Gradle, Groovy. Maven support is not needed.
- Create a new Scala Project.
- Create source folders: src/main/scala, src/main/resources, src/test/scala, src/test/resources.
- Right click on the project, Configure -> Convert to Gradle project.
- Create a file called build.gradle in the root project directory.
- Paste sample build.gradle content (see below).
- Right click on the build.gradle file and choose Gradle -> Refresh All. Give it a while...
- Right click on the build.gradle file and choose Run As -> Gradle Build. Tick build and hit Run.
- This should build the project and obtain all dependencies.
- Create a Scala object in a package inside src/main/scala. In my case the package is me.m1key.audioliciousmigration and the class name is Launcher. That corresponds to the build.gradle manifest Main-class declaration.
- Paste Launcher.scala content (from below).
- You can now right click on Launcher, choose Run As -> Scala Application and it will run.
- If you now run gradle install rather that gradle build, it will deploy the artifact to your local Maven (.m2) repository. The artifact name is deduced from the project name.
build.gradle
apply plugin: 'scala' apply plugin: 'maven' sourceCompatibility = 1.6 version = '0.0.1-SNAPSHOT' group = 'me.m1key.audiolicious-migration' jar { manifest { attributes 'Implementation-Title': 'Audiolicious Migration', 'Implementation-Version': version, 'Main-class': 'me.m1key.audioliciousmigration.Launcher' } from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } } repositories { mavenCentral() } dependencies { compile group: 'com.google.inject', name: 'guice', version: '3.0' testCompile group: 'org.scala-tools.testing', name: 'specs_2.9.1', version: '1.6.9' testCompile group: 'junit', name: 'junit', version: '4.+' // Libraries needed to run the scala tools scalaTools 'org.scala-lang:scala-compiler:2.9.1' scalaTools 'org.scala-lang:scala-library:2.9.1' // Libraries needed for scala api compile 'org.scala-lang:scala-library:2.9.1' }
Launcher.scala
package me.m1key.audioliciousmigration object Launcher { def main(args: Array[String]): Unit = { println("Hello world!") } }Once it's built, you can run the jar like that from project root:
java -jar build\libs\audiolicious-migration-0.0.1-SNAPSHOT.jar (Windows)
java -jar build/libs/audiolicious-migration-0.0.1-SNAPSHOT.jar (Unix)
Unit Tests
Here's how to write a unit test. You can run it as a JUnit test in your IDE and it runs as a part of gradle build, which I think is quite cool.package me.m1key.audioliciousmigration import org.specs._ import org.specs.runner._ import org.junit.runner.RunWith @RunWith(classOf[JUnitSuiteRunner]) class LauncherSpec extends Specification with JUnit { "2 + 2" should { "equal 4" in { 2 + 2 mustBe 4 } } }gradle install does not run tests!
Download source code
Source code for this article can be obtained via GitHub.Guice
In Part 2 of this tutorial I will describe how to use Guice in this project.2012-02-06
2012-01-18
2012-01-16
Hibernate - collection was not processed by flush
collection was not processed by flushWhat happened? I have the following class structure.
Artist -> Album -> Song (parent -> child) Library -> Stat (parent -> child) Song - Stat (association)Artist and Library are aggregates in DDD sense. Artist is the parent of Album, Album is the parent of Song. Library is the parent of Stat. What's important, there's also a bidirectional relationship between Song and Stat (but no ownership). The exception occurred in the following situation:
- I had an Artist with one Album with one Song that had an association with one Stat being a child of its Library and that was created in Transaction 1.
- In Transaction 2, the whole thing was loaded again, a new Stat added, being a child of a new Library.
- Transaction 2 ended. Exception thrown.