Efficient GWT unit testing – MVP + google gin + AtUnit

November 11, 2011 Leave a comment

For about the last year and a half I have been using a few simple patterns that make testing GWT applications very easy. None of these are anything new, in fact most have been in use for years. It always surprises me when I work with a new team that is not doing these in their UI (GWT) code, but have used all or most of them on the server side. Testing GWT apps is simple! There are just a few key things to consider from the start, and you can easily achieve (and measure) 80% test coverage.

  1. Maintain separation between the business logic and your GUI layout. I use the MVP (Model View Presenter) pattern to do this. The view is where all the UI layout, data display and eventing necessary to intercept user interaction lives. Any code that leads to a GWT.create() call will cause regular unit tests to fail, and needs to be confined to the view. The presenter is where almost all the code to unit test is. Business logic, data processing (if not done on the server), RPC, event handling, etc.
  2. Use Dependency Injection (Inversion of Control) to loosely couple the GUI code and business logic. Google gin is pretty much the only choice here, fortunately its a great one.
  3. Use a mock object library for creating stand-ins for your GUI code during unit testing. Mockito is my favorite choice for this task, it gets the job done and gets out of the way.
All of this by its self goes a long way toward easy unit testing of GWT code, but there is a simple library that really ties it all together. AtUnit. AtUnit allows you to reuse the DI configuration you have already built, overriding only what you need with mock objects or custom configuration.
To get started you need a version of AtUnit that has been patched to work with Mockito. A few patches have been provided to AtUnit for including Mockito support. Unfortunately, there has been no effort to include them. You can get a patched version from my github fork here:
Also, the example code for this article can be found here:
So, what do I mean when I say MVP? If you research it on the internet or read google’s descriptions you quickly get a picture like the diagram below. What is less clear, is what do you put in the view vs the presenter. Because testability is one of the biggest reasons to use MVP, I let that define the separation. Anything that causes a call to GWT.create() belongs in the view. Everything else is directly testable in a “JVM” unit test, and belongs in the presenter.
In order to keep the presenter and view implementations decoupled they only reference each other through their interfaces. Injecting them using gin is the next step towards easy unit tests. Gin has two ways to configure (bind) which implementation is used for an interface. The first is through annotations on the interface its self, this provides a default configuration. The second is using the gin module. Binding in the gin module can be used to over ride that done with annotations or to configure classes that are not annotated like the event bus. These examples are all annotation driven, as there is no need for module configuration.
Each interface is annotated with its default implementation.
@ImplementedBy(PresenterImpl.class)
public interface Presenter {

	void attach();

}

The implementation is the annotated to have the interfaces injected and to configure them as singletons. Here the presenter gets its view and the service injected.
@Singleton
public class PresenterImpl implements Presenter, InteractionHandler {

	protected static final String PLEASE_ENTER_AT_LEAST_FOUR_CHARACTERS = "Please enter at least four characters";
	protected static final String REMOTE_PROCEDURE_CALL_FAILURE = "Remote Procedure Call - Failure";
	protected static final String REMOTE_PROCEDURE_CALL = "Remote Procedure Call";

	@Inject
	protected View view;

	@Inject
	protected GreetingServiceAsync greetingService;

	@Override
	public void attach() {
		view.attach();
	}

	/**
	 * Send the name from the nameField to the server and wait for a response.
	 */
	@Override
	public void sendNameToServer(String textToServer) {
		// First, we validate the input.
		if (!FieldVerifier.isValidName(textToServer)) {
			view.setErrorLabelText(PLEASE_ENTER_AT_LEAST_FOUR_CHARACTERS);
			return;
		} else {
			view.setErrorLabelText("");
		}

		// Then, we send the input to the server.
		view.setSendButtonEnabled(false);
		view.setTextToServerLabelText(textToServer);
		view.clearServerResponseLabelText();
		greetingService.greetServer(textToServer, new TextToServerCallback());
	}

	protected class TextToServerCallback implements AsyncCallback<String> {

		public void onFailure(Throwable caught) {
			// Show the RPC error message to the user
			view.onFailure(REMOTE_PROCEDURE_CALL_FAILURE);

		}

		public void onSuccess(String result) {
			view.onSuccess(REMOTE_PROCEDURE_CALL, result);
		}
	}
}

Likewise the view gets a reference to the presenter through the interaction handler interface to pass user interaction back to it. The injection is done using a gin Provider to prevent an infinite loop in gin.

@Singleton
public class ViewImpl extends Composite implements View {

	private static final Binder BINDER = GWT.create(Binder.class);

	/**
	 * The message displayed to the user when the server cannot be reached or
	 * returns an error.
	 */
	private static final String SERVER_ERROR = "An error occurred while "
			+ "attempting to contact the server. Please check your network " + "connection and try again.";

	@Inject
	protected Provider<InteractionHandler> interacrionHandlerProvider;

	@UiField
	protected TextBox nameField;

	@UiField
	protected Button sendButton;

	@UiField
	protected Label errorLabel;

	private DialogBox dialogBox;

	private Button closeButton;

	private HTML serverResponseLabel;

	private Label textToServerLabel;

	public ViewImpl() {
		super();

		initWidget(BINDER.createAndBindUi(this));

		// Focus the cursor on the name field when the app loads
		nameField.setFocus(true);
		nameField.selectAll();

		createDialog();
	}

	@UiHandler("sendButton")
	public void handleSendButtonClick(ClickEvent event) {
		interacrionHandlerProvider.get().sendNameToServer(nameField.getText());
	}

	@UiHandler("nameField")
	public void handleNameFieldEnter(KeyUpEvent event) {
		if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
			interacrionHandlerProvider.get().sendNameToServer(nameField.getText());
		}
	}

	public void createDialog() {
		dialogBox = new DialogBox();
		dialogBox.setText("Remote Procedure Call");
		dialogBox.setAnimationEnabled(true);
		closeButton = new Button("Close");
		// We can set the id of a widget by accessing its Element
		closeButton.getElement().setId("closeButton");
		textToServerLabel = new Label();
		serverResponseLabel = new HTML();
		VerticalPanel dialogVPanel = new VerticalPanel();
		dialogVPanel.addStyleName("dialogVPanel");
		dialogVPanel.add(new HTML("<b>Sending name to the server:</b>"));
		dialogVPanel.add(textToServerLabel);
		dialogVPanel.add(new HTML("<br><b>Server replies:</b>"));
		dialogVPanel.add(serverResponseLabel);
		dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT);
		dialogVPanel.add(closeButton);
		dialogBox.setWidget(dialogVPanel);

		// Add a handler to close the DialogBox
		closeButton.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				dialogBox.hide();
				sendButton.setEnabled(true);
				sendButton.setFocus(true);
			}
		});
	}

	@Override
	public Widget asWidget() {
		return this;
	}

	@Override
	public void attach() {
		RootPanel.get().add(this);
	}

	@Override
	public void setErrorLabelText(String text) {
		errorLabel.setText(text);
	}

	@Override
	public void setSendButtonEnabled(boolean enabled) {
		sendButton.setEnabled(enabled);
	}

	@Override
	public void setTextToServerLabelText(String textToServer) {
		textToServerLabel.setText(textToServer);
	}

	@Override
	public void onFailure(String string) {
		dialogBox.setText(string);
		serverResponseLabel.addStyleName("serverResponseLabelError");
		serverResponseLabel.setHTML(SERVER_ERROR);
		dialogBox.center();
		closeButton.setFocus(true);
	}

	@Override
	public void onSuccess(String text, String result) {
		dialogBox.setText(text);
		serverResponseLabel.removeStyleName("serverResponseLabelError");
		serverResponseLabel.setHTML(result);
		dialogBox.center();
		closeButton.setFocus(true);
	}

	@Override
	public void clearServerResponseLabelText() {
		serverResponseLabel.setText("");
	}
}

Now for the testing magic! For the same reason you can replace the view implementation with something else at test time (decoupled by interfaces), you can replace the gin implementation with a guice one. Gin is full of GWT.create() calls, but Guice is a java library and works beautifully in unit tests. Because the gin annotations are really guice annotations, simply starting a guice module instead of a gin one works with no other modifications.

The most efficient way to start a guice module in a unit test and combine it with the power of Mockito is AtUnit. To setup a test where the presenter is created for you and injected with either the configuration defined in the annotations or mockito objects all you have to do is add a few simple annotations.

@RunWith(AtUnit.class)
@Container(Container.Option.GUICE)
@MockFramework(MockFramework.Option.MOCKITO)
public class PresenterTest extends Assert implements Module {

	private static final String IT_WORKED = "It worked";

	private static final String HELLO_WORLD = "Hello World";

	/**
	 * Inject the class to be tested. Using the Impl gives access to methods not
	 * on the presenter interface w/o casting.
	 */
	@Inject
	@Unit
	PresenterImpl presenter;

	/**
	 * Override the annotation configuration in the code to inject a mock
	 * instance.
	 */
	@Mock
	View view;

	/**
	 * Override the default gin call to GWT.create() to inject a mock instance.
	 */
	@Mock
	GreetingServiceAsync greetingService;

	@Override
	public void configure(Binder binder) {
		// Nothing extra to configure. All injection configuration is already
		// done by annotations in the code or the @Mock annotations above
	}

	/**
	 * Verify the Presenter.attach() delegates to the view.
	 */
	@Test
	public void testAttach() {
		presenter.attach();

		verify(view).attach();
	}

	@Test
	public void testSendToServer() {
		presenter.sendNameToServer(HELLO_WORLD);

		verify(view).setErrorLabelText("");
		verify(view).setSendButtonEnabled(false);
		verify(view).setTextToServerLabelText(HELLO_WORLD);
		verify(view).clearServerResponseLabelText();
		verify(greetingService).greetServer(Mockito.eq(HELLO_WORLD), Mockito.any(TextToServerCallback.class));
	}

	@Test
	public void testSendToServerInvalidText() {
		presenter.sendNameToServer("123");
		verify(view).setErrorLabelText(PresenterImpl.PLEASE_ENTER_AT_LEAST_FOUR_CHARACTERS);
	}

	@Test
	public void testCallbackOnSuccess() {
		TextToServerCallback callback = presenter.new TextToServerCallback();
		callback.onSuccess(IT_WORKED);

		verify(view).onSuccess(PresenterImpl.REMOTE_PROCEDURE_CALL, IT_WORKED);
	}

	@Test
	public void testCallbackOnFailure() {
		TextToServerCallback callback = presenter.new TextToServerCallback();
		callback.onFailure(new Exception());

		verify(view).onFailure(PresenterImpl.REMOTE_PROCEDURE_CALL_FAILURE);
	}
}

As always, a little forethought about the project structure pays big dividends in testability. Download or clone the sample code and eclipse project from github and explore it for yourself.

Spring: Handling incoming JMS messages asynchronously

September 2, 2009 1 comment

Handling incoming JMS messages in an asynchronous manner requires, implementing a MessageListener, creating a connection, starting a thread, handling connection loss and thread death, etc…
Spring’s MessageListenerContainers provide a “Message Driven Pojo” framework that handles all that for you except implementing the MessageListener.

To take advantage of this you first need to create JMS connection factory and Destination:

	<bean id="connectionPool"
		class="org.springframework.jms.connection.SingleConnectionFactory">
		<property name="targetConnectionFactory">
			<bean class="org.apache.activemq.ActiveMQConnectionFactory">
				<property name="brokerURL" value="tcp://localhost"/>
			</bean>
		</property>
	</bean>

	<bean id="messageDestination" class="org.apache.activemq.command.ActiveMQQueue">
		<constructor-arg value="MY.INCOMING" />
	</bean>

Then implement the MessageListener interface:

@Component("IncomingMsgProcessor")
public class IncomingMsgProcessor implements MessageListener {
    @Override
    public void onMessage(Message message)
    {
         //Your message handling code here
    }
}

And finally inject an instance into the MessageListenerContainer:

	<bean id="incommingMessageListenerContainer"
		class="org.springframework.jms.listener.DefaultMessageListenerContainer">
		<property name="connectionFactory" ref="connectionPool" />
		<property name="destination" ref="messageDestination" />
		<property name="messageListener" ref="IncomingMsgProcessor" />
		<!--
			Retry connection every 10 seconds.
		-->
		<property name="recoveryInterval" value="10000" />
	</bean>

org.springframework.jms.listener.DefaultMessageListenerContainer gives a nice set of features, but spring offers other implementations if more (or less) is needed. If your message listener needs access to the JMS session you can implement SessionAwareMessageListener which has a method that take the message and session onMessage(Message message, Session session) instead.

Categories: activemq, java, jms, spring

Spring: Injecting JAXB (Un)Marshaller

September 1, 2009 7 comments

Create a JAXB context passing an array of root element classes:

	<bean id="jaxbContext" class="javax.xml.bind.JAXBContext"
		factory-method="newInstance">
		<constructor-arg>
			<list>
				<value type="java.lang.Class">my.example.rootclass.Root</value>
			</list>
		</constructor-arg>
	</bean>

Create prototypes for the marshallers/unmarshallers

	<!-- Pool (un)marshallers to improve performance -->
	<bean id="marshallerTarget" class="javax.xml.bind.Marshaller"
		factory-bean="jaxbContext" factory-method="createMarshaller"
		scope="prototype">
	</bean>

	<bean id="unmarshallerTarget" class="javax.xml.bind.Unmarshaller"
		factory-bean="jaxbContext" factory-method="createUnmarshaller"
		scope="prototype">
	</bean>

Wrap these in pool targets:

	<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPoolTargetSource">
		<property name="targetBeanName" value="marshallerTarget" />
		<property name="maxSize" value="25" />
	</bean>

	<bean id="unmarshallerPoolTargetSource" class="org.springframework.aop.target.CommonsPoolTargetSource">
		<property name="targetBeanName" value="unmarshallerTarget" />
		<property name="maxSize" value="25" />
	</bean>

And create pools for reusing marshallers/unmarshallers:

	<bean id="marshaller" class="org.springframework.aop.framework.ProxyFactoryBean">
		<qualifier value="marshaller" />
		<property name="targetSource" ref="poolTargetSource" />
	</bean>

	<bean id="unmarshaller" class="org.springframework.aop.framework.ProxyFactoryBean">
		<qualifier value="unmarshaller" />
		<property name="targetSource" ref="unmarshallerPoolTargetSource" />
	</bean>

This will create one context that will be used to create up to 25 marshallers/unmarshallers. Each will be created on demand until there are 25 in use simultaneously, then calls to them will block until one becomes available. Be careful changing the state on the (un)marshallers. It would probably be a better idea to create prototypes for each configuration and inject a separate pool for each.

These can be injected in the application context or using annotations:

    @Autowired
    @Qualifier("unmarshaller")
    private Unmarshaller unmarshaller;

    @Autowired
    @Qualifier("marshaller")
    private Marshaller marshaller;

Categories: java, jaxb, spring

Spring: Injecting ActiveMQ JMS Connection

September 1, 2009 Leave a comment

First create a connection factory.

	<bean id="connectionPool"
		class="org.springframework.jms.connection.SingleConnectionFactory">
		<property name="targetConnectionFactory">
			<bean class="org.apache.activemq.ActiveMQConnectionFactory">
				<property name="brokerURL" value="tcp://localhost"/>
			</bean>
		</property>
	</bean>

Create destiation:

	<bean id="messageDestination" class="org.apache.activemq.command.ActiveMQQueue">
		<constructor-arg value="MY.OUTGOING" />
	</bean>

Inject this into spring a template:

	<bean id="jmsSendTemplate" class="org.springframework.jms.core.JmsTemplate">
		<qualifier value="messageTemplate" />
		<property name="connectionFactory">
			<ref bean="connectionFactory" />
		</property>
		<property name="defaultDestination">
			<ref bean="messageDestination" />
		</property>
	</bean>

Which can then be injected in the application context or using annotations:

    @Autowired
    @Qualifier("messageTemplate")
    JmsTemplate jmsReceiveTemplate;

Categories: activemq, java, jms, spring

Next Sequence Value Using Hibernate Entity Manager

September 1, 2009 4 comments

To retrieve the next value of an Orcale sequence using the Hibernate Entity Manager first create a result set mapping.

@SqlResultSetMapping(name = "NextSequenceVal", columns = { @ColumnResult(name = "NEXTVAL") })

The mapping can go anywhere, I like to put it on the entity it is most closely associated with.
Then in your DAO.

    public Long getNextElecTransUniqueId()
    {
        Query query = entityManager.createNativeQuery("SELECT MY_SEQUENCE.NEXTVAL from Dual",
                "NextSequenceVal");
        // Workaround for
        // http://opensource.atlassian.com/projects/hibernate/browse/EJB-434
        // which breaks query.getSingleResult()
        return ((BigDecimal) query.getResultList().get(0)).longValue();

    }

This example is written for Hibernate 3.3.2, Hibernate Entity Manager 3.4.0, Oracle 10g.

Note: in Entity Manager 3.4.0 if you use query.getSingleResult() you will get the exception:
Exception: org.hibernate.exception.SQLGrammarException: could not execute query^M
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:90)^M
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)^M
at org.hibernate.loader.Loader.doList(Loader.java:2235)^M
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2129)^M
at org.hibernate.loader.Loader.list(Loader.java:2124)^M
at org.hibernate.loader.custom.CustomLoader.list(CustomLoader.java:312)^M
at org.hibernate.impl.SessionImpl.listCustomQuery(SessionImpl.java:1723)^M
at org.hibernate.impl.AbstractSessionImpl.list(AbstractSessionImpl.java:165)^M
at org.hibernate.impl.SQLQueryImpl.list(SQLQueryImpl.java:175)^M
at org.hibernate.ejb.QueryImpl.getSingleResult(QueryImpl.java:88)^M
Caused by: java.sql.SQLException: ORA-02287: sequence number not allowed here

Categories: hibernate, java

Grails fails to start with cryptic error

May 25, 2009 3 comments

I ran into a coding error in a Grails (1.0.4, this does not appear to be an issue in 1.1.1) app recently that proved very hard to diagnose. It turned out to be a simple coding error, just hard to spot syntax.

After a simple refactor by someone else I was asked to help figure out why the app would no longer run. We got a error like:

Failed startup of context org.mortbay.jetty.webapp.WebAppContext@59139826{/ClosureTest,/Users/ahahn/devel/src/test/ClosureTest/web-app}
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'pluginManager' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Invocation of init method failed; nested exception is java.lang.RuntimeException: Unable to locate constructor with Class parameter for class org.codehaus.groovy.grails.commons.DefaultGrailsControllerClass
at java.security.AccessController.doPrivileged(Native Method)
at RunApp_groovy$_run_closure2_closure7.doCall(RunApp_groovy:67)
at RunApp_groovy$_run_closure2_closure7.doCall(RunApp_groovy)
at Init_groovy$_run_closure6.doCall(Init_groovy:131)
at RunApp_groovy$_run_closure2.doCall(RunApp_groovy:66)
at RunApp_groovy$_run_closure2.doCall(RunApp_groovy)
at RunApp_groovy$_run_closure1.doCall(RunApp_groovy:57)
at RunApp_groovy$_run_closure1.doCall(RunApp_groovy)
at gant.Gant.dispatch(Gant.groovy:271)
at gant.Gant.this$2$dispatch(Gant.groovy)
at gant.Gant.invokeMethod(Gant.groovy)
at gant.Gant.processTargets(Gant.groovy:436)
at gant.Gant.processArgs(Gant.groovy:372)
Caused by: java.lang.RuntimeException: Unable to locate constructor with Class parameter for class org.codehaus.groovy.grails.commons.DefaultGrailsControllerClass
... 13 more
Caused by: java.lang.reflect.InvocationTargetException
... 13 more
Caused by: org.codehaus.groovy.grails.exceptions.NewInstanceCreationException: Could not create a new instance of class [TestController]!
... 13 more
Caused by: java.lang.VerifyError: (class: TestController$_closure2, method: doCall signature: (Ljava/lang/Object;)Ljava/lang/Object;) Incompatible argument to function
at TestController.(TestController.groovy)
... 13 more
2009-05-25 11:01:25.026::WARN: Nested in org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'pluginManager' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Invocation of init method failed; nested exception is java.lang.RuntimeException: Unable to locate constructor with Class parameter for class org.codehaus.groovy.grails.commons.DefaultGrailsControllerClass:
java.lang.VerifyError: (class: TestController$_closure2, method: doCall signature: (Ljava/lang/Object;)Ljava/lang/Object;) Incompatible argument to function
at TestController.(TestController.groovy)
at java.security.AccessController.doPrivileged(Native Method)
at RunApp_groovy$_run_closure2_closure7.doCall(RunApp_groovy:67)
at RunApp_groovy$_run_closure2_closure7.doCall(RunApp_groovy)
at Init_groovy$_run_closure6.doCall(Init_groovy:131)
at RunApp_groovy$_run_closure2.doCall(RunApp_groovy:66)
at RunApp_groovy$_run_closure2.doCall(RunApp_groovy)
at RunApp_groovy$_run_closure1.doCall(RunApp_groovy:57)
at RunApp_groovy$_run_closure1.doCall(RunApp_groovy)
at gant.Gant.dispatch(Gant.groovy:271)
at gant.Gant.this$2$dispatch(Gant.groovy)
at gant.Gant.invokeMethod(Gant.groovy)
at gant.Gant.processTargets(Gant.groovy:436)
at gant.Gant.processArgs(Gant.groovy:372)

Which I could trace to the TestController code:

def list = Test.list( params )
 def testMaps = []
 if(list) {
   list.each {test ->
     if(test.bool) {
       testMaps << [
         foo: it.foo,
         bar: "Bar ${it.bar}"
       ]
     }
   }
}

Spot it yet? No really, spot it yet? It is simply a case of an incomplete refactor, the each closure was changed to have a named parameter ‘test’ but not all code in the body was refactored. Most of the references to the current object in the iteration still used the default parameter ‘it’. So what you would expect to be a run time error actual blows up at start up.

Just in case, here it the working code:

def list = Test.list( params )
 def testMaps = []
 if(list) {
   list.each {test ->
     if(test.bool) {
       testMaps << [
         foo: test.foo,
         bar: "Bar ${test.bar}"
       ]
     }
   }
}

Categories: grails, groovy Tags:

What jar is that friggin class in?!?!?!

March 21, 2009 Leave a comment

It seems to me that one of the most common tasks I have to preform when packaging a J2EE project is to find out what jar a class I depend on is in.

I have written (multiple times now) a simple perl script that finds all the jars in a given directory and scans them for a file. Thanks to the power of regular expressions you can pass in either com.foo.bar.Class or com/foo/bar/Class which ever is easier to cut-n-paste from the java.lang.NoClassDefFoundError, or similar, stack trace.

#!/usr/bin/perl

$JAR_COMMAND = "jar";
#Uncomment to use fastjar instead
#$JAR_COMMAND = "fastjar";

$class = "";
$lookin = ".";
if(@ARGV < 1) {
    print "USAGE: \n\tclassfinder [dir to search fo jars]
<packageName>\n";
    exit;
} elsif (@ARGV > 1) {
  $lookin = $ARGV[0];
  $class = $ARGV[1];
} else {
  $class = $ARGV[0];
}

foreach $jar (`find $lookin -name '*\.jar'`) {
    chomp $jar;
    foreach $file (`$JAR_COMMAND -tf $jar`) {
        chomp $file;
        if($file =~ $class) {
            print "$jar:$file\n"}
    }
}

The script relies on the standard unix utility find, it works on unix, linux, osx and cygwin. If you have an easy way to do this without using find I’d love to see it.

Minicom ymodem issue

This was a simple fix, but took a while to figure out because of less than informative error messages. While trying to transfer a file (a Linux kernel) using minicom and ymodem we got a protocol failed error when using one machine (my laptop) and it worked on another. After checking versions of minicom, serial adapter drivers, ymodem config, etc… we noticed that minicom is setup to use /usr/bin/rb and /usr/bin/sb for ymodem comm. Turns out, they weren’t installed! On Debian (*buntu) the lrzsz package is what is needed. A quick apt-get install lrzsz and we were up and running.

Gotta love cryptic error messages!

Categories: embedded, ubuntu Tags: , ,

Adventures in Grails – Running more than one version of your app

March 29, 2008 2 comments

Update: Posted a patch to the Grails JIRA http://jira.codehaus.org/browse/GRAILS-2771

Grails provides a simple versioning system for your application with the app.version property in application.properties. When you build a .war for deployment the file name is given by "${app.name}-${app.version}.war". For me this is great, I frequently need to run multiple versions of an app to support multiple client development branches. However, when I dropped myApp-0.2.war into tomcat’s webapp dir along side myApp-0.1.war the deployment failed. On tomcat 6.0.16 the error is cryptic and far from helpful SEVERE: Error listenerStart. Tomcat 5.5.26 provided a much more useful error message. It turns out that Grails only includes the version number in the .war file name and not in web.xml where it uses only the app name.

To fix this all you need to do is edit the web.xml of one of the versions and replace the app name with something else. Well, thats not very Groovy! To have Grails do this for you it is a simple change to the Package.groovy script that ships with Grails.

The lines (264-266):


Ant.copy(file:"${grailsHome}/src/war/WEB-INF/web${servletVersion}.template.xml", tofile:tmpWebXml)

                Ant.replace(file:tmpWebXml, token:"@grails.project.key@", value:"${grailsAppName}")


get changed to:

Ant.copy(file:"${grailsHome}/src/war/WEB-INF/web${servletVersion}.template.xml", tofile:tmpWebXml, overwrite:true)

Ant.replace(file:tmpWebXml, token:"@grails.project.key@", value:"${grailsAppName}-${grailsAppVersion}")


The overwrite:true in the copy task is necessary so that when the version is changed, web.xml is updated. This will break you if you rely on hand made changes to web.tmp.xml if your ~/.grails directory. Though, you might not want to do things that way anyway.On a side note, you may want to define more environments in grails-app/config/DataSources.groovy if you have changed your app’s DB schema to prevent errors.

Categories: grails, groovy, programming Tags: ,

MySQL install fails on Ubuntu with static IP

March 12, 2008 2 comments

Update:

It was not the static IP address, but the fact that due to a typo in /etc/network/interfaces the loopback device was not active.

Installing MySQL server on Ubuntu fails if the machine has a static IP address.

sudo apt-get install mysql-server

Results in:

Setting up mysql-server-5.0 (5.0.45-1ubuntu3.1) ...
* Stopping MySQL database server mysqld
...done.
* Starting MySQL database server mysqld
...fail!
invoke-rc.d: initscript mysql, action "start" failed.
dpkg: error processing mysql-server-5.0 (--configure):
subprocess post-installation script returned error exit status 1
dpkg: dependency problems prevent configuration of mysql-server:
mysql-server depends on mysql-server-5.0; however:
Package mysql-server-5.0 is not configured yet.
dpkg: error processing mysql-server (--configure):
dependency problems - leaving unconfigured
Processing triggers for libc6 ...
ldconfig deferred processing now taking place
Errors were encountered while processing:
mysql-server-5.0
mysql-server
E: Sub-process /usr/bin/dpkg returned an error code (1)

To fix this edit /etc/mysql/my.conf and change the bind-address to the static address of the machine. Then rerun apt-get to finish the configuration of the packages.

sudo apt-get -f install

Reported to Ubuntu launchpad

Follow

Get every new post delivered to your Inbox.