ptg 132 Chapter 6 Response Rendering Header Footer Header Footer Nav Nav Business Logic Dynamic Content Dynamic Content Figure 6.8 Updated assembly flow Listing 6.10 /public/quiz/index.gt—Simple Redirect to Business Logic <% invokeMethod("quiz.groovy", "go", null) %> Listing 6.11 /app/scripts/quiz.groovy—Business Logic Controller def go() { if ( request.params.quiz[] == null ) { request.params.page = "home" } else { request.params.page = "quiz" request.params.quiz = getQuizData() } // This would be pulled from DB or config file request.params.nav = [ 'home' : 'Home', 'php' : 'PHP Quiz', 'groovy': 'Groovy Quiz', 'dojo' : 'Dojo Quiz', 'smash' : 'sMash Quiz' ] request.view = "quiz/theme/main.gt" render() } Download from www.wowebook.com ptg Using Views for Rendering 133 def getQuizData() { // Determine quiz, and get questions. return [ 'title': 'PHP Quiz', 'questions': [ [ 'q': 'A PHP script block looks like:', 'a': [ '<?php ?>', '<$php $>', '<php{ }>', '<%php %>' ] ], /*Other questions*/ [:], [:], [:], [:] ] ] } As you can see, all of our controller and business logic is contained with the quiz.groovy script. We essentially set our target content page and push a view variable onto the request. Finally, we render our theme view template. I won’t repeat the code for the theme, but it’s essen- tially the page load of HTML and includes our header, footer, and navigation, which also had its business-specific content moved into our controller. The only difference now is we dynamically pull in our target content page, as shown in Listing 6.12. The nice thing here is that it would be trivial to modify this to handle either a single or multidimensional array of target pages to include, so we can have a simple mash-up environment. Listing 6.12 /app/views/quiz/theme/main.gt (fragment)—Include Our Content Page <td class="content"> <% request.view = 'quiz/' + request.params.page[] + '.gt' render() %> </td> The last item we should look at is the new quiz.gt view file, shown in Listing 6.13. In our controller file, we defined a complex quiz data object that we put on the request. This allows our Download from www.wowebook.com ptg 134 Chapter 6 Response Rendering Figure 6.9 PHP quiz rendered from theme quiz view to be ignorant of the data contents and thus makes it highly reusable. The first page of our PHP quiz can now be seen in Figure 6.9. In a real application, we would enable the viewing and answering of other questions, but this should give you the basic foundation of a well- structured application using views. Listing 6.13 /app/views/quiz/quiz.gt—Quiz View Page <% def quiz = request.params.quiz[] def currentQuestion = 1 // get from req param def question = quiz.questions[ currentQuestion-1 ] %> <h1>${quiz.title}</h1> <h3>Question ${currentQuestion} of ${quiz.questions.size()}</h3> <p>${question.q}</p> <% question.a.each() { %> <input type="radio" name="q${currentQuestion}" /> ${zero.util.XMLEncoder.escapeXML(it)}<br/> <% } %> <br/> <button>Next ></button> As a general rule, if an entity is accessed directly via URL, it belongs in /public; otherwise, you should probably think about moving it into /app/scripts and /app/views as appropriate. Download from www.wowebook.com ptg Managing Errors 135 Typically, what I do is use an index file in /public, which simply invokes the appropriate business logic in /app/scripts. The business logic then assembles the response as a collection of view pieces from /app/views into the response. Following this structure will provide your applications with a clean separation of concerns and aligns well to the standard Model-View-Controller design pattern. Managing Errors There is an old saying that says “stuff happens,” or something like that. No matter how careful you are in developing your application, things will eventually go wrong. These may either be bugs in your code or other external influences that you did not account for. I have seen applications where the majority of the code is dedicated to nothing but trying to address every conceivable error condition that could arise, and the actual business logic is buried among all this exception handling code. Most Web 2.0 applications tend to eschew this gated and walled environment and take a more relaxed attitude toward errors. Basically, if something fails, so be it, and try again later. There have even been several papers and articles written that go so far as to question if error trapping and handling is even a worthwhile effort. I guess I sit somewhere in the middle. Again, this is dependent on your style and the nature of the application you are creating. If your applica- tion is managing financial transactions, error handling should probably be fully baked in. But a Twitter-monitoring application can probably be somewhat more relaxed in its error checking. WebSphere sMash enables you to be as strict or casual in your error handling as you see fit. Both PHP and Groovy support the normal “try/catch” exception handling, so you can at a mini- mum trap expected or unreliable situations. You can then either choose to deal with error in an application-centric way, or simply bail out by setting an error code and letting WebSphere sMash deliver the bad news to the user. Often, this is actually the easiest and most pragmatic way to deal with truly exceptional conditions. Here is an example that we can walk through to see how we can deal with errors and also promote them for WebSphere sMash to deal with. In our example, we have a request that comes in, and we need to extract some data from a notoriously unreliable legacy system. Let’s first create a Groovy template file to act as our view controller, as shown in Listing 6.14. Listing 6.14 legacy1.gt—Get Some Data from “That” System <% def getLegacyData( ask ) { if ( ask == "please" ) { return "My pleasure. Your data has been sent." } throw new Exception("You should really ask nicely!"); } Download from www.wowebook.com ptg 136 Chapter 6 Response Rendering Figure 6.10 Application error try { def answer = getLegacyData( request.params.ask[] ) %> <h1>Legacy Data</h1> <h2>Answer: ${answer}</h2> <% } catch( Exception e ) { request.view = "error" request.status = HttpURLConnection.HTTP_SERVER_ERROR //request.error.view = "errorLegacy.gt" request.error.message = "This system never works: " + e.message render() } %> Let’s go through this sample and see how it works. When a request comes into legacy.gt, we make a call to our legacy system, passing in the “ask” parameter. Our legacy system is rather testy and does not always properly respond to our requests. If we ask nicely by saying “please,” we get our expected data returned. Otherwise, it throws an exception. In our catch block, we set our request view to “error,” which is a special reserved view renderer type in WebSphere sMash. We then set our status to a general error value of 500 (HTML_SERVER_ERROR), to which Web- Sphere sMash will automatically define our error view page to error500.* upon rendering. We discuss these status codes more thoroughly in Chapter 7. Instead of setting the error status code, we could also just directly set the error view page. Next, we provide an error message and tell WebSphere sMash to render the error. As you can see in Figure 6.10, we get a standard error page informing us of the situation. Download from www.wowebook.com ptg Managing Errors 137 So, what’s happening behind the scene here? When you set the request.view to “error” and call render, WebSphere sMash looks in the “/app/errors” virtual directory for an error document matching the error status number, which in our example is “error500.*” for a SYSTEM FAILURE. If that document is not found, a default “error.*” is used. Because we haven’t yet defined any of these error documents in our application, a default one is used. There is a lot we can do to improve on our current design, so let’s refactor things a little and let WebSphere sMash help us out here. First, let’s play fast and loose and get rid of all our error checking in our application. Listing 6.15 shows our new legacy2.gt file. Listing 6.15 /public/legacy2.gt—Nothing But Application Code <% answer = invokeMethod("legacySystem", "getData", request.params.ask[] ) %> <h1>Legacy Data</h1> <h2>Answer: ${answer}</h2> Well, that’s certainly a lot cleaner. We’ve extracted out the getLegacyData method into a reusable script and blissfully ignored any exceptions that may get thrown. We are now left with nothing but application logic and presentation. We can’t ask for much more than that. So, how do we cleanly deal with those exceptions that are sure to crop up? It’s simple: Let’s put our error handling into an error document, as shown in Listing 6.16. Listing 6.16 /app/errors/error.gt—Errors Is as Errors Do <style> .banner { background-color:#00f; color:#fff; width:100%; text-align:center; } .error { color:#f00; } </style> <h2 class='banner'>ACME Quiz Factory</h2> <h1 class='error'>${request.error.message[]}: ${request.error.exception[].message}</h1> <h2 class='banner'> </h2> Now when we call the legacy application and don’t provide the proper etiquette—make that parameter—we get an amazingly stylized error message in response, as shown in Figure 6.11. A real application may want to be a little more aware of error conditions than we show here, but you should be able to see how WebSphere sMash provides a clean separation of concerns with application logic and error handling. Download from www.wowebook.com ptg 138 Chapter 6 Response Rendering Figure 6.11 Custom error page Data Rendering Rendering data for RESTful requests is a fundamental process within WebSphere sMash. There are two default data renderers available within WebSphere sMash to aid you in servicing REST requests: JSON, which stands for JavaScript Object Notation, and XML. The process to respond with one of these data formats is essentially the same. First, you set your view to the desired response type. Then you set your output with the data to be returned, and finally render the out- put. Under the covers, WebSphere sMash will automatically convert the data from the native internal structure to the desired type. Be aware that WebSphere sMash uses introspection to per- form this data conversion, and complex types may cause a performance hit. Although the primary use of data rendering is within REST services, we’ll cover the data aspects of it here and expose its proper use when discussing REST in Chapter 7. There are no hard and fast rules for deciding which data format you should support in your application. As a rough guide, if you have a well-defined, browser-based client server application, JSON might be the right solution. If you have potentially nonbrowser clients and other undefined consumers of your data, XML is probably a better solution due to its universal acceptance as a data interchange format. Specific purpose-defined schemas are also available for XML. One such schema is ATOM, which is the standard for read/write syndication of news feeds. The bottom line is to do some research and choose one or more data formats to suit the widest audience possible. You w ill see t hat WebSphere sMash m ake s i t e asy t o support m ul tiple r es ponse t yp es. JSON Data Rendering Disregarding the acronym, JavaScript Object Notation (JSON) is a de-facto standard for data interchange with AJAX client application. AJAX, which stands for Asynchronous JavaScript and XML, was initially designed to integrate XML data streams with browsers. Due to performance efficiencies and ease of use, JSON soon became the dominant data structure for AJAX requests because it’s a native object type in JavaScript. Details on the JSON syntax can be found at json.org, along with an API for just about every language conceivable. There is a single configuration option that affects the rendering of JSON data. It is a Pretty Print option. By default, WebSphere sMash will render JSON data into a single line of text. With Pretty Print enabled, the data is expanded out to nicely show the structure of the data. This makes it easier for humans to read the data, but computers don’t care. There is a slight bandwidth increase by enabling this setting, but that is basically negated whenever the wire compression is enabled. The configuration option is shown in Listing 6.17. Download from www.wowebook.com ptg JSON Data Rendering 139 Listing 6.17 Enable Pretty Print for JSON Data # Turn on nice formatting of JSON data /config/json/prettyPrint = true # If set, you should also enable over the wire compression /config/compressResponse = true To render JSON data, you set the request view to the string ’JSON’ and then supply the native object to be rendered to the request.json.output slot (see Listing 6.18). When you render the output, the data object is automatically encoded (see zero.json.Json.encode). As you can see in Listing 6.19, the response has been Pretty Print formatted, and is very similar, but not exact, to the groovy data object created in the getQuizData function shown earlier in this chapter. Listing 6.18 JSON Rendering with Groovy def quiz = invokeMethod('quiz.groovy', 'getQuizData', null) request.view = 'JSON' request.json.output = quiz render() Listing 6.19 Resulting JSON Object { "title": "PHP Quiz", "questions": [ { "q": "A PHP script block looks like:", "a": [ "<?php ?>", "<$php $>", "<php{ }>", "<%php %>" ] }, { }, { }, { }, { } ] } Download from www.wowebook.com ptg 140 Chapter 6 Response Rendering We can also produce the same results using PHP, as shown in Listing 6.20. Assume that we reproduced our quiz.groovy script located at /app/scripts/ in PHP in the following example. The JSON output is exactly the same as in the Groovy example. Listing 6.20 JSON Rendering with PHP // // From: /app/scripts/quiz.php // Shown for Reference // <?php function getQuizData() { // Determine quiz, and get questions. return array( "title" => "PHP Quiz", "questions" => array( array( "q" => "A PHP script block looks like:", "a" => array('<?php ?>', '<$php $>', '<php{ }>', '<%php %>') ), array(), array(), array(), array() ) ); } ?> // // From: /public/dumpQuizPhp.php // <h1>Dump Quiz Data in PHP:</h1> <div style=’font:12px monospace; background-color:#ccc; margin:10px’> <?php include "quiz.php"; $quiz = getQuizData(); echo json_encode( $quiz ) ?> </div> Download from www.wowebook.com ptg XML Rendering 141 // // From: /app/resources/quizJson.php // If servicing as a rest resource // <?php include "quiz.php"; $quiz = getQuizData(); zput('/request/view', 'JSON'); zput('/request/json/output', $quiz); render_view(); ?> XML Rendering The most commonly used data interchange format has to be XML. Although it’s overly verbose, and cumbersome to process and manipulate, it’s well understood by everyone. The process to produce XML output is very similar to JSON rendering, with the exception of a few more options. Because every well-defined XML document has a root element, we need to supply that to the encoding process. We also have the option to supply ID references, which will insert a unique ID attribute value to each generated node. Listings 6.21 and 6.22 show our quiz data being encoded in XML for both Groovy and PHP. Figure 6.12 shows the results of our quiz ren- dered in XML. Figure 6.12 Results of XML rendering Download from www.wowebook.com . [ 'title': 'PHP Quiz', 'questions': [ [ 'q': 'A PHP script block looks like:', 'a': [ '<?php ?>', '<$php $>',. request.params.nav = [ 'home' : 'Home', 'php' : 'PHP Quiz', 'groovy': 'Groovy Quiz', 'dojo' : 'Dojo Quiz', &apos ;smash& apos;. => "A PHP script block looks like:", "a" => array('<?php ?>', '<$php $>', '<php{ }>', '<%php %>') ), array(),