These are my best photos yet, from incredible India.

Java, JEE, Spring, photography blog.
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.
Things to remember:
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.
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.
JAVA_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n $JAVA_OPTS" JPDA_TRANSPORT=dt_socket JPDA_ADDRESS=8000
@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.
@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.
@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.
@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.
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.
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.
git log -S "whatever you are looking for"
git log --grep="whatever you are looking for"
// ... 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' // … } // ...
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 } }
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) } } }
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; } }
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!
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") }
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)
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 }
Hi, these pictures are from Beelitz-Heilstätten, the abandoned sanatorium in Germany, where Adolf Hitler once was a patient.
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()
I have just uploaded infrared pictures of the Abney Park Cemetery in London.
This completes my Middle East photo albums. Please check out my photos from Petra - part 2!
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.
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.
// ... doBeforeSpec { deleteLibraries } //...This will run before the test (just once).
"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.
"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.
//... doAfterSpec { deleteLibraries } //...
You can see the whole source for this file here.
This sample app is a console app, so we have to handle entity management ourselves.
<?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>
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(); } }
val persistenceProvider = injector.getInstance(classOf[PersistenceProvider]) persistenceProvider.initialise val entityManager = persistenceProvider.getEntityManager entityManager.getTransaction().begin() //... entityManager.getTransaction().commit() persistenceProvider.close
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.
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.
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.
(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: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' }
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)
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!
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: