interview questions…

So yesterday I had a phone interview with Rally. Overall, I think the interview went good, but one question he asked me was about polymorphism – “What is polymorphism?” to be exact.

We all learn about polymorphism in college along with things like multiple inheritance, weak/strong typing, and object oriented design. But I have always found questions involving these concepts have really funky answers that are sometimes hard to articulate. My first reaction was to say that polymorphism is having a class Foo that can act like class Bar. Well that’s not technically the right answer, but I am not sure its necessarily the wrong answer as well.

Polymorphism is defined in my CS3410 book (Data Structures and Problem Solving using Java – second edition by Mark Weiss) as the following –

A reference type can refence objects of several different types. When methods are applied to the polymorphic type, the operation that is appropriate to the actual referenced object is automatically selected. In Java, this is implemented as part of inheritance. Polymorphism allows us to implement classes that share common logic.

So if Student and Faculty both extend Person and you assign a newly created Student object to a Person reference and then call toString(), it will use the Student’s toString method and not the Person class method (that is unless the Student toString calls super.toString()). The appropriate implementation of the method is called at run-time through dynamic binding.

So that means that a Person class can indeed act like a Student class and vice-versa depending on the methods called.

Oh and another question he asked was what is the difference between a list and a set? This one always screws me up, I get them backwards 9 times out of 10. So here are the answers:

List – Can have duplicates and has a defined order.
Set – Cannot have duplicates and does not have a defined order.

Posted in: Uncategorized

Grails – DomainObject.findAll() does not presort returned objects…

So I have a drop down list of Applications… Well the Applications domain object specifies that it should presort all returns by the appName field. This works great when doing something like user.applications. Yet when I did my normal Application.findAll() the list it returned was not sorted by appName. I instead had to use Application.list().

Posted in: grails

D6T-XL

The monthly assignment over on the Fred Miranada forums is “New” well here is my take on new. Oh the retail price of this bad boy is about $300,000.

D6T-XL

D6T-XL

Posted in: photography

SSO take 2

Ok so we purchased a new template from Theme Forest this week for our “ASM” system. So while I was reskinning the login box I started having issues outputting the html dynamically using jquery (the old box as you can see was rather simple). So I decided to iframe the entire modal window of the login box. The reason I ended up not doing this last time was because redirecting once authenticated was a bit of pain for some reason. But alas I have conquered that beast.

So what you end up with is something like this:

This is the page which needs to display a login box

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--
 To change this template, choose Tools | Templates
 and open the template in the editor.
-->
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>FDLE:AppHub</title>
    <script src="/AppHub/login/script"></script>
    <script type="text/javascript" src="/AppHub/js/jquery-1.3.2.min.js"></script>
    <script type="text/javascript" src="/AppHub/js/jquery-ui-1.7.2.custom.min.js"></script>
    <script>
        loadIFrameModal('local', window.location.protocol+"//"+window.location.host+"/AppHub/login/show/");
    </script>
</head>
<body>

</body>
</html>

The javascript (which is a GSP page)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function loadIFrameModal(env, targetUrl) {
    $(window).load(function() {
        var flag = ${flag};
        if(flag == false) {
            if(env == 'dev' || env == 'development') {
                $("body").append("<div id=\"modal\"></div>");
                $("#modal").append("<iframe src=\"http://162.143.99.200:8080/AppHub/login/?targetUrl="+targetUrl+"\" height=\"500\" width=\"400\" frameborder=\"0\" />");
            }
   
            if(env == 'local') {
                $("body").append("<div id=\"modal\"></div>");
                $("#modal").append("<iframe src=\"http://66869-irm:8080/AppHub/login/?targetUrl="+targetUrl+"\" height=\"475\" width=\"400\" frameborder=\"0\" />");
            }
   
            //open dialog
            $("#modal").dialog({
                autoOpen: true,
                height: 525,
                width: 475,
                modal: false,
                resizable: false,
                position: 'center',
                draggable: false,
                closeOnEscape: false
            });
            //hide the title bar
            $(".ui-dialog-titlebar").hide();
            $("#modal > iframe").attr("style", "overflow:hidden;");
        } else {
          $("body").append("<form id=\"form\" method=\"POST\" action=\""+targetUrl+"\"></form>");
          $("#form").append("<input type=\"hidden\" name=\"id\" value=\"${id}\" />");
          $("#form").append("<input type=\"hidden\" name=\"ip\" value=\"${request.getRemoteAddr()}\" />");
          $("#form").submit();
        }
    });
}

The iframe content:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<body>
    <!-- Start Wrap -->
    <div id="wrap" class="login">
        <div id="content">
            <div class="inner">
       
                <noscript>
                    <!-- Show message when javascript is not enabled -->
                    <div class="message error">
                            <p>Javascript is needed for this template to work properly. <br />Please <a href="http://browsehappy.com/" title="Upgrade to a better browser">upgrade</a> your browser or <a href="http://www.google.com/support/bin/answer.py?answer=23852" title="Enable Javascript in your browser">enable</a> Javascript to navigate the interface properly.</p>
                        </div> 
                </noscript>
                <!-- Start Content Box #1 -->
                <div class="title">
           
                    <h3>fdle:Connect</h3>
           
                    <div class="selector" title="content_box">
                        <a class="tab active" href="tab-login">Login</a>
                        <a class="tab" href="tab-password">Forgotten Password</a>
                    </div>
                </div>
                <div class="box">
                    <div class="txt">
                   
                        <div id="content_box">
                            <div class="tab-login">

                                <!-- Start Message -->
                                <g:eachError bean="${loginCmd}">
                                    <div class="message error">
                                        <p><g:message error="${it}" /></p>
                                    </div>
                                </g:eachError> 
                                <!-- End Message -->
                           
                                <!-- Start Login Form -->
                                <g:form action="signIn">
                                    <fieldset>
                                        <p><label>Username</label>
                                        <input type="text" class="txt-input large" name="username" />
                                        </p>
                                        <p><label>Password</label>
                                        <input type="password" class="txt-input large" name="password" />
                                        </p>
                                        <p style="float:left;"><input type="submit" class="button" name="submit" value="Login" />
                                        <input type="reset" class="reset" name="reset" value="Reset" /></p>
                                        <input type="hidden" name="targetUrl" value="${targetUrl}" />
                                        <input type="hidden" name="ip" value="${request.getRemoteAddr()}" />
                                    </fieldset>
                                </g:form>
                                <!-- End Login Form -->
                           
                            </div>
                            <div class="tab-password">
                                <!-- Start Password Form -->
                                <form method="post" action="index.html">
                                    <fieldset>
                                        <p><label>Email</label>
                                        <input type="text" class="txt-input large" />
                                        <small>Please enter the email address of your account</small></p>

                                        <input type="submit" class="button" name="submit" value="Request Password" />
                                    </fieldset>
                               
                                </form>
                                <!-- End Password Form -->
                            </div>
                        </div>
                   
                    </div>
                </div>
                <!-- End Content Box #1 -->
           
            </div>
        </div>         
    </div>
    <!-- End Wrap -->
</body>

And the page that redirects once authenticated:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!--
 To change this template, choose Tools | Templates
 and open the template in the editor.
-->

<%@ page contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>FDLE:Connect</title>
    <script src="http://www.google.com/jsapi"></script>
    <script>
      // Load jQuery
      google.load("jquery", "1.3.2");
      google.load("jqueryui", "1.7.2");
      google.setOnLoadCallback(function() {
            $(document).ready(function() {
                self.parent.location = '${targetUrl}';         
            });
      });
    </script>
  </head>
  <body>
    <span style="color:#000; font-size:14px; font-family:verdana;">If this page does not redirect please click the following <a href="${targetUrl}" target="_top">link</a>.</span
 </body>
</html>

Posted in: grails, javascript

Implementing Single Sign On…

So when I set out to rewrite our ASM system, I knew I had to find some selling points to the architecture I was pitching. One of them was the ability to write an SSO system that was easy and efficient and most of all did not require much code for implementing applications (JSSO and the like were out of the question).

So here’s what I came up with (everything is written in Grails)… I first created an AuthController which would handle the authentication and return back a user id for the person if they were a valid user (either in our custom user store or our AD system). This controller had a login action which displayed the login prompt and would return all the necessary error back to the user if something was wrong.

I then wrote another action whose corresponding GSP stored my javascript for creating the needed iframes on the page. This meant that when someone loaded the action I could access all of their cookies (which I needed for SSO) even if their application was on another domain. In the action I would process the cookies and either send them the login page or return the user id back to the browser which would then be processed by the application.

In the end the only code the implementing applications need to have is the follow:

1
2
3
4
5
6
7
8
9
10
11
12
  <script src="http://www.google.com/jsapi"></script>
  <script src="http://localhost:8080/AppHub/auth/script"></script>
  <script>
    // Load jQuery
    google.load("jquery", "1.3.2");
    google.load("jqueryui", "1.7.2");
    google.setOnLoadCallback(function() {
      loadModal('local',
      window.location.protocol+"//"+window.location.host+"/LoginApplication/login/show/",
      window.location.protocol+"//"+window.location.host+window.location.pathname);
    });
  </script>

And here is how I process the script action:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def script = {
    def cookie = request.cookies.find { it.name == 'session_id' }
    if (!cookie) {
      render(view: "script", contentType: "text/javascript", model: [flag: 'false'])
    } else {
      def id = Session.findBySessionIdAndKey(cookie.value, "id")
      if (id && cookie.value == id.sessionId) {
        render(view: "script", contentType: "text/javascript", model: [flag: 'true', id: id.value])
      } else {
        cookie.maxAge = 0
        response.addCookie(cookie)
        render(view: "script", contentType: "text/javascript", model: [flag: 'false'])
      }
    }
  }

And here is the script.gsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
function loadModal(env, targetUrl, returnUrl) {
  var flag = ${flag};
  $(window).load(function() {
    if(flag == false) {
      if(env == 'dev' || env == 'development') {
          $("head").append("<link rel=\"stylesheet\" href=\"http://162.143.99.200:8080/AppHub/css/ui-darkness/jquery-ui-1.7.2.custom.css\" type=\"text/css\" media=\"screen\" />");
          $("head").append("<link rel=\"stylesheet\" href=\"http://162.143.99.200:8080/AppHub/css/login.css\" type=\"text/css\" media=\"screen\" />");
          $("head").append("<link rel=\"stylesheet\" href=\"http://162.143.99.200:8080/AppHub/css/form.css\" type=\"text/css\" media=\"screen\" />");
          $("body").append("<div id=\"modal\"></div>");
          $("#modal").append("<div id=\"box\"></div>");
          $("#box").append("<h2 style=\"color:#666666;\">AppHub:<span class=\"light\">Connect</span></h2>");
          if(hasError('user.username.invalid')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.username.invalid" /></p>");
          }
          if(hasError('user.username.blank')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.username.blank" /></p>");
          }
          if(hasError('user.password.blank')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.password.blank" /></p>");
          }
          if(hasError('user.password.invalid')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.password.invalid" /></p>");
          }
          if(hasError('user.adproblem')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.adproblem" /></p>");
          }
          if(hasError('user.kerberos.6')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.kerberos.6" /></p>");
          }
          if(hasError('user.kerberos.18')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.kerberos.18" /></p>");
          }
          if(hasError('user.kerberos.23')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.kerberos.23" /></p>");
          }
          if(hasError('user.kerberos.24')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.kerberos.24" /></p>");
          }
          $("#box").append("<form id=\"form\" method=\"POST\" action=\"http://162.143.99.200:8080/AppHub/auth/signIn\"></form>");
          $("#form").append("<input type=\"hidden\" name=\"targetUrl\" value=\""+targetUrl+"\" />");
          $("#form").append("<input type=\"hidden\" name=\"returnUrl\" value=\""+returnUrl+"\" />");
          $("#form").append("<input type=\"hidden\" name=\"ip\" value=\"${request.getRemoteAddr()}\" />");
          $("#form").append("<ul></ul>");
          $("#form > ul").append("<li><label for=\"username\">Username</label><div><input type=\"text\" name=\"username\" class=\"max text\" /></div></li>");
          $("#form > ul").append("<li><label for=\"password\">Password</label><div><input type=\"password\" name=\"password\" class=\"max text\" /></div></li>");
          $("#form > ul").append("<li><input type=\"submit\" name=\"submit\" class=\"button\" value=\"Sign In\" /></li>");
          $("#form > ul").append("<li><a style=\"color:#0088CC;\" href=\"#\">Forgot Password</a><span style=\"color:#0088CC;\"> | </span><a style=\"color:#0088CC;\" href=\"#\">Register</a><span style=\"color:#0088CC;\"> | </span><a style=\"color:#0088CC;\" href=\"#\">Unlock Account</a></li>");
      }

      if(env == 'local') {
          $("head").append("<link rel=\"stylesheet\" href=\"http://localhost:8080/AppHub/css/ui-darkness/jquery-ui-1.7.2.custom.css\" type=\"text/css\" media=\"screen\" />");
          $("head").append("<link rel=\"stylesheet\" href=\"http://localhost:8080/AppHub/css/login.css\" type=\"text/css\" media=\"screen\" />");
          $("head").append("<link rel=\"stylesheet\" href=\"http://localhost:8080/AppHub/css/form.css\" type=\"text/css\" media=\"screen\" />");
          $("body").append("<div id=\"modal\"></div>");
          $("#modal").append("<div id=\"box\"></div>");
          $("#box").append("<h2 style=\"color:#666666;\">AppHub:<span class=\"light\">Connect</span></h2>")
          if(hasError('user.username.invalid')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.username.invalid" /></p>");
          }
          if(hasError('user.username.blank')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.username.blank" /></p>");
          }
          if(hasError('user.password.blank')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.password.blank" /></p>");
          }
          if(hasError('user.password.invalid')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.password.invalid" /></p>");
          }
          if(hasError('user.adproblem')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.adproblem" /></p>");
          }
          if(hasError('user.kerberos.6')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.kerberos.6" /></p>");
          }
          if(hasError('user.kerberos.18')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.kerberos.18" /></p>");
          }
          if(hasError('user.kerberos.23')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.kerberos.23" /></p>");
          }
          if(hasError('user.kerberos.24')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.kerberos.24" /></p>");
          }
          $("#box").append("<form id=\"form\" method=\"POST\" action=\"http://localhost:8080/AppHub/auth/signIn\"></form>");
          $("#form").append("<input type=\"hidden\" name=\"targetUrl\" value=\""+targetUrl+"\" />");
          $("#form").append("<input type=\"hidden\" name=\"returnUrl\" value=\""+returnUrl+"\" />");
          $("#form").append("<input type=\"hidden\" name=\"ip\" value=\"${request.getRemoteAddr()}\" />");
          $("#form").append("<ul></ul>");
          $("#form > ul").append("<li><label for=\"username\">Username</label><div><input type=\"text\" name=\"username\" class=\"max text\" /></div></li>");
          $("#form > ul").append("<li><label for=\"password\">Password</label><div><input type=\"password\" name=\"password\" class=\"max text\" /></div></li>");
          $("#form > ul").append("<li><input type=\"submit\" name=\"submit\" class=\"button\" value=\"Sign In\" /></li>");
          $("#form > ul").append("<li><a style=\"color:#0088CC;\" href=\"#\">Forgot Password</a><span style=\"color:#0088CC;\"> | </span><a style=\"color:#0088CC;\" href=\"#\">Register</a><span style=\"color:#0088CC;\"> | </span><a style=\"color:#0088CC;\" href=\"#\">Unlock Account</a></li>");
      }

      //open dialog
      $("#modal").dialog({
          autoOpen: true,
          height: 450,
          width: 500,
          modal: true,
          resizable: false,
          position: 'center',
          draggable: false,
          closeOnEscape: false
      });
      //hide the title bar
      $(".ui-dialog-titlebar").hide();
    } else {
      $("body").append("<form id=\"form\" method=\"POST\" action=\""+targetUrl+"\"></form>");
      $("#form").append("<input type=\"hidden\" name=\"id\" value=\"${id}\" />");
      $("#form").append("<input type=\"hidden\" name=\"ip\" value=\"${request.getRemoteAddr()}\" />");
      $("#form").submit();
    }
  });
}

function getParameters() {
    var vars = [], hash;
    var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
    for(var i = 0; i < hashes.length; i++) {
        hash = hashes[i].split('=');
        vars.push(hash[0]);
        vars[hash[0]] = hash[1];
    }
    return vars;
}

function getParameter(name) {
  return getParameters()[name];
}

function getErrors() {
  if(getParameter('error') != null) {
    return getParameter('error').split(';');
  } else {
    return null
  }
}

function hasError(code) {
  var errors = getErrors();
  if(getErrors() != null) {
    for(var i = 0; i < getErrors().length; i++) {
      if(errors[i] == code) {
        return true;
      }
    }
  } else {
    return false;
  }
}

Could it be cleaner? Sure and when I hone in my JQuery skills I will definitely clean it up, but for now it works. =)

Posted in: REST, grails, javascript

RESTClient 2.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import java.net.HttpURLConnection;

class RESTClient {
    String url
    String method = "GET"
    String body //I want the String representation of the object you want to send (either JSON or XML)
    String data //This is where the response data is stored
    Node xml //Store the processed response for xml in here
    Integer status //Where the status code is held, like 200, 201, 404, 400, etc...
    Map headers = ["Content-Type":"text/xml", "Accept":"text/xml"]
    Map params
    HttpURLConnection request
   
   
    def makeRemoteCall() {
        request = new URL(url).openConnection()
   
        //This is set to GET by default
        request.setRequestMethod(method)
   
        //Use the each closure to set all the requestHeader values
        headers.each { name, value ->
            request.setRequestProperty(name, value)
        }
       
        //Check to see what method you are performing, if post or put then you need to write out to the stream.
        if(method in ['POST', 'PUT']) {
            request.doOutput = true
            request.outputStream.withWriter("ASCII") { stream ->
                stream << body //Hopefully your body is well formatted xml or json =D
            }
        }
       
        request.connect()
        status = request.getResponseCode()
        if(status == 200 || status == 201) {
            if(method in ['GET', 'POST', 'PUT']) {
                data = request.content.text
                if(headers["Content-Type"] in ["text/xml", "application/xml"] && data) {
                    xml = new XmlParser().parseText(data)
                }
            }
        }
        return this
    }
   
}

Posted in: REST, groovy

Restful Client update

So I have just finished the first class of my rest client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import groovy.xml.StreamingMarkupBuilder
import java.net.HttpURLConnection;

import apphub.util.RESTClient

/**
 * Created by IntelliJ IDEA.
 * User: Chauncey-JL
 * Date: Jan 13, 2010
 * Time: 11:59:39 AM
 * To change this template use File | Settings | File Templates.
 */

class AccessLevel {
    String appCode
    String accessTypeCode
    String accessLevelCode
    String displayName
   
    def static list(appId, accessTypeCode) {
        def accessLevels = []
        def client = new RESTClient(url:"http://localhost:8080/AppHub/accessLevel/${appId}/${accessTypeCode}").makeRemoteCall()
        if(client.status == HttpURLConnection.HTTP_OK) {
            client.xml.children().each { list ->
                def accessLevel = new AccessLevel()
                list.children().each { child ->
                    accessLevel.setProperty(child.name(), child.text())
                }
                accessLevels.add(accessLevel)
            }
        }
        return accessLevels
    }
   
    def static get(appId, accessTypeCode, accessLevelCode) {
        def client = new RESTClient(url:"http://localhost:8080/AppHub/accessLevel/${appId}/${accessTypeCode}/${accessLevelCode}").makeRemoteCall()
        if(client.status == HttpURLConnection.HTTP_OK) {
            def accessLevel = new AccessLevel()
            client.xml.children().each { child ->
                accessLevel.setProperty(child.name(), child.text())
            }
            return accessLevel
        }
    }
   
    def save() {
        def client = new RESTClient(url:"http://localhost:8080/AppHub/accessLevel", method:"POST", body:this.toXML().toString()).makeRemoteCall()
        if(client.status == HttpURLConnection.HTTP_CREATED) {
            return true
        } else {
            return false
        }
    }
   
    def update() {
        def client = new RESTClient(url:"http://localhost:8080/AppHub/accessLevel/${this.appCode}/${this.accessTypeCode}/${this.accessLevelCode}", method:"PUT", body:this.toXML().toString()).makeRemoteCall()
        if(client.status == HttpURLConnection.HTTP_OK) {
            return true
        } else {
            return false
        }
    }
   
   
    def delete() {
        def client = new RESTClient(url:"http://localhost:8080/AppHub/accessLevel/${this.appCode}/${this.accessTypeCode}/${this.accessLevelCode}", method:"DELETE", body:this.toXML().toString()).makeRemoteCall()
        if(client.status == HttpURLConnection.HTTP_OK) {
            return true
        } else {
            return false
        }
    }
   
    private toXML() {
        StreamingMarkupBuilder builder = new StreamingMarkupBuilder();
        def accessLevel = {
            mkp.xmlDeclaration()
            accessLevel {
                appCode(this.appCode)
                accessTypeCode(this.accessTypeCode)
                accessLevelCode(this.accessLevelCode)
                displayName(this.displayName)
            }
        }
        builder.bind(accessLevel)
    }
   
    def String toString() {
        this.toXML()
    }
   
}

And the test class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package apphub.client

//def accessLevel = new AccessLevel(appCode:'LEGALDOC', accessTypeCode:'ACCESS_LVL', accessLevelCode:'TEST', displayName:'TEST')
//accessLevel.save()

AccessLevel.list('LEGALDOC', 'ACCESS_LVL').each {
    println "APPCODE:${it.appCode}"
}

println "AccessLevel.get().displayName:"+AccessLevel.get('LEGALDOC', 'ACCESS_LVL', 'USER').displayName

def lvl = new AccessLevel(appCode:'LEGALDOC', accessTypeCode:'ACCESS_LVL', accessLevelCode:'TEST', displayName:'TEST')
println "AccessLevel.save():${lvl.save()}"

lvl.displayName = 'Updated Display Name'
println "AccessLevel.update():${lvl.update()} --- Display Name:${lvl.displayName}"

Posted in: groovy, push

My own version of the RESTClient… Without all of the jars!

Ok here is the rough version I came up with this afternoon. It will definitely get better as I move deeper into my API.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package apphub.util;

import java.net.HttpURLConnection;

class RESTClient {
    String url
    String method = "GET"
    String body //I want the String representation of the object you want to send (either JSON or XML)
    String data //This is where the response data is stored
    Node xml //Store the processed response for xml in here
    Integer status //Where the status code is held, like 200, 201, 404, 400, etc...
    Map headers = ["Content-Type":"application/xml", "Accept":"text/xml"]
    Map params
    HttpURLConnection request
   
   
    def makeRemoteCall() {
        request = new URL(url).openConnection()
   
        //This is set to GET by default
        request.setRequestMethod(method)
   
        //Use the each closure to set all the requestHeader values
        headers.each { name, value ->
            request.setRequestProperty(name, value)
        }
       
        //Check to see what method you are performing, if post or put then you need to write out to the stream.
        if(method in ['POST', 'PUT']) {
            request.doOutput = true
            request.outputStream.withWriter("ASCII") { stream ->
                stream << body //Hopefully your body is well formatted xml or json =D
            }
        } else {
        }
       
        request.connect()
        data = request.content.text
        if(headers["Content-Type"] in ["text/xml", "application/xml"] && data) {
            xml = new XmlParser().parseText(data)
        }
        status = request.getResponseCode()
        return this
    }
   
}

And here is how I am using it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    def static list(appId, accessTypeCode) {
        def accessLevels = []
        def client = new RESTClient(url:"http://localhost:8080/AppHub/accessLevel/${appId}/${accessTypeCode}").makeRemoteCall()
        if(client.status == HttpURLConnection.HTTP_OK) {
            client.xml.children().each { list ->
                def accessLevel = new AccessLevel()
                list.children().each { child ->
                    accessLevel.setProperty(child.name(), child.text())
                }
                accessLevels.add(accessLevel)
            }
        }
        return accessLevels
    }
   
    def static get(appId, accessTypeCode, accessLevelCode) {
        def client = new RESTClient(url:"http://localhost:8080/AppHub/accessLevel/${appId}/${accessTypeCode}/${accessLevelCode}").makeRemoteCall()
        if(client.status == HttpURLConnection.HTTP_OK) {
            def accessLevel = new AccessLevel()
            client.xml.children().each { child ->
                accessLevel.setProperty(child.name(), child.text())
            }
            return accessLevel
        }
    }

Pretty simple =D

Posted in: REST, groovy

Fun with Groovy and RESTClient

So I have finished up the basic API for our user management system in grails so it is now time to switch gears and start working on the client. I decided to go with groovy (hey I need to hone in my skills =P) so I went off to find out how other people were attaching onto RESTful apis. Well it seems that most people are using the RESTClient found in the HTTPBuilder library. It has WAY more dependencies than I would like to have but I will worry about that later. Here is the first consuming method I have written using the client.

1
2
3
4
5
6
7
8
9
10
11
12
13
    def static list(appId, accessTypeCode) {
        def accessLevels = []
        def client = new RESTClient("http://localhost:8080/AppHub/accessLevel/${appId}/${accessTypeCode}")
        def resp = client.get(contentType:ContentType.XML, query:[format:'xml'])
        resp.data.children().each { list ->
            def accessLevel = new AccessLevel()
            list.children().each { child ->
                accessLevel.setProperty(child.name, child.text())
            }
            accessLevels.add(accessLevel)
        }
        return accessLevels
    }
1
println AccessLevel.list('LEGALDOC', 'ACCESS_LVL').size()

Pretty straight forward and the code is much shorter than it would be in Java (or heck even in Groovy) if I needed to manually setup all of the HTTP/URL connection and parse the response back.

Notice that I am using some metaprogramming to dynamically build AccessLevel objects from the responding XML. This works for this particular example and will continue to work for most of the API. However, if I ever return a deeper xml tree (User -> List of some object -> object) It will take a bit more finesse.

Posted in: REST, groovy

Testing Restful grails controllers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import grails.test.*
import grails.converters.XML;
import grails.converters.JSON;


class UserControllerTests extends GroovyTestCase {
   
    void testGETXMLResponse() {
        def controller = new UserController()
        def existingUser = new User(username: "fred", password: User.encrypt("letmein"), firstName: "Fred", lastName: "Flintstone", middleName: "T", phone: "555-555-5555", email: 'fred@flintstone.com', activationDate: new Date(), logonFailureCount: 0).save()
        controller.request.method = 'GET'
        controller.request.contentType = 'text/xml'
        controller.params.format = 'xml'
        controller.params.id = existingUser.id
        controller.show()
        def user = new XmlSlurper().parseText(controller.response.contentAsString)
        assert user.@id == existingUser.id //we have to use the .@id because id is stored as an attribute to the user tag
        assert user.phone.text() == existingUser.phone //we have to use phone.text() because it is stored as a value of the phone tag.
    }
   
    void testGETJSONResponse() {
        def controller = new UserController()
        def existingUser = new User(username: "wilma", password: User.encrypt("letmein"), firstName: "Wilma", lastName: "Flintstone", middleName: "T", phone: "555-555-5555", email: 'wilma@flintstone.com', activationDate: new Date(), logonFailureCount: 0).save()
        controller.request.method = 'GET'
        controller.request.contentType = 'text/json'
        controller.params.format = 'json'
        controller.params.id = existingUser.id
        controller.show()
        def user = JSON.parse(controller.response.contentAsString)
        assert user.id == existingUser.id //this is how we access json text
        assert user.phone == existingUser.phone
    }
   
    void testPUTXMLResponse() {
        def controller = new UserController()
        def existingUser = new User(username: "dino", password: User.encrypt("letmein"), firstName: "Dino", lastName: "Flintstone", middleName: "T", phone: "555-555-5555", email: 'dino@flintstone.com', activationDate: new Date(), logonFailureCount: 0).save()
        existingUser.phone = '555-555-1111'
        def xml = existingUser as XML
        controller.request.method = 'PUT'
        controller.request.contentType = 'text/xml'
        controller.params.format = 'xml'
        controller.request.content = xml.toString().getBytes()
        controller.request.getAttribute("org.codehaus.groovy.grails.WEB_REQUEST").informParameterCreationListeners()
        controller.params.id = existingUser.id
        controller.update()
        def user = XML.parse(controller.response.contentAsString)
        assert user.@id == existingUser.id
        assert user.phone.text() == existingUser.phone
    }
   
    // I have submitted a JIRA issue regarding this test not working.  http://jira.codehaus.org/browse/GRAILS-5585
    /*
     void testPUTJSONResponse() {
     def controller = new UserController()
     def pebbles = new User(username:"pebbles", password:User.encrypt("letmein"), firstName:"Pebbles", lastName:"Flintstone", middleName:"T", phone:"555-555-5555", email:'pebbles@flintstone.com', activationDate:new Date(), logonFailureCount:0, deactivationDate:null).save()
     def builder = new JSONBuilder()
     def result = builder.build { user = pebbles }
     controller.request.method = 'PUT'
     controller.request.contentType = 'application/json'
     controller.params.format = 'json'
     controller.request.content = result.toString().getBytes()
     controller.request.getAttribute("org.codehaus.groovy.grails.WEB_REQUEST").informParameterCreationListeners()
     controller.params.id = '4'
     controller.update()
     def user = JSON.parse(controller.response.contentAsString)
     assert user.id == 4
     assert user.phone == '555-555-1111'
     }
     */

   
    void testPOSTXMLResponse() {
        def controller = new UserController()
        def existingUser = new User(username: "barney", password: User.encrypt("letmein"), firstName: "Barney", lastName: "Rubble", middleName: "T", phone: "555-555-5555", email: 'barney@rubble.com', activationDate: new Date(), logonFailureCount: 0)
        def xml = existingUser as XML
        controller.request.method = 'POST'
        controller.request.contentType = 'text/xml'
        controller.params.format = 'xml'
        controller.request.content = xml.toString().getBytes()
        controller.request.getAttribute("org.codehaus.groovy.grails.WEB_REQUEST").informParameterCreationListeners()
        controller.create()
        def user = XML.parse(controller.response.contentAsString)
        assert user.username.text() == "barney"
    }
   
    // I believe this is not working due to the same bug for the PUT test. http://jira.codehaus.org/browse/GRAILS-5585
    /*
     void testPOSTJSONResponse() {
     def controller = new UserController()
     controller.request.method = 'POST'
     controller.request.contentType = 'text/json'
     controller.params.format = 'json'
     controller.request.content = '{"activationDate":"2009-12-09T15:28:37Z","activeDirectoryUsername":null,"class":"User","createdBy":null,"deactivationDate":null,"disabled":false,"email":"betty@rubble.com","firstName":"Betty","id":1,"lastAccessDate":null,"lastName":"Rubble","lastUpdatedBy":null,"lastUpdatedDate":null,"logonFailureCount":0,"middleName":"T","mustChangePassword":false,"password":"FtPJCHN","phone":"555-555-5555","useActiveDirectory":false,"username":"betty","version":null}'.getBytes()
     controller.create()
     def user = JSON.parse(controller.response.contentAsString)
     println controller.response.contentAsString
     assert user.id == 5
     assert user.username == "betty"
     }
     */

   
    void testDELETEResponse() {
        def controller = new UserController()
        def existingUser = new User(username: "fred", password: User.encrypt("letmein"), firstName: "Fred", lastName: "Flintstone", middleName: "T", phone: "555-555-5555", email: 'fred@flintstone.com', activationDate: new Date(), logonFailureCount: 0).save()
        controller.request.method = 'DELETE'
        controller.params.id = existingUser.id
        controller.delete()
        assert controller.response.status == 200
    }
   
   
}

Posted in: REST, grails, groovy