For a customer project there was the requirement to make certain parts of the application readonly depending on the state of a central domain object, which I’ll call Lock. Long story short:
- an implementation of AbstractPersistenceEventListener intercepts persistence events and throws a custom exception if changes are forbidden
- because the event listener is not able to access database state (because it’s triggered halfway through hibernate doing its saving), the necessary data is preloaded in a request filter
- a custom exception page for the specific exception displays a user-friendly message using information delivered by the exception object
The main part is the src/groovy/LockCheckPersistenceListener.groovy that intercepts persistence events.
There’s one catch – it only kicks in on domain object operations. If you’re using executeUpdate statements, you’ll have to check these in a different way (if there’s not many of them, perform checks at the individual places).
/**
* For details, see http://grails.org/doc/latest/guide/GORM.html#eventsAutoTimestamping
*/
class LockCheckPersistenceListener extends AbstractPersistenceEventListener {
public LockCheckPersistenceListener(final Datastore datastore) {
super(datastore)
}
@Override
protected void onPersistenceEvent(final AbstractPersistenceEvent event) {
if (!(event.entityObject instanceof Lock)) {
switch(event.eventType) {
case EventType.PreInsert:
case EventType.PreUpdate:
case EventType.PreDelete:
performReadonlyCheck();
break;
}
}
}
private performReadonlyCheck() {
//you'd better not perform database access where - you'll get Hibernate's StaleObjectException and
//'org.hibernate.AssertionFailure null id in XYZ' when accessing the database in any way
def lock = RequestContextHolder.currentRequestAttributes().getAttribute("activeLock", RequestAttributes.SCOPE_REQUEST);
if (lock.islocked) {
throw new LockException("lock.locked")
}
}
@Override
public boolean supportsEventType(Class
The persistence listener needs to be registered in Bootstrap.groovy’s init closure as follows
grailsApplication.mainContext.eventTriggeringInterceptor.datastores.each { k, datastore ->
grailsApplication.mainContext.addApplicationListener new LockCheckPersistenceListener(datastore)
}
Within the PersistenceListener, database access is not advisable as it leads to various Hibernate exception. Because the information about the lock needs to be retrieved database nevertheless, the relevant domain object is pre-loaded in a Grails filter that stores the object in request scope for later use.
The filter goes to grails-app/conf/lock/LockFilters.groovy
class LockFilters {
def filters = {
all(controller:'*', action:'*') {
before = {
//Store the active lock for later use (needed by LockCheckPersistenceListener which can't perform db access from within a hibernate listener
RequestContextHolder.currentRequestAttributes().setAttribute("activeLock", Lock.findByLocked(true), RequestAttributes.SCOPE_REQUEST)
}
}
}
}
In UrlMappings.groovy, you register a custom exception page for your exception so you can display an appropriate message.
"500"(controller: 'lock', action: 'lockedException', exception: LockException)
The controller action for the exception page is trivial (an empty closure).
The only noteworthy part of the custom exception gsp page is the retrieval of the exception, where in this case, the custom exception has a reason property that is used for a i18n message key.
(...snip..)
<h3>${message(code: request.exception.cause.reason)}</h3>
(...snip...)
This way, you get a neat page that is shown whenever a user tries to change anything while he isn’t allowed to.