Search

Dark theme | Light theme

April 6, 2009

Poll for files and use a Grails service with Apache Camel

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()
    }
}