We have seen how easy it is to add Apache Camel with the Grails plugin to our Grails application. In this post we see how we can invoke a Grails service from a Camel route. We want to poll for new XML files placed in a directory and pass each file to a Grails service. In the Grails service we parse the XML and update our database with domain classes, because we have full access to all Grails artifacts.
The XML in the files is like this:
<?xml version="1.0" ?>
<AnalyticsReport>
<Report name="Dashboard">
<Title id="Title">
<Detail></Detail>
<Name>Dashboard</Name>
<ProfileName>www.website.com</ProfileName>
</Title>
<Graph id="Graph">
<XAxisTitle>Day</XAxisTitle>
<Serie>
<Label>Visits</Label>
<Id>primary</Id>
<ValueCategory>visits</ValueCategory>
<Point>
<Value>20</Value>
<Label>Wednesday, March 4, 2009</Label>
</Point>
<Point>
<Value>22</Value>
<Label>Thursday, March 5, 2009</Label>
</Point>
<Point>
<Value>22</Value>
<Label>Friday, March 6, 2009</Label>
</Point>
<Point>
<Value>11</Value>
<Label>Saturday, March 7, 2009</Label>
</Point>
<Point>
<Value>42</Value>
<Label>Sunday, March 8, 2009</Label>
</Point>
<Point>
<Value>24</Value>
<Label>Monday, March 9, 2009</Label>
</Point>
<Point>
<Value>35</Value>
<Label>Tuesday, March 10, 2009</Label>
</Point>
</Serie>
</Graph>
</Report>
</AnalyticsReport>
Yes, it is part of the Google Analytics XML report. We are going to poll a directory to see if there are new files. If new files are placed in the directory we read the XML files from the directory and pass the file to the Grails service HandleReportService with the following Camel route:
import org.codehaus.groovy.grails.commons.*
class HandleReportRoute {
def configure = {
def config = ConfigurationHolder.config
from("file://${config.camel.route.save.dir}")
.to("bean:handleReportService?methodName=save")
}
}
Notice at line 7 how we can use a Grails service in our route. We define the name of the service and the method name. Suppose for our example we use the domain classes WebsiteProfile and Visit which look like this:
class WebsiteProfile {
static hasMany = [visits:Visit]
String name
}
class Visit {
static belongsTo = [profile:WebsiteProfile]
Long numberOfVisits
Date date
}
The Grails service has one argument which is the file object from our Camel route. We use the XmlSlurper to parse the XML from the file. We use data from the XML to create our domain objects and save them:
import java.text.SimpleDateFormat
class HandleReportService {
boolean transactional = true
def save(file) {
// Default parser to read date formats from Google Analytics XML.
def dateParser = new SimpleDateFormat("EEEE, MMMM ddd, yyyy", Locale.US)
// Read the XML file.
def report = new XmlSlurper().parse(file)
final String name = report.Report.Title[0].ProfileName.text()
// See if we already have a profile in the database.
def profile = WebsiteProfile.findByName(name)
if (!profile) {
// No profile yet, so we create one here.
profile = new WebsiteProfile(name: name)
profile.save()
}
report.Report.Graph[0].Serie[0].Point.each {
final Date date = dateParser.parse(it.Label.text())
// See if we already saved the # of visits for the date and profile,
// if so we don't need to add another.
if (!Visit.findByDateAndProfile(date, profile)) {
final Integer counter = new Integer(it.Value.text())
profile.addToVisits(new Visit(numberOfVisits: counter, date: date))
}
}
profile.save()
}
}
6 comments:
Mr Haki
I love your little blog series with great uses cases and how to leverage Grails with Camel.
Keep up the good work.
Claus Ibsen
A Camel rider
Great posts! I'm glad you are finding the plugin useful!
This was very helpful. Thank you.
trying this example locally and saving the XML content to my domain object throws this error
No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here
What could be wrong here?
@va: I am not sure why this happens. Do you invoke save() on the right domain object? And do you have defined def transational = true in the service?
I'm not very familiar with Camel. With the way you've structured things, is it guaranteed that HandleReportService.save() will never be invoked by more than one thread at once? I ask because SimpleDateFormat is not thread-safe. I've seen it behave very oddly if you try to share one instance of it between two or more threads.
Otherwise, your post was very cool! Thanks!
--Matt
@Matt, you're right. I've moved the dateParser into the save() method.
Post a Comment