Friday, October 19, 2018

Navigate safely with null-safe operator for Maps in Groovy . . .

It is not overstating to say that, NullPointerException (known as NPE), in Java is annoying and dreaded. ;) So much of noisy if conditional check code-blocks get added to main logic simply as a way to avoid it safely. Missing these safety if checks will result into run-time NullPointerException indicating a bug. Java doesn't provide any elegant syntax to deal with annoyance.

Groovy offered null-safe operator ( ?. ) right from the beginning. Recent JVM languages started offering ways to deal with it safely with which Java developers are falling in love. But, what Groovy offers with null-safe operator ( ?. ), is the most elegant syntax for safely navigating an object graph.

Groovy also offers consistent way of referring element in a collection with subscript operator ( [] ). The subscript operator is a short-hand notation for groovy's added getAt() method for collections in addition to Java provided get() method. Accessing elements of Arrays, Lists, Sets and Maps is consistent with subscript operator.

This safe navigation operator can't be used with collections to access elements safely with subscript notation. For instance, myList[0] to get first element from the list throws NPE when myList variable itself is null. Whereas, directly using the underlying Groovy provided method, myList?.getAt(0) is safer way to access first element without causing dreaded NPE. Java provided get(n) also can be used safely with ?. only for Arrays and Lists, but beware that it throws run-time IndexOutOfBoundsException if subscript index goes beyond the size, where Groovy's getAt(n) returns null in this case.

However, for Maps, Groovy offers additional notation (property notation) to reference key to get it's value. Unlike, subscript notation, this additional notation offers null-safety.

Environment: Groovy 2.4.15Java 8 on MacOS High Sierra 10.13.6

Example code

Map nullMap = null // calling getAt() method is null-safe anyway, as it is groovy's way of safely navigating object graph assert nullMap?.getAt('name') == null /* * Notation-1: Subscript notation * - Null-unsafe way of referencing key * - Throws NullPointerException */ try { assert nullMap['name'] == null // key literal } catch(NullPointerException npe) { assert npe.message == "Cannot get property 'name' on null object" } String key = 'name' try { assert nullMap[(key)] == null // when value of variable is the key } catch(NullPointerException npe) { assert npe.message == "Cannot get property 'name' on null object" } /* * Notation-2: Property notation * Null-safe way of referencing key * Doesn't throw NullPointerException */ assert nullMap?.name == null // key literal assert nullMap?."$key" == null // when value of variable is the key

Summary

So, for Maps, there are 3 options, where String ABCD is the key:
  • Subscript notation ( myMap['ABCD'] ) - can't use null-safe operator and hence can get NPE
  • Use underlying method directly ( myMap?.getAt('ABCD') ) - can use null-safe operator but is verbose
  • Property notation ( myMap?.ABCD) - can use null-safe operator and is more readable.
I got into this trap by using subscript operator ( [] ) few times in the past, and hence sharing it in this post.

Sunday, July 15, 2018

Add Custom Scope to a Grails 2 Application . . .

Grails services are Spring managed singleton beans by default. Singleton is one of the five different scopes (singleton, prototype, request, session, and globalSession) that Spring framework offers for managed beans. Prior to Spring 2.5, there were only 2 standard scopes: singleton and prototype. Spring 2.5 added 3 additional scopes: requestsession and globalSession for use in web-based applications. Grails adds two more scopes to the mix: flow and flash.

Sometimes, you might run into a situation that none of these scopes meet your requirements. For instance, when you have a multi-tenant or multi-client application, what you may need is a client scope, a separate bean instance for each client. Recently, I ran into this situation. Addressing an existing performance issue drove me into this situation, making an use case for a custom scope.

We have a Grails 2.5.4 multi-client application with clientId included in URL mappings for end-points like: /clients/$clientId/resource. There has been a performance issue with one of the end-points backed by a service. The service has a heavy-weight method which builds and caches client data once for each client with an expiration time set to expire cache few hours once built. It takes few minutes to build this data due to it's nature and some data rule complexities. Once built and cached, it rebuilds data from the cache really fast. One of the performance improvements identified upfront was to limit concurrent calls to that method. Obviously, there is no point in allowing concurrent builds for the same client. The solution to put in place was to allow one-and-only-one concurrent call for any given client, but allow concurrent calls for different clients one per each client. Making the method synchronized is an easy way to limit concurrent calls, but it only solves half of the problem. As service is a singleton bean and synchronized method uses this as the object lock. With that, concurrent calls get executed serially, across all clients. But, we need to allow concurrent executions for different clients, but not for the same client. It is still possible to achieve this with synchronized method, but only when there is one service instance per client. This opened the need for a custom scope. Since Spring 2.0, the concept of scoping beans is made extensible and is the way to add custom scope.

Spring maintains a cache of all scoped beans in it's container. Every scope except prototype has it's own in-memory cache of bean instances which is initialized & populated when the application gets started and maintained by spring container. All spring managed beans either get instantiated or get proxies created during the application startup. Obviously, prototype scoped beans don't need any kind of cache as they get created and injected into all beans that are auto-wired with Singleton beans. A Grails service being singleton bean is also stateless and hence it's methods can be executed by multiple concurrent threads.

Spring has a good documentation of all these details and the API is well-documented as well. For creating a custom scope, all we need to do is: 1) Create a custom Scope object by implementing org.springframework.beans.factory.config.Scope interface and 2) Register custom scope with Spring container. 3) Scope required beans at this custom scope. Sounds simple, in a Grails application it should even be simpler. Lets get through step-by-step implementation details of adding a new custom scope: Client scope to a Grails 2 application.

Environment: Grails 2.5.4, Spring 4.1.8, Java 8 on MacOS High Sierra 10.13.5

Step-1 Implement custom Scope

This is straight forward. Just implement the interface and provide implementation for all essential methods. Remember, the implementation should also maintain it's own cache for this custom scoped beans. Also, make sure that any needed scoped context (in this case, it is clientId) is available and accessible in this implementation when a reference to the bean scoped at this custom scope is needed.

src/groovy/com/giri/grails/scope/ClientScope.groovy
package com.giri.grails.scope import grails.plugin.springsecurity.SpringSecurityService import grails.util.Holders import groovy.util.logging.Log4j import org.springframework.beans.factory.ObjectFactory import org.springframework.beans.factory.config.Scope /** * Custom scope bean for client-scoped services registered in resources.groovy. * * All services(Spring beans) that need client-scope should define static property of scope like: * static scope = ClientScope.SCOPE_NAME * * @see resources.groovy * * @author gpottepalem * Created on July 15, 2018 */ @Log4j class ClientScope implements Scope { static final String SCOPE_NAME = 'clientScope' /** * Client scoped bean store. * A synchronized multi-thread-safe map of various beans scoped with {@link ClientScope#SCOPE_NAME} * Spring framework depends on this store for maintaining beans defined with this custom client-scope. * e.g. Two clients with one common service and two different services * [ client-1 : [ * 'ClientService' : clientServiceObjectRef, * 'OtherService' : otherServiceObjectRef * ], * client-2 : [ * 'ClientService' : clientServiceObjectRef, * 'SomeOtherService' : someOtherServiceObjectRef * ] * ] */ private Map<Integer, Map<String, Object<?> clientScopedBeansMap = [:].asSynchronized() /** * Helper method, returns client-scoped beans for a given client. * @param clientId the client id * @return A map of client-scoped beans for the given client. */ private Map<String Object> getClientScopedBeans(Integer clientId) { if(!clientScopedBeansMap[clientId]) { clientScopedBeansMap[clientId] = [:].asSynchronized() log.debug "No client scoped bean found for client:$clientId, just created new map" } return clientScopedBeansMap[clientId] } /** * Helper method, returns clientId taking it from authenticated user. * @return clientId of the current user logged in */ private Integer getClientId() { (Holders.grailsApplication.mainContext.getBean('springSecurityService') as SpringSecurityService).authentication?.clientId } @Override Object get(String name, ObjectFactory<?> objectFactory) { synchronized (this) { Integer clientId = getClientId() Map<String Object> clientScopedBeans = getClientScopedBeans(clientId) if (!clientScopedBeans[name]) { clientScopedBeans[name] = objectFactory.object log.debug "Added new instance: ${clientScopedBeans[name]} for bean: $name for client:$clientId to the bean store" } return clientScopedBeans[name] } } @Override Object remove(String name) { Map<String Object> scopedBeanMap = getClientScopedBeans(getClientId()) return scopedBeanMap.remove(name) } @Override void registerDestructionCallback(String s, Runnable runnable) { // nothing to register } @Override Object resolveContextualObject(String s) { return null } @Override String getConversationId() { return SCOPE_NAME } }

Step-2 Register custom scope

Register custom scope in:
grails-app/conf/spring/resources.groovy
import com.giri.grails.scope.ClientScope import org.springframework.beans.factory.config.CustomScopeConfigurer beans = { ... // Custom scope: per-client clientScope(ClientScope) // register all custom scopes customScopeConfigurer(CustomScopeConfigurer) { scopes = [(ClientScope.SCOPE_NAME) : ref('clientScope')].asImmutable() } ... }

Step-3 Scope a client-specific service with custom scope

Say, we have a service ClientService that we need to scope at clientScope. Just define a static scope property set with this custom scope like:
grails-app/services/com/giri/ClientService.groovy
package com.giri import com.giri.grails.scope.ClientScope class ClientService { static scope = ClientScope.SCOPE_NAME def clientDataBuilderService //DI //delegates to clientDataBuilderService synchronized Map buildClientData(String clientId) { clientDataBuilderService.buildData(clientId) } ... }

Step-4 Custom-scoped service - Dependency Injection

Grails supports Spring Dependency Injection by convention. A property name that matches the class name of a Spring managed bean gets injected automatically. Unlike Spring applications, you don't need @Autowired annotation on a property or a setter method. But for custom scoped beans, the actual custom scoped bean might get instantiated lazily when the client context (in this case, clientId) is available in the application (either taken from the request, session, security authentication etc.). This context is not known at the start of the application. So, without creating proxies, dependency injection may not be possible. This requires the scoped bean to be programmatically resolved, unlike auto-wired by Grails convention. The following is a way to get a handle to the scoped bean instance. The assumption here is, that the context needed for creating a scoped bean for specific client (clientId) is available in the security context and all end-points are secured.

grails-app/controllers/com/giri/ClientController.groovy
package com.giri import grails.converters.JSON import grails.util.Holders class ClientController { ... def index(String clientId) { ClientService clientService = Holders.grailsApplication.mainContext.getBean('clientService', ClientService.class) clientService.buildClientData(clientId) as JSON } ... }

TIP: Unit Testing

Without scoped bean dependency injected by following Grails naming convention for DI and by referring it using Grails ApplicationContext puts us into a limitation in unit tests. The actual scoped bean instance is needed which can otherwise be mocked if it was injected. Typically, Grails doesn't load bean definitions as the complete ApplicationContext is not needed in unit-tests. The following are two options that Grails offers to get away with this and have bean definitions loaded and ApplicationContext available for unit-tests:

Option-1
test/unit/com/giri/ClientControllerSpec.groovy
package com.giri import grails.test.mixin.TestFor import spock.lang.Specification @TestFor(ClientController) class ClientControllerSpec extends Specification { static loadExternalBeans = true //loads beans defined in resources.groovy and beans are available in applicationConext ClientService clientService def setup() { //controller.clientService = Mock(ClientService) //doesn't work clientService = applicationContext.getBean('clientService') clientService.clientDataBuilderService = Mock(ClientDataBuilderService) } void "test index"() { when: controller.index('client-1') then: (1.._) * clientService.clientDataBuilderService.buildData('client-1') >> ['abcd' : 1234] and: response.json == [ "abcd": 1234 ] } }

Option-2
package com.giri import grails.test.mixin.TestFor import spock.lang.Specification @TestFor(ClientController) class ClientControllerSpec extends Specification { ClientService clientService def setup() { //controller.clientService = Mock(ClientService) //doesn't work //define bean to get into applicationContext as it is not injected to mock it out defineBeans { clientService(ClientService) } clientService = applicationContext.getBean('clientService') clientService.clientDataBuilderService = Mock(ClientDataBuilderService) } void "test index"() { when: controller.index('client-1') then: (1.._) * clientService.clientDataBuilderService.buildData('client-1') >> ['abcd' : 1234] and: response.json == [ "abcd": 1234 ] } }

Summary

To add custom scope to a Grails Application, all you need to know is some Spring Framework details and Grails integration with Spring. There is still one improvement that can be made to this solution, getting scoped beans injected by following Grails convention. This particular use-case requires client context (clientId) to be available for scoped bean cache maintenance. This makes the case for a proxy to be generated for this custom-scoped beans. A proxy bean needs to be generated and injected for scoped bean at the start of application. The proxy bean should be able to retrieve the actual target bean from the scoped cache and delegate method calls to that target object. This might simply need some additional Spring configurations, I guess. I left it out for now, to explore later.

References

Wednesday, June 27, 2018

Log exceptions with exceptional details . . .

Logging is generally considered a cross-cutting concern. So as logging exceptions is. An appropriate level of details added to exception log is always very helpful and gives a jump-start in investigating the root cause of an exception.

Grails provides good exception logging, filters out unnecessary stack traces (configurable by setting grails.full.stacktrace property) by default, that otherwise would be too long. It also provides configurable way of adding additional details to the exception log message. Additional details like request parameters can be added to exception log message (by setting the property  grails.exceptionresolver.logRequestParameters = true in Config.groovy) which is by default enabled in only development env). Also, certain sensible request parameters and values can be masked from exception logging by setting an additional property grails.exceptionresolver.params.exclude = ['password']).

In a Web or RESTful API application, request parameters added to exception log message cover all http methods like GET that send parameters as part of the query string. However, http methods like POST typically send parameters in the request body, but not in the query string. Logging request body along with exception message is as useful as logging request parameters. Grails doesn't provide a configurable way of adding this level of details to exception log message. But this can easily be added with minimal coding, configuration and by leveraging the underlying Spring framework.

Environment: Grails 2.5.4, Java 8 on MacOS High Sierra 10.13.5

Imagine, a Grails application providing various RESTful end-points that take JSON payload as the body in POST requests. If some exception arises while processing a request, an exception log that includes request payload received will be very helpful to investigate the root cause. This requires accessing the original http request body in an exception resolver at the time when the exception is raised. Grails default exception resolver class is: GrailsExceptionResolver. This class is extendable. All we need to do is override one of it's methods that forms the exception log message.

Extend GrailsExceptionResolver and override getRequestLogMessage(Throwable e, HttpServletRequest request). This method already has HttpServletRequest parameter. So, getting access to request body is possible, but it is not available. The problem is, by this time, the request body must have already been read either by calling getInputStream() or getReader() method in order to consume and process the JSON payload. Once read, it's not available anymore and subsequent reads only result in an exception: IllegalStateException. The only way to deal with this limitation is to wrap the original HttpServletRequest into a wrapper HttpServletRequestWrapper, cache it and pass it along the filter chain by making cached request available for multi-reads.

This is bit low-level to dive into in a Grails application. Grails 2 offers a high-level Filters support but it has limitations to use in this solution. So, we have to dive little deeper and put a solution by introducing a Servlet filter that caches and wraps the original request to make it available for multi-reads. But, remember Grails underpins Spring framework. So, it's always a layer to look into for customizations like this and see if there is anything readily available for adopting. In this case, there is a filter org.springframework.web.filter.CommonsRequestLoggingFilter which wraps the request into  org.springframework.web.util.ContentCachingRequestWrapper and is just appropriate for this solution to leverage.

Now, we have all pieces of the puzzle. Let's put these together into a solution.

Step-1

Extend GrailsExceptionResolver and overwrite getRequestLogMessage(Throwable e, HttpServletRequest request)method. The following is an example of extended exception resolver:
src/groovy/grails/logging/LogRequestBodyExceptionResolver.groovy
package com.giri.grails.logging import com.giri.grails.web.RequestCacheFilterUtil import org.codehaus.groovy.grails.web.errors.GrailsExceptionResolver import org.springframework.http.HttpMethod import org.springframework.http.MediaType import javax.servlet.http.HttpServletRequest /** * Exception resolver, adds request body to exception log message by getting it from the request body cached by * {@link org.springframework.web.util.ContentCachingRequestWrapper} * * @see {@link org.springframework.web.util.ContentCachingRequestWrapper} * @see /src/templates/war/web.xml * @see /conf/spring/resources.groovy * * @author gpottepalem * Created on Jun 27, 2018 */ class LogRequestBodyExceptionResolver extends GrailsExceptionResolver { static final List LOG_PAYLOAD_FOR_HTTP_METHODS = [HttpMethod.POST, HttpMethod.PUT]*.name() static final List LOG_PAYLOAD_FOR_HTTP_CONTENT_TYPES = [MediaType.APPLICATION_JSON_VALUE] /** * Enhances Grails log message for logging exceptions by adding the original request payload to the exception * message * @param e the exception * @param request request * @return enhanced log message that includes request payload (body) */ @Override String getRequestLogMessage(Throwable e, HttpServletRequest request) { String logMessage = super.getRequestLogMessage(e, request) String payload = RequestCacheFilterUtil.getCachedRequestPayload(request) if (request.method in LOG_PAYLOAD_FOR_HTTP_METHODS && request.contentType in LOG_PAYLOAD_FOR_HTTP_CONTENT_TYPES) { logMessage += "\n${request.method} ${request.contentType} payload: ${payload}\n" } return logMessage } }

Here is the utility class that the above code snippet uses:
src/groovy/grails/web/RequestCacheFilterUtil.groovy
package com.giri.grails.web import org.springframework.web.util.ContentCachingRequestWrapper import org.springframework.web.util.WebUtils import javax.servlet.http.HttpServletRequest /** * Utility class. Provide methods for working on the cached request payload. The original request once per * request is cached by {@link ContentCachingRequestWrapper} in the filter * {@link org.springframework.web.filter.CommonsRequestLoggingFilter} setup in web.xml * * @see /src/templates/war/web.xml * * @author gpottepalem * Created on Jun 27, 2018 */ class RequestCacheFilterUtil { /** * Utility method to get payload of the cached request. * * @param request the request * @return payload as String * @throws UnsupportedEncodingException */ static String getCachedRequestPayload(final HttpServletRequest request) throws UnsupportedEncodingException { String payload ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class) if (wrapper) { byte[] buffer = wrapper.getContentAsByteArray() if (buffer) { payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding()) } } return payload } }

Once we have extended Grails provided GrailsExceptionResolver class as shown above, we need to override Grails provided exception resolver and register this as our application's exception handler. A bean definition in resources.groovy like the following will exactly do that:
grails-app/conf/spring/resources.goovy
beans = { ... // custom exception handler to log request body in addition to log message for exceptions exceptionHandler(LogRequestBodyExceptionResolver) { exceptionMappings = [ 'java.lang.Exception': '/error' ] } }

That is one side of this problem which takes care of logging request body along with the exception message for exceptions.

Step-2

The other side of the problem is to put a solution to make request body available for multiple reads. Typically once getInputStream() or getReader() is called to consume request body from  HttpServletRequest the next call will throw an exception preventing it to call multiple times. This needs a way to cache the request and wrap it using HttpServletRequestWrapper to make it's body readable multiple times. Fortunately Spring comes with ContentCachingRequestWrapper that exactly does this. But this needs to be done in a filter to wrap the request. Grails filters will not let this level of customization with request or response objects available in filters. This needs a servlet filter configuration. Spring comes with few handy implementations of filters that use ContentCachingRequestWrapper in them. One such useful filter class is: CommonsRequestLoggingFilter that allows request customizations. This filter can be setup in web.xml as a servlet filter that in-turn caches and wraps HttpServletRequest.

A typical Grails application doesn't include web.xml as it gets created when the application war file is built. In order to get this filter definition into the generated web.xml that gets bundled into war file, we need to install Grails templates. These are the templates that Grails framework uses for code generations. For customizing any code generation, we need to install and make changes to the corresponding template, in this case templates/war/web.xml file is the one we add a filter into.

Running the command: grails install-templates, installs all templates that Grails framework itself uses to generate code under src/templates dir. For this solution, we don't need all installed template files. We can delete all except web/web.xml once templates.

Edit war/web.xml and add the following filter entry:
... <!-- ================ Custom filters BEGIN ================ --> <!-- Filter provides org.springframework.web.util.ContentCachingRequestWrapper which in turn caches and makes request body multi-readable. We leverage the cached request and access request body in LogRequestBodyExceptionResolver for logging enhanced exception message that includes request payload. --> <filter> <filter-name>commonsRequestLoggingFilter</filter-name> <filter-class>org.springframework.web.filter.CommonsRequestLoggingFilter</filter-class> <init-param> <param-name>includePayload</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>includeClientInfo</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>includeQueryString</param-name> <param-value>true</param-value> </init-param> </filter> <!-- URL patterns the filter is applied to --> <filter-mapping> <filter-name>commonsRequestLoggingFilter</filter-name> <url-pattern>/sites/*</url-pattern> <url-pattern>/api/*</url-pattern> <url-pattern>/admin/*</url-pattern> </filter-mapping> <!-- ================ Custom Filters END ================= --> ...

This makes every request go through this filter for listed URL patterns.

With this, we have enhanced Grails provided exception resolver that adds request body to exception log message which gets logged when an exception arises while processing a http request that includes body.

TIP

As this solution leverages Spring provided request logging filter that offers logging request parameters, body and client-info of every request for debug level when enabled, we can as well have debug log level enabled for this class in development in Config.groovy:

log4j = { environments { development { ... debug 'org.springframework.web.filter.CommonsRequestLoggingFilter' ... } } }

This will log every request that goes through this filter with extra enabled details in debug mode.

References


  • Grails Filters Doc
  • Grails install-templates command
  • Grails Goodness: Exception Methods in Controllers
  • Logging http request and response with Spring 4


  • Sunday, January 28, 2018

    Add time-zone sense . . .

    As today's applications run from the Cloud hardware and serve requests coming in from time-zones spread across the globe, date-time-zone handling becomes more sensitive than ever. Applications need to be designed and built with this sensible capability and need to have not just Date-Time sense but also added Zone sense.

    Recently, I had to work on adding Timestamp and Zone capability to a Grails3 application. The initial implementation & business-logic around a couple of Date-Time sensitive fields was written just to track Date in MM/dd/yyyy format using java.util.Date. The new requirement was to capture date, timestamp and timezone. Sounds simple, but not really ;). It actually involved a bit of exploration on many fronts including: Java, Hibernate, Grails and PostgreSQL database.

    Environment: Grails 3.2.3, Java 8, PostgreSQL on MacOS High Sierra 10.13.2

    First Steps - Towards the right direction (Java 8 Date-Time classes)

    Java 8 Date Time classes

    I did some first-hand reading on Java 8 date-time classes from java.time package and an up-front research on the support for these classes in PostgreaSQL, Hibernate, Spring Boot and Grails.

    Java 8 (at last) added fluent date-time-zone API classes that are immutable, thread-safe and based on ISO 8601 calendar. I looked into details of few classes like: Instant, LocalDateTime, ZonedDateTime and OffsetDateTime. From Java docs, OffsetDateTime is the class recommended for modeling date-time concepts in more detail and when communicating with database/networking. After experimenting little bit all these classes, without further exploration I chose java.time.OffsetDateTime class over java.util.Date.

    java.time.Instant - an instantaneous point on the time line in UTC zone
    e.g 2018-01-21T12:21:12.122Z indicating Jan 21, 2018 12:21 pm 12 seconds and 122 milliseconds, Z stands for UTC timezone)

    java.time.LocalDateTime - date-time with no timezone information
    e.g. 2018-01-21T07:21:12.122 indicating Sun Jan 21, 2018 07:21 am 12 seconds and 122 milliseconds with no timezone, as my system clock is set to EST, this is the time in EST America/New_York zone

    java.time.ZonedDateTime - current date time in the current timezone with offset from UTC
    e.g. 2018-01-21T07:21:12.122-05:00[America/New_York] indicating Sun Jan 21, 2018 07:21 am 12 seconds and 122 milliseconds with timezone offset and timezone. As my system clock is set to EST, this is the time in EST America/New_York zone. It's more like LocalDateTime with offset from UTC timezone but with local timezone)

    java.time.OffsetDateTime - current date time in the current timezone with offset from UTC
    e.g. 2018-01-21T07:21:12.122-05:00 indicating Sun Jan 21, 2018 07:21 am 12 seconds and 122 milliseconds with timezone offset. As my system clock is set to EST, this is the time in EST with -05:00 offset that indicates the zone. It's more like LocalDateTime with offset from UTC timezone. Offset indicates the zone.)

    Other useful classes

    Next Steps - Along the path (PostgreSQL)

    PostgreSQL

    It is quite common to leverage Grails Database Migration Plugin in a Grails application to manage database changes. When model changes, running the plugin provided dbm-gorm-diff command generates schema changes as a groovy script. For java.util.Date and java.time Date classes the plugin generated change-log script will have column types set to: TIMESTAMP WITHOUT TIME ZONE for PostgreSQL.

    For instance, if a domain class had two new properties:
    Agreement.groovy
    class Agreement { ... Date signedDate Date expirationDate ... }

    Then running dbm-gorm-diff
    e.g. grails -Dgrails.env=development dbm-gorm-diff agreement-changes.groovy

    would have change-log script generated for the model changes as follows:
    databaseChangeLog = { changeSet(author: "Gpottepalem (generated)", id: "1234567890123-1") { createTable(tableName: "agreement") { ... column(name: "signed_date", type: "TIMESTAMP WITHOUT TIME ZONE") { constraints(nullable: "false") } column(name: "expiration_date", type: "TIMESTAMP WITHOUT TIME ZONE") { constraints(nullable: "false") } ... } } }

    That clearly indicates NO time-zone details are stored in the database.

    Move on - Application Code Changes (Grails)

    Grails application code

    Step-1 Dependencies - Add compile-time and run-time dependencies to gradle build script.

    PostgreSQL jdbc driver
    - Use the correct PostgreSQL JDBC driver for Java 8 date time classes support. Make sure you are using JRE 8+ driver. If not, change it:

    Hibernate - Next add hibernate Java8 compile time dependency.

    Grails - Then add grails Java8 dependency plugin

    Following is the Gradle build script with these dependencies added:
    build.gradle
    dependencies { ... runtime "org.postgresql:postgresql:42.1.4" //Java JDBC 4.2 (JRE 8+) driver for PostgreSQL database compile 'org.hibernate:hibernate-java8 //hibernate java 8 java.time classes support compile "org.grails.plugins:grails-java8" //grails java 8 java.time classes support ... }

    Step-2 Domain Model - Change Date types to OffsetDateTime

    Agreement.groovy
    class Agreement { ... OffsetDateTime signedDate OffsetDateTime expirationDate ... }

    Assuming that the model had Date types earlier and the generated dbm change-log script: agreement-changes.groovy was already applied to database, now as the domain object properties are changed from java.util.Date to java.time.OffsetDateTime, we need to generate change-log script for these changes and apply them to database. But, the command dbm-gorm-diff will not generate the change-log script to have the right PostgreSQL type: TYPE TIMESTAMP WITH TIMEZONE. So, it requires a manual dbm change-log like:

    e.g. agreement-date-type-changes.groovy
    databaseChangeLog = { changeSet(author: 'gpottepalem', id: 'alter signed_date to store time zone') { grailsChange { change { sql.execute('ALTER TABLE myschema.my_table ALTER COLUMN signed_date TYPE TIMESTAMP WITH TIME ZONE') } } } changeSet(author: 'gpottepalem', id: 'alter expiration_date to store time zone') { grailsChange { change { sql.execute('ALTER TABLE myschema.my_table ALTER COLUMN expiration_date TYPE TIMESTAMP WITH TIME ZONE') } } } }

    With this schema change applied to database, when the domain object is saved, OffsetDate gets stored in PostgreSQL database with timestamp and offset indicating the time zone.

    Step-3 Views - JSON views

    If you have JSON views in the application, you need to make sure that the OffsetDateTime fields' data looks good and is as expected in the JSON output produced. Unlike java.util.Date fields, java.time.OffsetDateTime fields output data won't get formatted. The JSON output for OffsetDateTime fields not only contain all of it's direct properties but also recursive properties of all of it's object references accessible via getXXX() methods. e.g. OffsetDateTime's getOffset() method's returned ZoneOffset object's getAvailableZoneIds() method's returned all zoneIds will show up as well.

    For instance, if you have a view under grails-app/views/agreement/_agreement.gson like:
    model { Agreement agreement } json.agreement { signedOn agreement.signedDate expiresOn agreement.expirationDate }

    You will have it rendered in JSON as:
    { "agreement": { "signedOn": { "dayOfMonth": 27, "dayOfWeek": "SATURDAY", "dayOfYear": 27, "hour": 12, "minute": 44, "month": "JANUARY", "monthValue": 1, "nano": 356000000, "offset": { "availableZoneIds": [ "Asia/Aden", "America/Cuiaba", "Etc/GMT+9", ... ], "id": "-05:00", "rules": { "fixedOffset": true, "transitionRules": [], "transitions": [] }, "totalSeconds": -18000 }, "second": 35, "year": 2018 }, "expiresOn": { "dayOfMonth": 27, ... } } }

    A quick solution is to simply convert java.time.OffsetDateTime to java.util.Date or simply stringify using toString() depending on your needs. The following gson template:
    import java.time.Instant import java.time.OffsetDateTime model { Agreement agreement } json.agreement { signedOn agreement.signedDate.toString() //current zone date-time with offset from UTC expiresOn Date.from(agreement.expirationDate.toInstant()) //OffsetDateTime to Date in UTC timezone }
    will produce the output as:
    { "agreement": { "signedOn": "2018-01-27T17:10:57.443-05:00", "expiresOn": "2018-01-27T22:10:57Z" } }

    Gotcha

    PostgreSQL stores offset when OffsetDateTime field is persisted. But when queried, will not give stored offset back. Instead, gives data in the timezone the database is set (for instance UTC) with no offset. Databases add their own steep learning curve to deal with date-time-zone data ;)

    Tip

    Grails console is the handy tool for explorations with changes like this in a Grails application.
    e.g. A simple Grails script to persist and retrive Agreement object
    import com.my.app.Agreement import java.time.OffsetDateTime println Agreement.count() Agreement agreement = new Agreement( signedDate: OffsetDateTime.now(), expirationDate: OffsetDateTime.now().plusYears(1) ).save(flush: true, failOnError: true) println Agreement.count() Agreement.all.each { println "id:${it.id}, signedDate:${it.signedDate} expirationDate:${it.expirationDate}" }

    Summary

    Date - is the central issue of Y2K problem which costed IT industry billions of dollars and isn't trivial yet. Even in modern languages and frameworks, it hasn't become as primitive as it can become, and dealing with it requires a special care and attention from all levels of the application right from presentation, business logic, and up to the persistence.

    In modern Java applications, Java 8's added date.time classes have become better if not any simpler, but is a definitive way to go with, when dealing with date, time and zone in present times!

    References