Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

Tuesday, December 20, 2016

Grails 3 Push Notifications: Spring Web Sockets, AngularJS, JWT.

Apparantly its not very common to want to combine these sets of technologies. But I did, and I wanted to share some of the tips I found, and hopefully combine the knowledge into one place. There are a bunch of different sources that will help you solve several problems you may encounter.

The use-case


I wanted to be able to have my API server push updates to my web-based angular app as interesting things took place. The most common use is that someone else posted new messages in a chat thread - but it can also happen when someone else updates a shared resource or other collaboration. Or perhaps even to populate new items into a news feed without needing a page refresh.

To "push" data to my angular app, I knew I was going to need sockets. Grails 3 has updated spring support that allows for the use of spring websockets - which use the STOMP protocol & SockJS. SockJS is a library that abstracts away a lot of the lower level websocket management in browsers and allows fallbacks to long polling if needed. STOMP makes it easy to create per-user topics and other utility methods.

The websocket pattern integrates really nicely with grails reactor integration. As controllers recieve updates from other clients, events can be raised to push notifications to interested listeners.


Starting Out


Understanding the basics of web sockets are outside the scope of this post - but spring has made integrating web sockets over STOMP easy in recent versions.

There are a couple good sources for starting out. There is an up-to-date grails 3 plugin that allows you to use socket topics easily from within existing controllers.

Defining a controller that takes incoming chat requests over socket is easy.
class ChatController {

    def springSecurityService    def messagingService
    def index = {}

    @ControllerMethod    
    @MessageMapping("/incchat")
    @PreAuthorize("hasRole('ROLE_USER')")
    @SendToUser("/queue/incchat")
    /**     * Take a JSON input and parse from object
     * id: threadid, text: messageText     */    
    String doChat(String jsonIn, Principal principal) {
     ...


Defining a listener to updates from other requests as a service is easy.
@Consumer
class WebChatService {

    SimpMessagingTemplate brokerMessagingTemplate
    @Selector('message.received.chat')
    void sendChatToUser(WebChatTarget target) {
        String data = [message: target.message, thread: target.messageThread] as JSON
        brokerMessagingTemplate.convertAndSendToUser(target.username, "/queue/chat", data)
    }
}

SOCKJS allows http websocket upgrades - so make sure to use a http:// base url if your sockets are using sockjs. Use ws:// otherwise. Make sure to allow origins that would allow your angular app to connect if its running on a different host.

@Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
    stompEndpointRegistry.addEndpoint("/stomp")
            .setAllowedOrigins("*")
            .withSockJS() //SOCKJS requires http connection, other use ws://
            .setSessionCookieNeeded(false);
}

Then it gets tricky.


Everything works amazingly up to this point if you are using session based authentication. I'm not doing that in the Yolobe stack. Our api is secured via stateless JWT to make mobile integration easier  -you just need a couple services and an http interceptor to have angular send the correct headers every time. But what about Web sockets - can you send the right headers to have JWT work on websocket? Kind of.

Spring sockets was recently updated to allow JWT type tokens. The provided example will allow basic JWT authentication to work. But user-based topics and queues will stop functionining as the principal user is missing from the session. Youll have to inject it on every call.

A full solution


You'll need to create a pretty extensive configuration bean to integrate spring websocket and spring security rest:

Your configuration bean

webSocketConfig(YolobeWebSocketConfigurationBean) {
    jwtService = ref("jwtService")
}

Will need to parse JWT tokens on each request (inside configureClientInboundChannel )- assign the principal to the session/message, then forward the request to a new principal-aware user registry.

Configuration Bean

@Configuration
@EnableWebSocketMessageBroker
@Order(HIGHEST_PRECEDENCE)
public class YolobeWebSocketConfigurationBean extends AbstractWebSocketMessageBrokerConfigurer {

    public DefaultSimpUserRegistry _userRegistry = new DefaultSimpUserRegistry();
    public DefaultUserDestinationResolver _resolver = new DefaultUserDestinationResolver(_userRegistry)


    JwtService jwtService    
    MessageBrokerRegistry messageBrokerRegistry

    @Bean    
    @Primary    
     public SimpUserRegistry userRegistry() {
        return _userRegistry;
    }

    @Bean
    @Primary
    public UserDestinationResolver userDestinationResolver() {
        return _resolver;
    }

    def tokenPattern = ~/Bearer\s(?<token>\S+)/    
    private Principal getUser(StompHeaderAccessor accessor) {
        String header = accessor.getNativeHeader("Authorization")[0]
        def matcher = (header =~ tokenPattern)
        if (matcher.matches()) {
            try {
                JWT jwt = jwtService.parse(matcher.group('token'))
                return new SocketPrincipal(username:jwt.payload.toJSONObject()["sub"]);

            }
            catch (JOSEException formatError) {
                throw new Exception("Bad Authorization: Bearer - Token")
            }
        }
        throw new Exception("Bad Authorization: Bearer - Token")
    }


    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {


        registration.setInterceptors(new ChannelInterceptorAdapter() { //authenticate user on connection request            

                @Override
                public Message<?> preSend(Message<?> message, MessageChannel channel) {

                StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);

                Principal yourAuth = getUser(accessor)

                if (accessor.messageType == SimpMessageType.CONNECT) {
                    YolobeWebSocketConfigurationBean.this._userRegistry.onApplicationEvent(new SessionConnectedEvent(this, message, yourAuth));
                } else if (accessor.messageType == SimpMessageType.SUBSCRIBE) {
                    YolobeWebSocketConfigurationBean.this._userRegistry.onApplicationEvent(new SessionSubscribeEvent(this, message, yourAuth));
                } else if (accessor.messageType == SimpMessageType.UNSUBSCRIBE) {
                    YolobeWebSocketConfigurationBean.this._userRegistry.onApplicationEvent(new SessionUnsubscribeEvent(this, message, yourAuth));
                } else if (accessor.messageType == SimpMessageType.DISCONNECT) {
                    YolobeWebSocketConfigurationBean.this._userRegistry.onApplicationEvent(new SessionDisconnectEvent(this, message, accessor.sessionId, CloseStatus.NORMAL));
                }


                accessor.setUser(yourAuth);
                accessor.setLeaveMutable(true);
                return MessageBuilder.createMessage(message.payload, accessor.messageHeaders)
            }
        });
    }

    @Override    
    public void configureMessageBroker(MessageBrokerRegistry messageBrokerRegistry) {
        messageBrokerRegistry.enableSimpleBroker("/queue", "/topic");
        messageBrokerRegistry.setUserDestinationPrefix("/user")
        messageBrokerRegistry.setApplicationDestinationPrefixes("/app");
        this.messageBrokerRegistry = messageBrokerRegistry;
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
        stompEndpointRegistry.addEndpoint("/stomp")
                .setAllowedOrigins("*")
                .withSockJS() //SOCKJS requires http connection, other use ws://                .setSessionCookieNeeded(false);
    }

    @Bean
    public GrailsSimpAnnotationMethodMessageHandler grailsSimpAnnotationMethodMessageHandler(SubscribableChannel clientInboundChannel, MessageChannel clientOutboundChannel, SimpMessagingTemplate brokerMessagingTemplate) {
        GrailsSimpAnnotationMethodMessageHandler handler = new GrailsSimpAnnotationMethodMessageHandler(clientInboundChannel, clientOutboundChannel, brokerMessagingTemplate);
        handler.setDestinationPrefixes(["/app","/queue","/topic"]);
        return ((GrailsSimpAnnotationMethodMessageHandler) (handler));
    }

}

Client side

I used this fork of ng-stomp that allows multiple socket connections and disconnection notifications. here is some ugly sample code that shows what is possible. Credentials.decorate() is an angular service that holds stored credentials (persisted to localstorage) and creates a header map.

decorate :  function() {
    return { 'Authorization': this.getType() + " " + this.getToken() };
}

$stomp
    .connect("messaging", platformApiEndpoint.host+'/stomp',  Credentials.decorate() )
    .then(function (frame) {
        console.log(frame);        // notify callback function
        var showResponse = function (res) {
            console.log("RESPONSE! OMG", JSON.stringify(JSON.parse(res.body)));        
        };        
        $stomp.subscribe("messaging", '/user/queue/chat', Credentials.decorate()).then(null, null, showResponse);
        $timeout(function() { //example sending a message to topic            
                $stomp.send("messaging", '/app/incchat', {
                      id: $scope.threadId,                text: "connected VIA SOCK"            
                }, Credentials.decorate() );
        }, 1000);

Wednesday, June 8, 2016

Intellij and Grails 3 Command Failure

File this one under "doh, thats obvious" but I completely spaced out as to why my Intellij commands to generate new grails resources - like creating a new taglib
 

Would fail with an error like:

"C:\Program Files\Java\jdk1.8.0_45\bin\java" -XX:+TieredCompilation [...] \lib\grails-rt.jar" org.grails.cli.GrailsCli create-taglib <className> Error |
Command [create-taglib] error: Profile [org.grails.profiles:base:3.1.6] declares and invalid dependency on parent profile [org.grails.profiles:base:3.1.6] (Use --stacktrace to see the full trace)

The solution is to ensure the intellij SDK settings are set to the same level of grails SDK specific within your gradle file. I had upgraded to grails 3.1.6 within my build, but IntelliJ uses its own settings to define which grails binary to use for generating classes.




(Notice how Intellij still thinks my project is using 3.1.8 although I had downgraded to 3.1.6 - another curious feature that may crop up later)

Monday, March 26, 2012

Detecting environment for Google Web Toolkit

Have a piece of code that you want to run only when running in Hosted Mode? I made a small utility class that uses one method exposed in the Google Web Toolkit API to do just that.


import com.google.gwt.core.client.GWT;


/**
 * Utility Class to help write debugging code
 * References to these function could be searched for and removed when deploying to production for increased speed.
 * @author Jason Lambert
 *
 */
public class Development {
public static boolean isDevelopmentEnvironment() { return !GWT.isScript(); } //detect hosted mode


}

Inspired by how Grails handles development, test and production environments, I  made this quick static utility function.

GWT.isScript detects if the current environment is running in compiled byte code or not. useful for switching to download local resources when perhaps same origin policy (SOP) prevents your code from downloading data from external databases or servers.

Thursday, March 15, 2012

Tomcat SEVERE: Error listenerStart - Debugging.

From my travels across Google, this error is common. In Grails applications it is almost always from a bad datasource.groovy file. This error is the non descriptive string you receive when deploying your servlet (Grails, Spring, Otherwise) to tomcat (probably version 5.5 or higher). The servlet probably runs fine in development mode on your machine. How do we debug it? Read on.

Lets start by getting more useful error messages from Tomcat.
Tomcat by default uses java.util.logging, which is giving you the basic error messages along the lines of...


DATETIME org.apache.catalina.core.StandardContext startInternal
SEVERE: Error listenerStart
DATETIME org.apache.catalina.core.StandardContext startInternal
SEVERE: Context [/AppName] startup failed due to previous errors

Lets configure tomcat to use log4j, which will produce a much better call stack to debug. Taken from advice here, I'll paraphrase [read the source material for a full understanding however].


  1. Stop Tomcat
  2. Install Log4j on your deployment machine. (package install available for a lot of linux distros)
  3. Download tomcat-juli-adapters.jar and tomcat-juli.jar from the extras section of the tomcat website (For version 7.0 that extras page is here) and place them into the $CATALINA_HOME/lib folder
  4. Replace the $CATALINA_HOME/bin/tomcat-juli.jar with that same tomcat-juli.jar you just placed in lib.
  5. Delete $CATALINA_BASE/conf/logging.properties to prevent the old logging system from being used
  6. Start Tomcat


In my case, the error stack now looked like the following. As you can see like an idiot I had left the driverClassName to the grails 2.0 default of  org.h2.Driver instead of the com.mysql.jdbc.Driver required to connect to a mysql service. Interestingly my eclipse/tomcat development setup was forgiving me and loading the correct driver anyway - be warned!

SEVERE: Exception sending context initialized event to listener instance of class org.codehaus.groovy.grails.web.context.GrailsContextLoaderListener
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManagerPostProcessor': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creati$
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
        at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
        at java.util.concurrent.FutureTask.run(FutureTask.java:166)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
        at java.lang.Thread.run(Thread.java:679)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManager': Cannot resolve reference to bean 'sessionFactory' while setting bean property 'sessionFactory'; nested exception is org.sp$
        ... 6 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory': Cannot resolve reference to bean 'hibernateProperties' while setting bean property 'hibernateProperties'; nested exception is $
        ... 6 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'hibernateProperties': Cannot resolve reference to bean 'dialectDetector' while setting bean property 'properties' with key [hibernate.dialect];$
        ... 6 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dialectDetector': Invocation of init method failed; nested exception is org.springframework.jdbc.support.MetaDataAccessException: Error while e$
        ... 6 more
Caused by: org.springframework.jdbc.support.MetaDataAccessException: Error while extracting DatabaseMetaData; nested exception is org.apache.commons.dbcp.SQLNestedException: Cannot create JDBC driver of class 'org.h2.Driver' for connect URL '$
        ... 6 more
Caused by: org.apache.commons.dbcp.SQLNestedException: Cannot create JDBC driver of class 'org.h2.Driver' for connect URL 'jdbc:mysql://xxx.xx.xx.xx:3306/mydatabasename?autoreconnect=true&zeroDateTimeBehavior=convertToNull'
        at org.apache.commons.dbcp.BasicDataSource.createConnectionFactory(BasicDataSource.java:1452)
        at org.apache.commons.dbcp.BasicDataSource.createDataSource(BasicDataSource.java:1371)
        at org.apache.commons.dbcp.BasicDataSource.getConnection(BasicDataSource.java:1044)
        ... 6 more
Caused by: java.sql.SQLException: No suitable driver
        at java.sql.DriverManager.getDriver(DriverManager.java:279)
        at org.apache.commons.dbcp.BasicDataSource.createConnectionFactory(BasicDataSource.java:1437)
        ... 8 more

Tuesday, February 21, 2012

Searching Multiple Data Sources in Grails

Have you got domain classes mapped to multiple datasources? The Searchable plugin will mysteriously not be building indexes for domain classes in your secondary datasources by default. The following explains a way to change the behavior.

This is taken from my personal experience, and information compiled from a helpful thread archived on Grails, found here. We are going to need to redefine some beans.

Step 1:
Open conf/spring/resources.groovy


add in the following bean:


import org.compass.gps.device.hibernate.HibernateGpsDevice //need this import for new datasource
import grails.plugin.searchable.internal.compass.config.SessionFactoryLookup //need this import to get correct sessionfactory class


beans = {


/* ~ your other beans here */



compassGpsDevice(HibernateGpsDevice) { bean ->
bean.destroyMethod = "stop"
name = "hibernate"
sessionFactory = { SessionFactoryLookup sfl ->
sessionFactory = ref('sessionFactory_datasourceName') 
}//replace datasourceName with the name of the datasource as defined in conf/DataSource.groovy
fetchCount = 5000
}
}


Step 2:
You could stop here if this was the only datasource that contained indexable classes, but continue to add each compassGPSdevices for each additional datasource (including the default), making sure they are unique names ie:



anotherUniquecompassGpsDevice(HibernateGpsDevice) { bean ->
bean.destroyMethod = "stop"
name = "unqiueHibernateName"
{ SessionFactoryLookup sfl ->
sessionFactory = ref('sessionFactory_uniquedatasource') 
}
fetchCount = 5000
 }


then finally we need to combine all of these datasources into one SingleCompassGps for searchable. Add this last bean:


import org.compass.gps.impl.SingleCompassGps

compassGps(SingleCompassGps) {
compass = ref('compass')
gpsDevices = [compassGpsDevice, anotherUniqueCompassGpsDevice /* ~add the other sources in here */ ]
}

Thanks to the original thread for guidance. Hopefully this compilation of guidance will help someone else save some time. The major additions beyond the original thread are to use the sessionfactorylookup. You can see these objects in SearchableGrailsPlugin.groovy - we are simply re defining them with new datasources!





Friday, February 17, 2012

Undocumented? Grails scaffolding feature. Widgets

Present in old docs, but weirdly not the latest, you can request a different widget on scaffolded pages for your domain class.

In the domain's scaffold closure:


def constraints = {
        myStringField(widget:'textarea')
}

Thursday, February 16, 2012

Fun fact: Grails GString's require double quotes

Will work:


def element = "<script type=\"text/javascript\" src=\"${g.resource(dir: 'folder', file: 'file.js')}\"></script>"
out <<  element.toString()


Wont work:


def element = '<script type="text/javascript" src="${g.resource(dir: "folder", file: "file.js")}"></script>'
out <<  element.toString()


And by "wont work" the groovy expressions in the GString will not be evaluated, they will be delivered literally. Note the double quotes beginning and ending the GString in the first example.

I knew this. Yet I was dissecting why my unit test was failing for over an hour. Turns out junit can't detect silly human error! Since I re-learnt this lesson, I thought I would share it with you, blogosphere!

Friday, January 20, 2012

Grails Codec file location and extension

If you are just beginning out in Grails development, the creation of custom codecs can greatly increase productivity. Codecs help you code/decode (funny that) strings. Grails ships with a bunch of them already, but when you feel like you want to create you own, greate a groovy class file (extension .groovy), with the name ending in "Codec" in your projects graips-app/utils folder. Remember that if you want to place these files in packages, you will need to create the additional folder structure to match the package structure. A sample codec structure is shown below. This file would be in the folder grails-app/utils/org/mycompany/:


package org.mycompany
import /* imports here */

class SampleCodec{
  static encode = { str ->
   /* Coding data goes here */
  }

static decode = { str ->
   /* Decoding data goes here */
  }

}

Once you start your grails app, Spring will inject the methods encodeAsSample() and decodeSample() to the Java.lang.String class.

Start creating custom hash codes for your passwords now!

Wednesday, January 18, 2012

JMX and Grails

Developing on windows can sometimes really be a pain. I had found documentation all over the web as to where to place my JVM arguments to make grails accept remote JMX connections while developing in eclipse. Most have you modifying $GRAILS_HOME/bin/grails.bat but I was noticing in Java Visual VM (great tool by the way) that the VM arguments were not changing.

In one of those 'the answer is simple stupid' moments - some clarity was given to me by this page of documentation: http://www.objectpartners.com/2009/05/27/eclipse-setup-for-grails-11-development/

By adding the following string the run configuration for my Grails project:

-Dcom.sun.management.jmxremote -Djava.rmi.server.hostname=<yourHostIPHere> -Dcom.sun.management.jmxremote.port=9004 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false
[Note this is obviously not a production setting, you will NEED AUTHENTICATION in production]

I was able to get jconsole to connect to the remote process. Of course local connections had never been a problem as JMX is enabled locally by default.

For anyone curious, the run configuration for a tutorial project is shown below.

A run configuration for JMX with Grails on Windows Eclipse.

Tuesday, December 27, 2011

Basic Grails custom tag testing errors

You may encounter an error such as the following:

"No such property: out<or other method name> in package.classname "


This comes from writing the taglibtest like all the tutorials out on the web had said to, e.g. using:

class DateTagLib {
def thisYear = {
out << Calendar.getInstance().get(Calendar.YEAR)
'' //return empty text
 }
}

This problem arises from the newer versions of grails producing UNIT tests rather than INTEGRATION tests. In the unit environment, the Grails engine is not active to inject the dynamically created methods, such as validate, out.

So how do I resolve this? It runs just fine on the test webpage its included on. The test was failing when the tag clearly worked.



Three things to fix:

  1. Make sure your test case is extending TagLibUnitTestCase.
  2. Make sure you are calling super.setUp() in your test constructor
  3. Make a call to mockTagLib(YourTagClassHere) at the beginning of your test method.


So a basic test case for a custom tag (as shown above) might look like the following:


package yourpackage
import grails.test.*
class DateTagLibTests extends TagLibUnitTestCase  {
  def dateTagLib


  void setUp(){
 super.setUp()
 
    dateTagLib = new DateTagLib()

  }


  void testThisYear() {
 mockTagLib(DateTagLib)
    String expected = Calendar.getInstance().get(Calendar.YEAR)
    assertEquals("the years don't match", expected, dateTagLib.thisYear().toString())
  }
}

Wednesday, December 21, 2011

Changing syntax highlighting for Groovy/Grails in Eclipse

Looking for something a little easier on the eyes?
By combining the Eclipse color theme plugin with some specific groovy settings, you can make working with groovy much more 'visually' appealing.

1) Install the eclipse color theme plugin. Help->Eclipse Marketplace...->Search for "Eclipse Color Theme". Select a dark theme (or really any one of your choosing). You might notice some elements are not being colored correctly however, namely brackets and operators. To fix these last few elements we can manually change their color to something that suits.

2) Window->Preferences->Groovy->Editor. In this settings page you will find the rest of the settings you need to change. Set the operator, bracket colors to something more visible (and any other fine tweaks you may wish)

Enjoy!

Friday, August 12, 2011

Generating class diagrams from Eclipse projects

A zoomed out view of my class structure - using subclasses of standard packages prevents some connections from being detected.

I was looking to jump back into a project I had started a while ago in Eclipse. A GWT project for viewing resources from our website, and the class structure had totally evaded me. So I wanted to generate a UML class diagram quickly to get back up to speed and remind myself of what everything did.

I recommend ESS-Model if you ever encounter the same situation. Its free and fast. No bothersome purchase orders to fill out!