Technipelago AB

Technipelago Blog Stuff that we learned...


Grails 1.3.7 to 2.0.1 upgrade experiences

Publicerad den 28 feb 2012

Today I finally took the time to upgrade my largest Grails 1.3.7 application to Grails 2.0.1. This blog post describe how I did it.

I started to develop this application during spring 2008. What was the Grails version back then? Was it 1.0 or maybe 1.1? I'm not sure what version I started with. Anyway I've been eager to upgrade to new Grails versions fairly quickly after each release so I've been using all Grails versions since 1.1.

This particular application is in production use by a handfull of clients. It's a mission critical CRM system so quality is important. That's the main reason why I didn't jump on the Grails 2.0 wagon as early as I used to. All my Grails apps in production are still on Grails 1.3.7.

I constantly monitor the Grails mailing list to hear what people say about upgrade experiences and the quality of Grails 2.0. I did not touch Grails 1.4/2.0 until 2.0.0 was released. Then I felt comfortable enough to upgrade a few plugins and simple apps. But I still was a little afraid to upgrade my largest and most important Grails app to 2.0.

When Grails 2.0.1 was released I upgraded some more plugins and started a completely new client project on Grails 2.0.1. After two weeks working full days with 2.0.1 I decided to try and upgrade my major 1.3.7 application to 2.0.1.

This is a transcript of my upgrade session:

 

2012-03-01 12:30 - Starting Grails 1.3.7 to 2.0.1 upgrade

# grails upgrade

SUCCESS

# Manually updated BuildConfig.groovy by looking at a freash 2.0.1 application. I also bumbed the version number for all plugin dependencies to the latest and greatest.

# grails compile

/Users/goran/.grails/2.0.1/projects/myapp/plugins/persistent-result-0.6/src/java/grails/plugins/persistentresult/QueryResult.java:42: cannot find symbol

symbol  : constructor PagedResultList(java.util.List)

location: class grails.orm.PagedResultList

        super(list);

        ^

This code is in one of my own developed plugins. I have a custom class derived from grails.orm.PagedResultList and it seems like it has changed a lot between 1.3.7 and 2.0

I looked at my implementation and decided I could subclass java.util.AbstractList instead of PagedResultList. It took some time to change the implementation but I finally got it to work.

# grails compile

SUCCESS

# grails test-app

| Error Error executing script TestApp: org.codehaus.groovy.grails.exceptions.GrailsConfigurationException: Database driver [org.hsqldb.jdbcDriver] for HSQLDB not found. Since Grails 2.0 H2 is now the default database. You need to either add the 'org.h2.Driver' class as your database driver and change the connect URL format (for example 'jdbc:h2:mem:devDb') in DataSource.groovy or add HSQLDB as a dependency of your application. (Use --stacktrace to see the full trace)

Ahh. I must modify DataSource.groovy and change HSQLDB to H2. Since this is a plugin a standard DataSource.groovy is sufficient so I copied one from a fresh Grails 2.0.1 plugin.

# grails test-app

Tests PASSED

# grails publish-plugin --repository=myCompanyRepository

OK

Now I went back to the main Grails app and updated BuildConfig.groovy to depend on the fixed plugin.

# grails compile

| Warning The [saveQuery] action in [PersonController] accepts a parameter of type [PersonQueryCommand] which has not been marked with @grails.validation.Validateable. This may lead to an intermittent MissingMethodException for validate(). We strongly recommend that you mark command object classes with @Validateable in order to avoid the problem.

I added @Validateable to all commands objects

# grails compile

SUCCESS

# grails run-app

| Running Grails application

| Error 2012-03-01 13:31:06,814 [pool-7-thread-1] ERROR [localhost].[/myapp]  - Error configuring application listener of class org.codehaus.groovy.grails.web.util.Log4jConfigListener

Message: org.codehaus.groovy.grails.web.util.Log4jConfigListener

   Line | Method

->>  46 | findClass in org.grails.plugins.tomcat.ParentDelegatingClassLoader

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

|   306 | loadClass in java.lang.ClassLoader

|   247 | loadClass in     ''

Hmmm.. Something with logging. I copied Config.groovy from a fresh Grails 2.0.1 application and merged my Config.groovy file manually inspecting every line.

# grails run-app

SAME PROBLEM

Googling for: grails org.codehaus.groovy.grails.web.util.Log4jConfigListener

Ahh I must upgrade applicationContext.xml and sitemesh.xml in web-app/WEB-INF.

I copied applicationContext.xml and sitemesh.xml from a fresh Grails 2.0.1 application.

# grails run-app

SAME PROBLEM

NOW I READ THE UPGRADE NOTES! :-)

http://grails.org/doc/2.0.x/guide/gettingStarted.html#upgradingFromPreviousVersionsOfGrails

Ahhh stupid me! I had a a custom src/templates/war/web.xml

I removed web.xml since I could not find any customizations in it.

# grails run-app

SUCCESS!!!    ALMOST…

 

| Error 2012-03-01 13:58:48,901 [http-bio-8080-exec-9] ERROR [/myapp].[default]  - Servlet.service() for servlet [default] in context with path [/myapp] threw exception [Filtered request failed.] with root cause

Message: It looks like you are missing some calls to the r:layoutResources tag. After rendering your page the following have not been rendered: [head]

Updated my layouts to use Resources.

Application is up and running and I browsed through the main pages. (No I don't have any functional tests yet)

PROBLEM

One controller that subclasses a base controller in src/groovy did not work (404).

I changed all closures to methods in the abstract controller and did the same in the controller subclass.

SAME PROBLEM

I added all methods that only existed in the abstract class to my subclass and made them call super.method()

(this was a quick fix. I need to go back later and find out what really was the problem)

ANOTHER PROBLEM

In the problematic controller class I had a lot of redirects that referenced action closure properties directly instead of their name.

redirect(action: show, id:params.id)

I changed all to: 

redirect(action: "show", id:params.id)

SUCCESS!!!

 

FINISHED 14:30

TOTAL TIME FOR UPGRADE: 2 HOURS

There are lot more tests I need to perform before I can put this in production but it feels great to have come this far. And it went faster than I expected. I was prepared for a few days of banging my head against the keyboard.

Tags: grails upgrade


Setup two Ubuntu 10.04 LTS Servers as Cassandra Hosts

Publicerad den 22 dec 2011

Step-by-step instructions for setting up a Ubuntu server from scratch with Apache Cassandra NoSQL database

Step-by-step instructions for setting up a Ubuntu server from scratch with Apache Cassandra NoSQL database.

Install Ubuntu

Install Ubuntu Server 10.04 without any extra packages.

Install Java

sudo apt-get install python-software-properties

sudo add-apt-repository "deb http://archive.canonical.com/ lucid partner"

sudo apt-get update

sudo apt-get install sun-java6-jdk

sudo vi /etc/profile

export JAVA_HOME=/usr/lib/jvm/java-6-sun

Install Apache Cassandra

wget http://apache.mirrors.spacedump.net//cassandra/1.0.6/apache-cassandra-1.0.6-bin.tar.gz

tar xf apache-cassandra-1.0.6-bin.tar.gz

sudo mkdir /var/log/cassandra

sudo mkdir /var/lib/cassandra

sudo chmod 777 /var/log/cassandra

sudo chmod 777 /var/lib/cassandra

Configuring a basic cluster with two hosts

Setup a second Ubuntu server, follow all of the "Install Ubuntu, Java and Cassandra" steps above, then:

On the first Ubuntu server, modify conf/cassandra.yaml:

cluster_name: 'Your Cluster Name'

initial_token: 0

listen_address: <this server's IP address>

rpc_address: <this server's IP address>

seeds: <ip1, ip2>

On the second Ubuntu server, modify conf/cassandra.yaml:

cluster_name: 'Your Cluster Name'

initial_token: 85070591730234615865843651857942052864

listen_address: <this server's IP address>

rpc_address: <this server's IP address>

seeds: <server_1_ip, server_2_ip>

Note: Each Ubuntu server must be able to contact the other via the IP addresses mentioned.

Now start Cassandra on the first Ubuntu server, give it a moment to come up, then start Cassandra on the second Ubuntu server.

Start Cassandra

cd .../apache-cassandra-1.0.6

bin/cassandra -f

Install SSH

Install SSH Server so you can access the node

sudo apt-get install openssh-server

Tags: cassandra bigdata ubuntu setup


Basic Authentication with grails-cxf plugin

Publicerad den 10 aug 2010

Secure your CXF-powered web services with 'basic-authentication

With the help of Chris Dail's excellent blog post I managed to secure my CXF-powered web service with 'basic-authentication'. This is how I did it:

Step 1: Download BasicAuthAuthorizationInterceptor.java from Chris blog and put it in src/java.

Step 2: Add this to resources.groovy

securityInterceptor(BasicAuthAuthorizationInterceptor) {
  users = ['wsuser':'secret']
}

Step 3: Add this to BootStrap.groovy

class BootStrap {
  def myWebServiceFactory
  def securityInterceptor

  def init = { servletContext ->
    myWebServiceFactory.getInInterceptors().add(securityInterceptor)
  }
}

Done!

SOAP requests must now provide the right username and password to access the web service.

In this example I have a grails service called MyWebService that exposes web service methods. In BootStrap you access it's factory by appending "Factory" to the service class name.

Next step for me is to use Shiro plugin to authenticate requests instead of hard coded usernames and passwords.

Tags: grails security cfx


How to drop a foreign key in MySQL

Publicerad den 19 jun 2010

Often when trying to drop a foreign key in MySQL I get an error message.

ERROR 1025: Error on rename of ‘./dbname/#sql-110_1′ to ‘./dbname/tablename’ (errno: 150)

The procedure I take when this happens is this:

MySQL> show engine innodb status;

Scroll up to find this section:

LATEST FOREIGN KEY ERROR

————————

100623 16:15:14 Error in foreign key constraint of table devdb/foo:

there is no index in the table which would contain

the columns as the first columns, or the data types in the

table do not match the ones in the referenced table

or one of the ON … SET NULL columns is declared NOT NULL.

CONSTRAINT “FK335CD11BA9A1557E” FOREIGN KEY (”bar_id”) REFERENCES “bar” (”id”)

MySQL> alter table tablename drop foreign key FK335CD11BA9A1557E;
MySQL> alter table tablename drop bar_id;

Sometimes I have to repeat the steps twice because there are two foreign keys to drop.

Tags: mysql


Groovy loves POI and POI loves Groovy

Publicerad den 19 feb 2010

This little Groovy builder makes reading Microsoft Excel documents a breeze. With it you can write the following code to insert customers into your Grails database


new ExcelBuilder("customers.xls").eachLine([labels:true]) {
  new Person(name:"$firstname $lastname",
    address:address, telephone:phone).save()
}

If the spreadsheet has no labels on the first row, you can use numeric index to access cells.


new ExcelBuilder("customers.xls").eachLine {
  println "First column on row ${it.rowNum} = ${cell(0)}"
}

Here is the builder source.

The only jar you need is the Apache POI jar


package extract.excel

import org.apache.poi.hssf.usermodel.HSSFWorkbook
import org.apache.poi.hssf.usermodel.HSSFSheet
import org.apache.poi.hssf.usermodel.HSSFRow
import org.apache.poi.hssf.usermodel.HSSFCell
import org.apache.poi.hssf.usermodel.HSSFDateUtil

/**
 * Groovy Builder that extracts data from
 * Microsoft Excel spreadsheets.
 * @author Goran Ehrsson
 */
class ExcelBuilder {

    def workbook
    def labels
    def row

    ExcelBuilder(String fileName) {
        HSSFRow.metaClass.getAt = {int idx ->
            def cell = delegate.getCell(idx)
            if(! cell) {
                return null
            }
            def value
            switch(cell.cellType) {
                case HSSFCell.CELL_TYPE_NUMERIC:
                if(HSSFDateUtil.isCellDateFormatted(cell)) {
                    value = cell.dateCellValue
                } else {
                    value = cell.numericCellValue
                }
                break
                case HSSFCell.CELL_TYPE_BOOLEAN:
                value = cell.booleanCellValue
                break
                default:
                value = cell.stringCellValue
                break
            }
            return value
        }

        new File(fileName).withInputStream{is->
            workbook = new HSSFWorkbook(is)
        }
    }

    def getSheet(idx) {
        def sheet
        if(! idx) idx = 0
        if(idx instanceof Number) {
            sheet = workbook.getSheetAt(idx)
        } else if(idx ==~ /^\d+$/) {
            sheet = workbook.getSheetAt(Integer.valueOf(idx))
        } else {
            sheet = workbook.getSheet(idx)
        }
        return sheet
    }

    def cell(idx) {
        if(labels && (idx instanceof String)) {
            idx = labels.indexOf(idx.toLowerCase())
        }
        return row[idx]
    }

    def propertyMissing(String name) {
        cell(name)
    }

    def eachLine(Map params = [:], Closure closure) {
        def offset = params.offset ?: 0
        def max = params.max ?: 9999999
        def sheet = getSheet(params.sheet)
        def rowIterator = sheet.rowIterator()
        def linesRead = 0

        if(params.labels) {
            labels = rowIterator.next().collect{it.toString().toLowerCase()}
        }
        offset.times{ rowIterator.next() }

        closure.setDelegate(this)

        while(rowIterator.hasNext() && linesRead++ < max) {
            row = rowIterator.next()
            closure.call(row)
        }
    }
}

Tags: groovy poi excel


How to copy a MySQL database

Publicerad den 10 dec 2009

How to copy a MySQL database with mysqldump

This is how I copy databases.


# Export
mysqldump --opt --skip-add-drop-table
  -h localhost -u root -p
    database > database.sql

# Import
mysql -u root -p
drop database dbname;
create database dbname;
use dbname;
source database.sql

Tags: mysql


Tomcat och hantering av svenska tecken

Publicerad den 5 okt 2009

Apache Tomcat fungerar inte alltid med svenska tecken om du inte först ändrar lite i konfigurationen

När jag började köra mina Grails-applikationer på Tomcat så slutade bl.a. searchable-pluginen att fungera med svenska tecken (åäö). Samma sak med ui-komponenten jQuery/autocomplete.

Problemet är kodningen av svenska tecken i en URL och löses genom följande tillägg i Tomcats server.xml


<Connector port="8080" protocol="HTTP/1.1"
  redirectPort="8443" connectionTimeout="20000"
  useBodyEncodingForURI="true"
/>

Eller om man använder Apache HTTPD framför Tomcat med AJP:


<Connector port="8009" protocol="AJP/1.3" redirectPort="8443"
useBodyEncodingForURI="true"
/>

Tags: tomcat encoding


Win32Wrapper startup timeout

Publicerad den 29 mar 2009

This blog post describes how to increase the startup timeout for Win32Wrapper

I use Win32Wrapper to start Jetty as a windows service. However, the default startup timeout of 30 seconds is to short for my Grails application to start. So I have to increase the startup timeout.

I did not find the parameter documented clearly, so I put it here as a reminder if I need it again.

Add this line to jetty-service.xml


wrapper.startup.timeout = 60

Tags: jetty windows


Reading IMAP mailbox with Groovy

Publicerad den 15 mar 2009

Groovy can make it easy to read an IMAP mailbox

The following Groovy code connects to an IMAP mailbox and read all unread messages.


import javax.mail.*
import javax.mail.search.*
import java.util.Properties

Properties props = new Properties()
props.setProperty("mail.store.protocol", "imap")
props.setProperty("mail.imap.host", host)
props.setProperty("mail.imap.port", port)
def session = Session.getDefaultInstance(props, null)
def store = session.getStore("imap")
def inbox

try {
  store.connect(host, username, password)
  inbox = openFolder(store, "INBOX")
  def messages = inbox.search(
    new FlagTerm(new Flags(Flags.Flag.DELETED), false))
  messages.each { msg ->
    println("${msg.subject} ${msg.sender}")
    msg.setFlag(Flags.Flag.SEEN, true)
  }
} finally {
  if(inbox) {
    inbox.close(true)
  }
  store.close()
}

Tags: groovy email imap


Make sure you have the right Content-Type for JSON

Publicerad den 3 mar 2009

Yesterday I spent 4 hours debugging a YUI AutoComplete component that I put on my page via the Grails-UI plugin. The result container (drop down) was not expanded even though the AJAX request returned perfect formed JSON data.

It was not until I put a YAHOO logger on my page and put som extra logging statements in autocomplete-debug.js that I found the simple and stupid problem.

The content-type of my AJAX response was set to “text/json” instead of “application/json”.

I copied an example from the web and did not notice the wrong content type.

The content type “application/json” is registered with IANA but there are many examples out there with other content types for JSON data. Read the specification.

Happy to have found the problem, but very frustrated that I spent that many hours on it.

By the way: Grails make it really simple to render JSON responses. But make sure you specify “application/json”.


def ajaxFindSystems = {
  def result = Systems.list()
  render(contentType:'application/json') {
    results {
      result.each{sys->
        system(id:sys.id, name:sys.name)
      }
    }
    resultset (rows:result.size())
  }
}

Tags: grails json


Add method on String to truncate long texts nicely

Publicerad den 17 nov 2008

I had several domain classes with long text content that I wanted to display just a summary of on the screen, especially in lists columns, etc.. I first added a getIntro(int numChars) method on domain classes with long strings, and used them when appropriate.

But I soon realised that I violated the DRY principle (Dont Repeat Yourself) with getInto() methods all over the place. I could have put the method in a base class but my text properties did not have the same name in all domain classes, so that was complicated.

I think I found a much better solution. I removed all those methods and added a getIntro(int) method to String and GString in BootStrap.groovy.


import org.apache.commons.lang.StringUtils

String.metaClass.intro = {len ->
  return StringUtils.abbreviate(delegate, len) ?: ''
}

GString.metaClass.intro = {len ->
  return StringUtils.abbreviate(delegate.toString(), len)
}

Now if I want a short introduction of a long text displayed somewhere I can just do like this:


def longText = "This is a long text that can mess upp the gui if it is displayed in its full length"
longText.intro(20).encodeAsHTML()

The result will be: This is a long text…

Adding new methods to existing classes is a really nice feature in Groovy!

Tags: groovy grails


Multiple Grails-UI AutoComplete components

Publicerad den 9 nov 2008

If you have a Grails-UI autoComplete component on a page that are dependent on another input element on the page, you can use the dependsOn=”id-of-other-element” config parameter.

Then the value of that other component will be passed in a “dependsOnValue” request parameter.

However, if the dependant element is also an autoComplete component and you want to send it’s hidden id-value in the request, you must use an alternative syntax for dependsOn.


<gui:autoComplete
  id="category"
  controller="demo"
  action="findCategories"
/>
<gui:autoComplete
  id="item"
  controller="demo"
  action="findItems"
  dependsOn="[value:’category’, label:’cat’, useId:true]"
/>

The label parameter is optional, but you can use it to change the request parameter name from “dependsOnValue” to something else (in this case ‘cat’).

This is not well documented but I found it by looking at the Grails-UI source (InputTagLib.groovy).

Tags: grails ui


Invoke JPA lifecycle events in Grails

Publicerad den 24 sep 2008

If you use JPA instead of GORM, Grails will not invoke JPA lifecycle events automatically, i.e. @PrePersist, @PreUpdate, @PostRemove, etc.

That was a requirement in my project and I don’t know if this will be fixed in future versions of Grails. An issue exists that are targeted for Grails 1.1 (http://jira.codehaus.org/browse/GRAILS-2094)

However I solved it with a Hibernate event listener. Now I can annotate my JPA entities as usual and have Grails call them at the right time. The same entities are used by other Java Enterprise components in Glassfish so it’s good to have this work transparently from both sides.

Here’s the code:


package my.package;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import org.hibernate.event.PreInsertEvent;
import org.hibernate.event.PreInsertEventListener;
import org.hibernate.event.PreUpdateEvent;
import org.hibernate.event.PreUpdateEventListener;

/**
 * If you use JPA instead of GORM, grails will not invoke
 * JPA lifecycle events automatically.
 * This event listener will bridge the gap.
 * It will listen to Hibernate events and invoke
 * corresponding JPA lifecycle events,
 * i.e. @PreInsert, @PreUpdate, @PostRemove, etc.
 * Put this code in BootStrap.groovy
 * to install the listener
 * (modify/duplicate for each event).
 *
 * def init = { servletContext ->
 * def mel = new my.package.MyEventListener()
 * def ctx = WebApplicationContextUtils.
 *     getRequiredWebApplicationContext(servletContext)
 * def sessionFactory  = ctx.getBean('sessionFactory')
 * EventListeners eventListeners = sessionFactory.eventListeners
 * // PreInsert
 * def listeners = eventListeners.preInsertEventListeners
 * listeners = Arrays.copyOf(listeners, listeners.length + 1)
 * listeners[-1] = mel
 * eventListeners.preInsertEventListeners = listeners
 * // PreUpdate
 * listeners = eventListeners.preUpdateEventListeners
 * listeners = Arrays.copyOf(listeners, listeners.length + 1)
 * listeners[-1] = mel
 * eventListeners.preUpdateEventListeners = listeners
 * }
 *
 */
public class MyEventListener implements PreInsertEventListener, PreUpdateEventListener {
    /**
     * Invoke JPA entity methods annotated with @PreInsert.
     * @param event the Hibernate event
     * @see javax.persistence.PrePersist
     * @return normally false, true if method cannot be invoked.
     */
    public boolean onPreInsert(final PreInsertEvent event) {
        Object m = event.getEntity();
        boolean veto = false;
        for (Method meth : m.getClass().getDeclaredMethods()) {
            if (meth.getAnnotation(PrePersist.class) != null) {
                try {
                    meth.invoke(m);
                } catch (Exception ex) {
                    Logger.getLogger(MyEventListener.class.getName()).log(Level.SEVERE, null, ex);
                    veto = true;
                }
            }
        }
        return veto;
    }

    /**
     * Invoke JPA entity methods annotated with @PreUpdate.
     * @param event the Hibernate event
     * @see javax.persistence.PreUpdate
     * @return normally false, true if method cannot be invoked.
     */
    public boolean onPreUpdate(final PreUpdateEvent event) {
        Object m = event.getEntity();
        boolean veto = false;
        for (Method meth : m.getClass().getDeclaredMethods()) {
            if (meth.getAnnotation(PreUpdate.class) != null) {
                try {
                    meth.invoke(m);
                } catch (Exception ex) {
                    Logger.getLogger(MyEventListener.class.getName()).log(Level.SEVERE, null, ex);
                    veto = true;
                }
            }
        }
        return veto;
    }
}

Tags: grails jpa


Serialize domain objects in Grails Web Flow

Publicerad den 15 sep 2008

If you are using Grails Web Flow and put domain object in flash scope.


flash.model = new MyModel()

Make sure your events beforeInsert, beforeUpdate, beforeDelete, etc. are marked transient. Otherwise the domain object cannot be serialized and you get the following exception:



Could not serialize flow execution; make sure all objects stored in flow or flash scope are serializable; nested exception is java.io.NotSerializableException
 

This happens because closures cannot be serialized.

Always mark your closues as transient to avoid future serialization problems.


class MyModel implements java.io.Serializable {

transient def beforeInsert = { … }

}

Tags: grails


Varför göra något enkelt när man kan krångla till det?

Publicerad den 20 jun 2008

Jag utvecklade ett program som läser data från min väderstation och publicerar statistiken på nätet. Så här gick det till.

Jag behövde en dedicerad file/subversion-server så jag köpte en liten server från Dell och placerade i serverrummet. Men även en liten server producerar värme och den här servern var den som fick bägaren (termometern) att rinna över. Det blev för hett och jag insåg att utan åtgärd så skulle något tråkigt inträffa.

Vad göra? Det fanns två huvudalternativ som jag såg det:

  1. Uppgradera kylanläggningen
  2. Göra sig av med en gammal energislukande server

Jag valde alternativ 2 och den gamla Windowsservern med dubbla Xeonprocessorer och 3 st SCSI-diskar (varav 2 st i en RAID0 array) hamnade på dödslistan. Det skulle utrota Microsoftoperativen från datorrummet helt och hållet, vilket inte gjorde mig det minsta olycklig.

Men den servern hade ju den ärofyllda uppgiften att ladda upp data från väderstationen till Internet var 10:e minut. Och av någon anledning så dög inte de väderstationsprogram för Linux som jag hittade på nätet. Hmmm…. Vad gör man då? Jo man skriver ett eget program så klart! Och enligt mina efterforskningar så fanns det inte många väderstationsprogram skrivna i Java så det fick bli mitt val. Man vill ju inte göra som alla andra.

Först av allt var jag tvungen att skriva en emulator som emulerade väderstationen. Jag ville ju inte utveckla och testa direkt mot den riktiga stationen för då fanns ju risken att jag skulle förlora data, och ingen information skulle laddas upp till Internet under tiden. Och det här kunde ju ta några veckor. Jag vill inte riskera några hack i kurvan på temperatur.nu.

Emulatorn var riktigt rolig att utveckla. Med dokumentationen från Davis så gick det som en dans. Därefter kunde jag ge mig på seriekommunikationen och här var jag också tvungen att emulera för jag ville fortfarande inte dra ur sladden ur väderstationen och börja hacka loss. Jag skrev en egen implementation av Java Communications API som istället för att öppna en serieport på datorn, öppnade en TCP/IP förbindelse till emulatorn. Så här långt var det fortfarande frid och fröjd allt såg ut att fungera galant. Nu kunde jag ge mig på den delen som laddade ner data och lagrade den i en MySQL databas. Hej och hå inga problem här heller. Och slutligen utvecklade jag diagramgenerering med JFreeChart, generering av HTML-sidor med FreeMarker och uppladdning på hemsidan med Apache Commons Net.

Nu fungerade hela kedjan från nedladdning och lagring av data till visualisering på webben. Äntligen dags att dra ur seriekabeln ur Windowsservern och koppla in väderstationen på Linuxservern istället.

Det var nu problemen började…

För att kommunicera med serieporten (RS-232) så valde jag Suns Java Communications API. Sun har implementationer för följande plattformar:

  • Solaris SPARC
  • Solaris x86
  • Linux x86

Det fanns en windowsimplementation tidigare, men den har man tagit bort från hemsidan av någon anledning. Det finns en open source variant också som heter RXTX men jag ville först testa den officiella implementationen från Sun.

Jag bytte ut min egen javax.comm implementation mot Suns och kopplade in väderstationen till Linuxservern. Men det gick inte lika smärtfritt som med emulatorn. Först spökade devicedrivern för /dev/ttyS0 som konverterade tecken både på in och utvägen. Dessutom var hårdvaruhandskakning inte aktiverat så jag tappade tecken. Men efter att alla I/O parametrar satts rätt (med stty kommandot) så började också rätt tecken komma från väderstationen. Men sedan visade sig den sida av javax.comm som många andra ha beskrivit på nätet. Det är inte den populäraste och stabilaste implementationen från Sun. Under ett dygn så fick jag omkring 10 st JVM krascher (Segmentation Violation). Jag fick bygga stöd för omstart av serieservern när den kraschade. Jag körde så en dryg vecka men sedan bestämde jag mig för att köpa en dedicerad hårdvarulösning för TCP/IP <-> RS232 konverteringen. Jag beställde en IP-port (se referenser). Men innan den hann komma så testade jag den open source implementation som är nästan helt kompatibel med javax.comm. Man byter bara ut alla ”import javax.comm” till ”import gnu.io”. Det vara bara några rader i en källkodsfil så det var inga problem.

Plötsligt fungerade allt som förväntat! Serieservern har inte kraschat på flera dagar. Dessutom så har RXTX stöd för både amd64 och Windows vilket Suns implementation inte har stöd för.

Innan jag påbörjade projektet så läste jag alla positiva kommentarer om RXTX, men jag vill alltid prova den officiella och standardiserade varianten först. Därför valde jag Suns Java Communications API. Jag ångrar inte att jag gjorde det. Det gav nyttiga erfarenheter. Det var ett hobbyprojekt så jag hade tid att testa och strula. Men i ett tids- och kostnads-kritiskt projekt så har man inte tid med såna här experiment.

Så mina råd till dig som ska kommunicera seriellt över RS232 från Java är:

  1. Ladda ner RXTX från http://www.rxtx.org/
  2. Se till att serieportens inställningar är korrekta. I mitt fall fungerade följande bra:
     
    stty -F /dev/ttyS0 9600 sane clocal crtscts -hupcl -icrnl -opost -onlcr

Jag kunde ha undvikt allt det här om jag hade låtit bli att röra Windowsservern och istället åkt till Jula och köpt en stor fläkt. Men vilka erfarenheter hade det gett mig? :-)

Referenser

Tags: java weather