Wednesday, May 30, 2012

Tapestry - Dependency Injection

Before we understand Dependency Injection in Tapestry, let us see some fundamentals.

Tapestry has the notion of Pages, Components and Services.

While all of them are POJOs, Tapestry assumes that the POJOs in /pages folder are Pages, those in /components are Components and /services are Services.

Pages and Components have their corresponding .tml files. A page can be rendered, but a component cannot be rendered. A component can be used by a page.

Services do not have .tml files. Instead, each Service has an explicit interface and implementation. Services provided functionality that other classes can use. For example, a service can implement a business computation, it can transform, filter, process data, do security checks, write logs, handle email communication, schedule events for data backup, etc. Essentially, anything that is not directly related to rendering a page or handling a page event (user clicking on a page) can be made a service.

Tapestry allows Pages, Components and Dependencies to all be injected into each other, using the following annotations:
@InjectPage
@InjectComponent
@InjectService

Additionally, it has the generic @Inject annotation which "automagically" picks up the correct thing to inject (except when it runs into ambiguity - but let us not worry about that now).

In this post, let us create a Service POJO and inject it.

In /src/main/java/org/confucius, create a folder 'services'

In /src/main/java/org/confucius/services, create a Interface GreetDAO.java, like this:

 package org.confucius.services;  
   
 import java.util.List;  
   
 import org.confucius.Greet;  
   
 public interface GreetDAO {  
      public List<Greet> getInternationalGreetsList();  
 }  
   

In /src/main/java/org/confucius/services, create a class GreetDAOImpl.java, like this:

 package org.confucius.services;  
   
 import java.util.ArrayList;  
 import java.util.List;  
   
 import org.confucius.Greet;  
   
 public class GreetDAOImpl implements GreetDAO {  
   
      public List<Greet> getInternationalGreetsList() {  
           List<Greet> greetList = new ArrayList<Greet>();  
             
           Greet englishGreet = new Greet("English", "Hello World!");  
           greetList.add(englishGreet);  
             
           Greet frenchGreet = new Greet("French", "Bonjour tout le Monde!");  
           greetList.add(frenchGreet);  
             
           Greet italianGreet = new Greet("Italian", "Buongiorno a Tutti!");  
           greetList.add(italianGreet);  
             
           Greet spanishGreet = new Greet("Spanish", "Hola Mundo!");  
           greetList.add(spanishGreet);  
             
           Greet swahiliGreet = new Greet("Swahili", "Jambo!");  
           greetList.add(swahiliGreet);  
             
           return greetList;  
      }  
   
 }  
   


Now we need to tell Tapestry to load GreetDAOImpl as an implementation of service GreetDAO.

For this we need to make a Module.

A Module is a place where services are defined, besides other things. Conveniently, a Module is just another POJO - one with a very specific name by convention: AppModule.java.

In /src/main/java/org/confucius/services, create a class AppModule.java, like this:

 package org.confucius.services;  
   
 import org.apache.tapestry5.ioc.ServiceBinder;  
   
 public class AppModule {  
        
      public static void bind(ServiceBinder binder){  
         binder.bind(GreetDAO.class, GreetDAOImpl.class);  
       }  
   
 }  
   


Note that there are other ways to define modules, but we will not worry about that right now. We will just use the default AppModule.java


Finally, we need to make one more change for this Module to get loaded.

By convention, we should name our Tapestry Filter in web.xml 'app'. So update your web.xml, like this:

 <!DOCTYPE web-app PUBLIC  
  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"  
  "http://java.sun.com/dtd/web-app_2_3.dtd" >  
   
 <web-app>  
   <context-param>  
     <param-name>tapestry.app-package</param-name>  
     <param-value>org.confucius</param-value>  
   </context-param>  
   
   <filter>  
     <filter-name>app</filter-name>  
     <filter-class>org.apache.tapestry5.TapestryFilter</filter-class>  
   </filter>  
   
   <filter-mapping>  
     <filter-name>app</filter-name>  
     <url-pattern>/*</url-pattern>  
   </filter-mapping>  
     
 </web-app>  
   


We are all set to inject our new Service.


Update your InternationalGreets.java class to use this GreetDAO service, like this:

 package org.confucius.pages;  
   
 import java.util.List;  
   
 import org.apache.tapestry5.annotations.Property;  
 import org.apache.tapestry5.ioc.annotations.Inject;  
 import org.confucius.Greet;  
 import org.confucius.services.GreetDAO;  
   
 public class InternationalGreets {  
   
   @Property  
   private Greet greet;  
   
      @Inject  
      private GreetDAO greetDAO;  
        
      public List<Greet> getInternationalGreetsList(){  
           return greetDAO.getInternationalGreetsList();  
      }  
 }  
   


If you now rebuild and redeploy HelloWorldTapestry, you will see the International Greets in a table, just like you did before.

However, this time Tapestry injected the GreetDAO service to make the greets available.

Tuesday, May 29, 2012

Tapestry - Grid

Tapestry uses the Grid component to display data tables.

Let us see this by displaying the International Greets in a Grid.

In /src/main/java/org/confucius, create a class Greet.java, like this:

 package org.confucius;  
   
 public class Greet {  
      private String language;  
      private String greeting;  
        
      public Greet(String language, String greeting) {  
           this.language = language;  
           this.greeting = greeting;  
      }  
   
      public String getLanguage() {  
           return language;  
      }  
        
      public void setLanguage(String language) {  
           this.language = language;  
      }  
        
      public String getGreeting() {  
           return greeting;  
      }  
        
      public void setGreeting(String greeting) {  
           this.greeting = greeting;  
      }  
 }  
   

Update InternationalGreets.java with a method to return a List of Greets, like this:

 package org.confucius.pages;  
   
 import java.util.ArrayList;  
 import java.util.List;  
   
 import org.apache.tapestry5.annotations.Property;  
 import org.confucius.Greet;  
   
 public class InternationalGreets {  
   
   @Property  
   private Greet greet;  
   
      public List<Greet> getInternationalGreetsList(){  
           List<Greet> greetList = new ArrayList<Greet>();  
             
           Greet englishGreet = new Greet("English", "Hello World!");  
           greetList.add(englishGreet);  
             
           Greet frenchGreet = new Greet("French", "Bonjour tout le Monde!");  
           greetList.add(frenchGreet);  
             
           Greet italianGreet = new Greet("Italian", "Buongiorno a Tutti!");  
           greetList.add(italianGreet);  
             
           Greet spanishGreet = new Greet("Spanish", "Hola Mundo!");  
           greetList.add(spanishGreet);  
             
           Greet swahiliGreet = new Greet("Swahili", "Jambo!");  
           greetList.add(swahiliGreet);  
             
           return greetList;  
      }  
 }  
   

Update InternationaGreets.tml to use the Grid component to display the greets, like this:

 <html t:type="layout" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">  
      <body>  
     <t:grid source="internationalGreetsList" row="greet"/>  
      </body>  
 </html>  
   

Since we are using the default Grid, i.e. there are no specific instructions, Grid will show each property of the Greet object in the order in which it appears in Greet.java. Each column will be sortable by default.

If you rebuild and redeploy HelloWorldTapestry, you will see the International Greets in a sortable table.

Tapestry - Navigation

Let us see how to navigate from one page to another in Tapestry.

Let us create two new pages.

In your /src/main/resources/org/confucius/pages, create a file called InternationalGreets.tml, like this:

 <html t:type="layout" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">  
      <body>  
         <p>   
       English: Hello World!   
       <br/>   
       French: Bonjour tout le Monde!   
       <br/>   
       Italian: Buongiorno a Tutti!   
       <br/>   
       Spanish: Hola Mundo!   
       <br/>   
       Swahili: Jambo!   
         </p>   
      </body>  
 </html>  
   

In your /src/main/java/org/confucius/pages, create a file called InternationalGreets.java, like this:

 package org.confucius.pages;  
   
 public class InternationalGreets {  
   
 }  
   


In your /src/main/resources/org/confucius/pages, create a file called AmericanGreets.tml, like this:

 <html t:type="layout" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">  
      <body>  
         <p>   
          Regular: Hi!   
       <br/>   
       Friendly: Whatsup!   
       <br/>   
       Extra Friendly: Hey, How are you doing!   
         </p>   
      </body>  
 </html>  
   

In your /src/main/java/org/confucius/pages, create a file called AmericanGreets.java, like this:

 package org.confucius.pages;  
   
 public class AmericanGreets {  
   
 }  
   


Update Index.tml to contain two new buttons, like this:

 <html t:type="layout" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">  
      <body>  
           <h2>${greeting}</h2>  
             
           <form t:id="greetForm">  
                <input t:id="internationalGreetsButton" t:type="submit" value="Show International Greets"/>  
                <input t:id="americanGreetsButton" t:type="submit" value="Show American Greets"/>  
           </form>  
      </body>  
 </html>  
   


Update Index.java to handle the two new buttons, like this:

 package org.confucius.pages;  
   
 import org.apache.tapestry5.annotations.Component;  
 import org.apache.tapestry5.corelib.components.Form;  
   
 public class Index {  
      private String greeting;  
      private Object formEventReturn;  
        
      @Component(id = "greetForm")  
      private Form form;       
        
      public Index() {  
           this.greeting = "Hello World, Tapestry!";  
      }  
   
      void onSelectedFromAmericanGreetsButton() {   
           formEventReturn = AmericanGreets.class;  
      }  
        
      void onSelectedFromInternationalGreetsButton() {   
           formEventReturn = InternationalGreets.class;  
      }  
   
      Object onSuccessFromGreetForm() {  
           return formEventReturn;  
      }  
        
      public void setGreeting(String greeting) {  
           this.greeting = greeting;  
      }  
   
      public String getGreeting() {  
           return greeting;  
      }  
        
 }  
   


Note that we have introduced three new methods, two which handle the button clicks, and one which handles the form submission.

We return the .class corresponding to the appropriate page - this tells Tapestry which page to navigate to.

If you rebuild and redeploy HelloWorldTapestry, you will see two buttons which navigate you to International and American greets.

Tapestry - Bean-side Components

Tapestry allows you to specify components in the bean, instead of in the template.

Let us create the Sliding Panel component in the bean. To refresh our memory, the bean is the Java class associated with the template.

Update your Layout.java class to include the SlidingPanel component, like this:

 package org.confucius.components;  
   
 import org.apache.tapestry5.annotations.Component;  
 import org.chenillekit.tapestry.core.components.SlidingPanel;  
   
 public class Layout {  
   
      @Component(parameters = {"subject=Chenillekit Greeter"})  
      private SlidingPanel panel1;  
        
 }  
   

Here, we have used annotations to tell Tapestry that 'panel1' is a SlidingPanel component, and its 'subject' property is 'Chenillekit Greeter'.

Now, update Layout.tml to use this Sliding Panel, like this:
 <html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">  
   <head>  
     <title>Tapestry</title>  
   </head>  
   <body>  
     <h3>Basic Tutorial Series</h3>  
     
           <div t:id="panel1">  
                  <t:body/>  
           </div>  
   
           <p>Visit Us @ www.projectconfucius.org</p>  
             
   </body>  
 </html>  


If you rebuild and redeploy HelloWorldTapestry, you will see the Sliding Panel just like before. Thus, there is no difference in presentation whether you specify a component in the template or in code.

Typically, you will specify the component in code when you want to configure it dynamically, or if you want to keep your template clean of configurations.

Tapestry - Chenillekit

There are several open source component libraries for Tapestry, like Tynamo, TapX, Chenillekit, etc.

We will use Chenillekit as a demo, and put the greeting inside a Sliding Panel.

Update your pom.xml to contain the Chenillekit dependency, like this:

 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">  
       <modelVersion>4.0.0</modelVersion>  
       <groupId>org.confucius</groupId>  
       <artifactId>HelloWorldTapestry</artifactId>  
       <packaging>war</packaging>  
       <version>0.0.1-SNAPSHOT</version>  
         
       <dependencies>  
           <dependency>  
                <groupId>org.apache.tapestry</groupId>  
                <artifactId>tapestry-core</artifactId>  
                <version>5.3.3</version>  
           </dependency>              
   
           <dependency>  
                <groupId>org.apache.tapestry</groupId>  
                <artifactId>tapestry5-annotations</artifactId>  
                <version>5.3.3</version>  
           </dependency>  
               
           <dependency>  
                <groupId>org.chenillekit</groupId>  
                <artifactId>chenillekit-tapestry</artifactId>  
                <version>1.3.3</version>  
           </dependency>  
         
   </dependencies>  
         
    <build>  
        <finalName>HelloWorldTapestry</finalName>  
    </build>  
         
 </project>  
   

Update Layout.tml to use a Sliding Panel, like this:

 <html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">  
   <head>  
     <title>Tapestry</title>  
   </head>  
   <body>  
     <h3>Basic Tutorial Series</h3>  
     
           <t:chenillekit.SlidingPanel t:subject="Chenillekit Greeter">  
             <t:body/>  
           </t:chenillekit.SlidingPanel>      
   
           <p>Visit Us @ www.projectconfucius.org</p>  
             
   </body>  
 </html>  

Now if you rebuild and redeploy HelloWorldTapestry, you will see the greeting inside a Chenillekit Sliding Panel.

Tapestry - Layouts

To create the same look-n-feel across multiple pages, Tapestry provides the concept of Layout. For example, all pages may have the same header and footer,

A Layout looks very similar to any other Page, except that it is placed in the /components folder instead of /pages.

For now, we will not worry about the difference between a component and a page - they are very similar. Both have a foo.tml file and a corresponding foo.java POJO.

Let us build a Layout for our pages.

In /src/main/resources/org/confucius, create a folder 'components'

In  /src/main/resources/org/confucius/components, create a file Layout.tml, like this:

 <html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">  
   <head>  
     <title>Tapestry</title>  
   </head>  
   <body>  
     <h3>Basic Tutorial Series</h3>  
     
     <t:body/>  
   
     <p>Visit Us @ www.projectconfucius.org</p>  
             
   </body>  
 </html>  


Note that we have included the Tapestry tag library. The magic here is the t:body tag - this will get automatically replaced by the content of the actual page.

Again, this looks just like any other Tapestry page, but because it is in the /components folder, Tapestry treats it like a 'component'.


In /src/main/java/org/confucius, create a folder 'components'

In  /src/main/java/org/confucius/components, create a class Layout.java, like this:

 package org.confucius.components;  
   
 public class Layout {  
   
 }  
   

This is just an empty POJO, but it is necessary to make it available to Tapestry.

Now, update your Index.tml page to use the Layout, like this:

 <html t:type="layout" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">  
      <body>  
           <h2>${greeting}</h2>  
      </body>  
 </html>  
   


The t:type attribute tells Tapestry to use Layout.tml as the layout component.

If you now rebuild and redeploy HelloWorldTapestry, you will see the header and footer rendered in the page.

Note:
If some of your pages needed a different layout, say Layout2 - then you will need to create a Layout2.tml and Layout2.java, and in your pages, you will say t:type="layout2"

Friday, May 25, 2012

Tapestry - Using Beans

Due to Tapestry convention, Index.java is already a Bean, no configuration necessary.

Let us see how to use it in Index.tml.

Update your Index.java to have a property 'greeting', like this:
 package org.confucius.pages;  
 public class Index {  
      private String greeting;  
      public Index() {  
           this.greeting = "Hello World, Tapestry!";  
      }  
      public void setGreeting(String greeting) {  
           this.greeting = greeting;  
      }  
      public String getGreeting() {  
           return greeting;  
      }  
 }  

We can now use this field in Index.tml, like this:
 <html>  
 <body>  
 <h2>${greeting}</h2>  
 </body>  
 </html>  

Due to the one-to-one mapping between pages and Beans, Tapestry will look for the 'greeting' property in Index.java, since it is referred to in Index.tml

Tapestry will automatically call the getGreeting()) method to get the property 'greeting'.

If you rebuild and redeploy HelloWorldTapestry, and point your browser to:
http://localhost:8080/HelloWorldTapestry/

you will see "Hello World, Tapestry!" displayed.