Yeast Server for Java

Example

Imagine that you are developing an e-commerce application that will be used to sell books. At some point, you must present the typical shopping basket page, where you show the state of his shopping to the user. The visual appearance of this page could be as shown in the following picture.



Click here to see the template in your browser

This is exactly what the graphical HTML designer has created for your application. It is a Yeast template, in which the designer has marked as dynamic the following content (this is the content of the template model section):

<script yst="model">
  customer="FRANCISCO GARCIA" 
  time = '2-17, 2006 12:37';
  books = new Array();
  books[0] = new Book('0','Yeast Manual', 'F. J. García', 17.46,'Easy  Books');
  books[1] = new Book('1','HTML for dummies', 'John Smith', 19.95, 'XX Pub.');
</script>

That is, there are two strings (the customer's name and a date) and a list of books. The HTML designer has structured a book as a JavaScript object with the following features:

<script yst="init">
  function Book(id, b_title, b_author, b_price, b_publisher) {
    this.id = id;
    this.b_title = b_title;
    this.b_author = b_author;
    this.b_price = b_price;
    this.b_publisher = b_publisher;
  }
</script>

The application developer needs this last definition in order to understand the meaning of each value that appears in the preceding new Book(... JavaScript statements that appear in the model section. If this implicit semantic meaning was not enough, the HTML designer might include comments inside the code.

The application programmer does not need more information to develop a servlet that renders the previous page. Taking as input parameters the identifiers of the books that must be shown in the page, he can develop the following servlet (imagine that those identifiers come in the form of CGI parameters named ids):

1.  package ystsrv.demo;
2. 
3.  import java.util.*;
4.  import javax.servlet.http.HttpSession;
5.  import org.ystsrv.servlet.YSTContext;
6.  import org.ystsrv.servlet.YSTServlet;
7. 
8.  public class ShowBasket extends YSTServlet {
9.
10.  protected String handle(YSTContext context) {
11.    // Retrieve the customers name from the session
12.    HttpSession session = context.getRequest().getSession(false);
13.    String customer = (String) session.getAttribute("customer");
14.
15.    // Get input params
16.    String[] ids = context.getRequest().getParameterValues("ids");
17.    
18.    // Business layer method that returns the list of Book objects
19.    List books = processBooksInBasket(ids);
20. 
21.     // Push the data needed for the response onto the template.
22.    context.setResponseContentType("text/html");
23.    context.toResponse(books);
24.    context.toResponse("customer", customer); 
25.    context.toResponse("time", new Date(),"M-d, yyyy hh:mm");
26.
27.    // Returns the name of the template to be used in the response
28.    return "Basket";
29.  }
30. }

In the above code, we have highlighted in red the code related to Yeast Server. You must draw your attention on the following line numbers:

8 The Yeast server provides the programmers with an especial servlet class, org.ystsrv.servlet.YSTServlet, which can be used as a base for implementing any servlet that uses Yeast templates. Its main purpose is to manage common actions related to the template retrieval and its sending to the requesting client's browser.
10 A servlet class extending this class does not need to implement the usual doGet or doPost servlet methods. YSTServlet class defines an abstract method called handle(YSTContext) that, typically, will implement the processing of the input parameters, will obtain the data necessary to fulfil the template model section and will select the template to be used as view. This method receives as parameter a org.ystsrv.servlet.YSTContext object, in which some commonly accessed services such as the request and the response objects are done available (see lines 12 and 16).
19 The servlet invokes the suitable business logic services to retrieve the list of books that the user has in his shopping basket. In this case, this method returns a list of Book objects. This is a typical bean class with the following properties: id, title, price, publisher and author, being this last author another bean with properties name and surname.
22 The servlet sets the response content type to text/html.
23 - 25 The only action the servlet must perform to process the response is to push the required data onto the YSTContext (this object will push the data onto the template on your behalf). This is done by means of toResponse methods. In this case we are pushing the list of books (23), the customer's name (24) and the instant of the response (25). These two last values are associated to the names of the JavaScript variables that are being used in the Yeast template model section (customer and time in this case). Moreover, the date value is associated to a formatting pattern that will be used in the date transcription.
28 To finish the execution, the handle method must return the name of the Yeast template that, once processed with the data pushed previously, will be sent to the client's browser. In this case the chosen template is Basket.

And that's all. Well, to be completely honest, this is almost all. The Yeast templates processing will not be possible without the Transformers. Transformers are special classes that will adapt objects of the application business model to the template design model. Objects must be transformed into the string whose format has been imposed by the HTML designer. Transformed versions of objects will be inserted in the model section of a Yeast template, substituting the test model section designed by the HTML designer.

The set of transformers a template needs can be statically set up in a configuration file named YSTConfig.xml (if you hate configuration file read the next subsection). In that file you specify for each template its identifier, its file location (optional) and the set of transformers it needs.

Yeast server provides the programmer with three general transformers: the NamedDataTransformer (that acts on the NamedData objects), which is implicitly associated to every template; and the BeanTransformer and BeanCollectionTransformer, specialized in the transformation of beans and collections of beans respectively. These two last transformers accept as a building parameter a string with the format to be applied in the transformation. In that format string you can insert, enclosed between curly braces ({}), references to the bean properties. To refer to a certain property you must use its name. The transformer will substitute these references with the values of the bean properties.

Remember the content of the model section of the Yeast template that the HTML designer created. It was:

books = new Array();
books[0] = new Book('0','Yeast Manual', 'Francisco J. García Izquierdo', 17.46,'Easy  Books');
books[1] = new Book('1','HTML for dummies', 'John Smith', 19.95, 'XX Pub.');

Here the application programmer has the format he needs to configure the BeanCollectionTransformer that the application will use for the Basket template. This is definitely the last piece of work that the programmer needs to do, and it can be done without programming. He knows the business model classes (Book in this case). He knows the name of the class properties. Therefore he can easily configure the format string (this a process of copying, pasting and substituting the concrete values for the suitable bean property, or properties, references). That is, from this definition in the designer's model:

books[1] = new Book('1', 'HTML for dummies', 'John Smith', 19.95, 'XX Pub.'); 

he can easily specify the following format string:

books[{#i}] = new Book({id}, {title}, {author.name}+' '+{author.surname}, {price}, {publisher});

Note that the author is obtained concatenating the Book.author.name and the Book.author.surname properties. Sometimes you will need to include JavaScript code in the format string in order to accomplish the transformation.

BeanCollectionTransformer will iterate over the books collection values. If you want to refer to the implicit counter associated to the iteration process, you can use the predefined internal variable #i. In this case this variable is used to generate the array index. So the configuration info for the template of identifier Basket (see line 28 in the servlet code) will be as follows:

<template id="Basket">
  <location>Basket.html</location>
  <transformer class="org.ystsrv.transformer.BeanCollectionTransformer">
    <param name="baseClass">ystsrv.demo.Book</param>
    <param name="header">books = new Array();</param>
    <param name="format">books[{#i}] = new Book({id}, {title}, {author.name} + ' ' + 
{author.surname}, {price}, {publisher}); </param>
  </transformer>
</template>

You specify the class of the objects that the transformer will process by means of the parameter baseClass. NOTE: in case you haven't guessed by you own, the other parameter (header) that is configured for the BeanCollectionTransformer is the initialization code that appears in the template model section before the construction of the JavaScript Book objects.

Pack the servlet and business classes with the Yeast template files and deploy the application in your favourite application server. Your application is ready to run.

Avoiding configuration files

Note that the core transformation details are described in a configuration file, YSTConfig.xml, that can be tuned without recompiling the application. If you consider there is too much a hassle with the use of configuration files, there is a variation on the last example that do not require to use them. This is simpler to develop but more difficult to maintain, since any change in the transformation rules must be coded into the presentation logic.

1.  package ystsrv.demo;
2.
3. import java.util.*;
4. import javax.servlet.http.HttpSession;
5. import org.ystsrv.servlet.YSTContext;
6. import org.ystsrv.servlet.YSTServlet;
7.
8. public class ShowBasket extends YSTServlet {
9.
10. protected String handle(YSTContext context) {
11. // Retrieve the customers name from the session
12. HttpSession session = context.getRequest().getSession(false);
13. String customer = (String) session.getAttribute("customer");
14.
15. // Get input params
16. String[] ids = context.getRequest().getParameterValues("ids");
17.
18. // Business layer method that returns the list of Book objects
19. List books = processBooksInBasket(ids);
20.
21. // Push the data needed for the response onto the template 22. context.setResponseContentType("text/html");
23. context.toResponse("books", books, "new Book({id}, {title}, {author.name} + ' ' + {author.surname}, {price}, {publisher})");
24. context.toResponse("customer", customer);
25. context.toResponse("time", new Date(),"M-d, yyyy hh:mm");
26.
27. // Returns the name of the template to be used in the response
28. return "Basket";
29. }
30. }

The only change in the servlet appears in line 23, where another version of the toResponse method is used. This method allows programmers to specify the transformation format string with which the list of Book objects will be transformed. This format string is similar to the one specified in the previous YSTConfig.xml file. The only difference is that it only specifies the format for each one of the list elements, which will be a component of an array called books, which is the array that the graphic designer created to hold the books data. In this case the obtaines transformation is slightly different, but equivalent:

<script yst="model">
  customer="FRANCISCO GARCIA"
  time = '2-17, 2006 12:37';
  books = [new Book('0','Yeast Manual', 'Francisco J. García Izquierdo', 17.46,'Easy  Books'),
           new Book('1','HTML for dummies', 'John Smith', 19.95, 'XX Pub.')];
</script>