Thursday, June 11, 2015

Grails 2.5.0 - Disabling autoTimestamp during Integration Tests

At some point between Grails 2.2 and 2.5, the functionality around the autoTimestamp flag has changed in a way that has caused it to not function during integration tests.
What this means is that if you are testing integrations that involve the creation of a domain that has the autoTimestamp flag, you may be hit with not-nullable errors:
org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value : com.your.package.YourDomain.dateCreated; nested exception is org.hibernate.PropertyValueException: not-null property references a null or transient value : com.your.package.YourDomain.dateCreated
Obviously the reason for this is that the dateCreated (and lastUpdated if you use it) field is not being set in the way that it would if the application was running.
If you are unable to manually add these in your test, (e.g. in my case the object was being created and saved by a service that was under test - no chance to mock or modify before the save), you might look to somehow disabling the autoTimeout and setting the default value
An answer lies in Bootstrap.groovy, where we can catch the grails context and modify the domain class before the tests are executed.
Note that if you're developing a plugin, you wouldn't normally have a Bootstrap.groovy file, since its unlikely you're interacting with a database. But you can create it manually, and it won't get packaged up in your plugin build.

Bootstrap

In your Bootstrap.groovy, you'll need the following function. This will disable the autoTimestamp, and set the dateCreated and lastUpdated fields to the current date:
def disableAutoTimestamp(Class domClass) {
    def grailsSave = domClass.metaClass.pickMethod('save', [Map] as Class[])

    domClass.metaClass.save = { Map params ->

        def m = new GrailsDomainBinder().getMapping(delegate.getDomainClass())
        println("Disabling autoTimestamp for ${domClass.name}")

        if (m?.autoTimestamp) m.autoTimestamp = false

        def colList = []
        domClass.declaredFields.each {
            if (!it.synthetic)
                colList.add(it.name.toString())
        }
        if (colList.contains("dateCreated")) {
            println "Setting ${domClass.simpleName}.dateCreated to now"
            domClass.metaClass.setProperty(delegate, "dateCreated", new Date(System.currentTimeMillis()))   // Update this when using Java8
        }

        if (colList.contains("lastUpdated")) {
            println "Setting ${domClass.simpleName}.lastUpdated to now"
            domClass.metaClass.setProperty(delegate, "lastUpdated", new Date(System.currentTimeMillis()))
        }
        grailsSave.invoke(delegate, [params] as Object[])
    }
}
Note that prior to grails 2.4, the GrailsDomainBinder().getMapping() was static.
The stackoverflow question that I based this code on iterated through all classes with:
grailsApplication.domainClasses.each { gdc ->
    def domClass = gdc.clazz
    ...
}
I found that this didn't work when proccessing all domains, so I specify them explicitly.
The rest of the Bootstrap.groovy looks like this (Note the conditional check for Environment - Very important!)
import grails.util.Environment
import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsDomainBinder

class BootStrap {

    def init = { servletContext ->
        if (Environment.current == Environment.TEST) {
            disableAutoTimestamp(YourDomain.class)
        }
    }

    def disableAutoTimestamp(Class domClass) {
        ...
    }
}

References

Wednesday, October 8, 2014

How to use Tomcat 7 Host Manager

Tomcat Host Manager is a web application inside of Tomcat that manages Virtual Hosts within the Tomcat application server.
Virtual Host allows you to define multiple hostnames on a single server, so you can use the same server to handles requests to different subdomains, for example, ren.myserver.com and stimpy.myserver.com.
Unfortunately documentation on the GUI side of the Host Manager doesn't appear to exist, but documentation on configuring the virtual hosts manually in context.xml is here:
The full explanation of the Host parameters you can find here:

Accessing the Virtual Host Manager

To access the virtual host manager, you need to have appropriate permissions set up in your tomcat-users.xml - at the very least the manager-gui role (see the official documentation here for full information). It should look something like this:
<role rolename="manager-gui"/>
  <user username="tomcat-admin" password="change-me!" roles="manager-gui"/>
</role>
Once you have access, you can access the host-manager application at http://localhost:8080/host-manager

Adding a new virtual host

The Add Virtual Host panel looks like this:
Tomcat Virtual Host Manager
At a minimum you need the Name and App Base fields defined. From those, Tomcat will create the following directories:
{CATALINA_HOME}\conf\Catalina\{Name}
{CATALINA_HOME}\{App Base}
App Base will be where web applications will be deployed to the virtual host. Can be relative or absolute.
Name is usually the fully-qualified domain name (e.g. ren.myserver.com)
Alias can be used to extend the Name also where two addresses should resolve to the same host (e.g. www.ren.myserver.com). Note that this needs to be reflected in DNS records.
I'll let you look up the other manual parameters, but I find the most useful ones are:
Unpack WARs: Unpack WAR files placed or uploaded to the App Base, as opposed to running them directly from the WAR.
Auto Deploy: Automatically redeploy applications placed into App Base. Careful - this is potentially dangerous for Production environments!
Deploy On Startup: Automatically boot up applications under App Base when Tomcat starts
Copy XML: Copy an application's META-INF/context.xml to the App Base/XML Base on deployment, and use that exclusively, regardless of whether the application is updated. Irrelevant if Deploy XML is false (Tomcat 8 only).
Deploy XML: Determines whether to parse the application's /META-INF/context.xml
Manager App: Add the manager application to the Virtual Host (Useful for controlling the applications you might have underneath ren.myserver.com). You would then access this at http://{Name}/manager
Once you hit Add, Tomcat will create the directories above and move in the tomcat-manager app config under {CATALINA_HOME}\conf\Catalina\{Name}. You should be able to hit that straight away!

Now, a warning:


You need to add your virtual-host entry to Tomcat's server.xml for it to persist after a restart! (See the documentation link at the start of the article for how to do this)
For who-knows-what reason, the virtual-host entry you add via the GUI isn't written to disk. So, you may ask yourself, why don't I just add the entry into server.xml instead of trying to use the GUI? Good question.. 

Originally posted by myself as an answer on stackoverflow.com.

Monday, November 4, 2013

jQuery plugin for collapsing content - readmore.js

If you're looking for a simple method to collapse and expand blocks of HTML content, I highly recommend the jQuery plugin readmore.js by Jed Foster.

Highly customisable, and implemented with a single function call against any id, class or element, e.g.:
$('p').readmore();
All CSS, more/less links and animation is taken care of automatically!

One thing to pay attention to however, is the height of the collapsed text, as a mis-alignment can cause text lines to be chopped in half horizontally. This is fixed by tweaking the maxHeight parameter, or alternatively you might be able to do something fancy with CSS to pretty it up.

Another tweak to consider is replacing the default 'read more' text with an image or button.

Project page: http://jedfoster.com/Readmore.js
Github: https://github.com/jedfoster/Readmore.js