Showing posts with label Tapestry5. Show all posts
Showing posts with label Tapestry5. Show all posts

Wednesday, February 17, 2016

Tapestry5 and the case of the disappearing bootstrap modal dialog

Tapestry5 has a pretty neat way to support JavaScript but once in a while I forget some things. Forgetting things then leads you to errors. In my case, the error was a bootstrap modal not properly displaying or disappearing immediately.

I was working on a simple but reusable modal Tapestry5 component. For some context on how to do this, go here. The important part here is the top part where you declare your JavaScript includes:

@Import(module = {"bootstrap/modal"}) 
public class ModalDialog implements ClientElement{...}

This loads the modal.js from bootstrap. Tapestry5 can do this because the framework is bootstrap aware. We then finish this up with the javascript and tml file for the component.

Thinking all was Ok, I then use this component on a page and this is where I encountered the "disappearing modal" dialog.

After debugging, I followed the bug to my layout component. Again, the important part here is where we declare the JavaScript includes:

@Import(stylesheet = {"context:datatables/css/dataTables.jqueryui.css","context:patternfly/css/patternfly.css", 
                      "context:patternfly/css/patternfly-additions.css"},
        library = {"context:patternfly/js/patternfly.js", "context:mybootstrap/js/bootstrap.min.js"})
public class Layout{...}

Apparently, bootstrap doesn't like when we double load modules. The bootstrap.min.js is a minified bootstrap and it already has the modal.js module. Then, when we load the page where we use the ModalDialog component which double loads the modal.js module.

This bug was easily fixed by removing the import annotation in my ModalDialog component.

I forgot about my JavaScript basics that invoking the script twice equals problems.

Also, this was confirmed by this StackOverflow question.

Wednesday, December 2, 2015

A tutorial on how to use the Tapestry-Security module Part 2

We pick up where we left off.

We start with our AppModule and configure our security service to use our "custom" realm.

    
    // @see http://tynamo.org/tapestry-security+guide
    @Contribute(WebSecurityManager.class)
    public static void addRealms(Configuration configuration, @InjectService("MiscAppRealm") AuthorizingRealm userRealm) {
        configuration.add(userRealm);
    }

With this we then update our MiscRealm.java code:
    
  @Inject
  private UserMembershipDAO umDAO;    

  public MiscAppRealm() {
        super(new MemoryConstrainedCacheManager());
        setAuthenticationTokenClass(UsernamePasswordToken.class);
        setCredentialsMatcher(new MiscRealmCredentialMatcher());
  }

  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
        if(pc == null) throw new AuthenticationException("PrincipalCollection was null, which should not happen");
        
        if(pc.isEmpty()) return null;
        
        if(pc.fromRealm(getName()).size() <= 0) return null;
        
        String loginName = (String)pc.fromRealm(getName()).iterator().next();
        if(loginName == null) return null;
                
        AppUsers user = umDAO.FindAppUsersByLoginId(loginName);
        
        if(user == null) return null;
                
        Set roles = new HashSet<>(user.getRolesList().size());
        user.getRolesList().stream().forEach((role) -> {
            roles.add(role.getDescription());
        });
        
        System.out.println("Roles: " + roles);
        
        return new SimpleAuthorizationInfo(roles);
  }

  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken at) throws AuthenticationException {                
        UsernamePasswordToken token = (UsernamePasswordToken)at;
        
        String username = token.getUsername();
        
        if(username == null){
            throw new AccountException("Null usernames are not allowed by this realm");
        }
        
        AppUsers user = umDAO.FindAppUsersByLoginId(username);
                
        if(user.getIsLocked()){
            throw new LockedAccountException("Account: " + username + " is locked.");
        }
         
        return new SimpleAuthenticationInfo(username, user.getEncodedPassword(), new SimpleByteSource(user.getPasswordSalt()), getName());
 }

We then move on to our custom credential matcher (MiscRealmCredentialMatcher.java) which extends from SimpleCredentialsMatcher. The thing there is overriding the doCredentialsMatch() method on it. You need to remember though is that the UserMembership table stores the encrypted password instead as plain text. You'll get the idea when you see the jasypt documentation.

@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
     
     AppUsers target = FindAppUsersByLoginId(UserId);
     String encryptedPassword = target.getPassword();
        
     BasicPasswordEncryptor passwordEncryptor = new BasicPasswordEncryptor();
        
     return passwordEncryptor.checkPassword(String.valueOf(token.getPrincipal()), encryptedPassword);
}

The last part of the puzzle is how we handle the Login form when we submit it. The Tynamo-Security's own code is helpful here.

    
void onValidateFromLogin() throws ValidationException {
        
     Subject currentUser = securityService.getSubject();
                
     if(currentUser == null){
         throw new IllegalStateException("Subject can`t be null");
     }
        
     UsernamePasswordToken token = new UsernamePasswordToken(loginId, password);
     token.setRememberMe(rememberMe);
        
     try {
          currentUser.login(token);
            
     } catch (UnknownAccountException e) {
         login.recordError(loginIdField, "");
         login.recordError(passwordField, "Invalid user name or password.");
     } catch (IncorrectCredentialsException e) {
         login.recordError(loginIdField, "");
         login.recordError(passwordField, "Invalid user name or password.");
     } catch (LockedAccountException e) {
         login.recordError(loginIdField, "Account is Locked. Please see Administrator");
     } catch (AuthenticationException e) {
         login.recordError(loginIdField, "");
         login.recordError(passwordField, "Invalid user name or password.");
     }
}

Object onSuccessFromLogin() throws MalformedURLException, IOException {
    if (StringUtils.hasText(successURL)) {
        if ("^".equals(successURL)) {
             return pageRenderLinkSource.createPageRenderLink(componentResources.getPage().getClass());
        }
        return new URL(successURL);
    }

    if (redirectToSavedUrl) {
         String requestUri = loginContextService.getSuccessPage();
         if (!requestUri.startsWith("/") && !requestUri.startsWith("http")) {
             requestUri = "/" + requestUri;
         }
         loginContextService.redirectToSavedRequest(requestUri);
        return null;
    }
    return loginContextService.getSuccessPage();
}

What's left is to annotate the pages to secure.

Edit: smallish changes to code

Monday, November 30, 2015

A tutorial on how to use the Tapestry-Security module Part 1

It often bothered me how many times I've been answering questions from my students (and Stack) this question: "How do you use Tapestry-Security?"

Actually, Tynamo already wrote a guide but some are actually having a hard time figuring it out. They are confused by the database part and how it connects to the authentication and authorization parts. So this is where I come in and since this is should be pretty long post, I'm breaking it into two parts. Also, I'd be working on the assumption readers:
  • Have working knowledge of Tapestry5
  • Can get a Tapestry5 DAO service going
  • Basic understanding how Shiro works (or at least read the tutorial); YOU NEED TO UNDERSTAND SHIRO because Tapestry-Security is based on it.
  • Are OK with me using jasypt lib for encryption; I know it's not the best so chill your titties, we need to make work first THEN we'll deal with a much stronger encryption procedure.
To get started,

Open your POM.xml file and add the Tapestry-Security and Jasypt modules as a dependency.

<dependency>  
  <groupId>org.tynamo</groupId>  
  <artifactId>tapestry-security</artifactId>  
  <version>0.6.2</version>  
</dependency> 

<dependency>  
  <groupId>org.jasypt</groupId>  
  <artifactId>jasypt</artifactId>  
  <version>1.9.2</version>  
</dependency> 

Rebuild your project after to get the module.

Once that's out of the way, we should start creating a Realm. We are making an Authorizing Realm, which authenticates subjects (or users) AND authorizes them with either Roles or Permissions. Here's a sample:

public class MySecurityRealm extends AuthorizingRealm {

    @Inject
    private UserMembershipDAO umDAO;
    
    public MiscAppRealm() {
        super(new MemoryConstrainedCacheManager());
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken at) throws AuthenticationException {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }
}

You then need to configure your WebSecurityManager to use this realm. The WebSecurityManager service is something that Tapestry-Security module adds. Also, don't worry about the unimplemented methods in the realm. We're going to fix that on the next post.

Monday, October 12, 2015

Tapestry5, JPA 2 and Hibernate

The documentation for Integrating JPA found on the Tapestry5 website was lacking.
  1. It doesn't tell you that you have add the Tapestry-jpa dependency in you project POM
  2. It uses EclipseLink
  3. Explicitly tell you to use JPA 2.0 NOT JPA 2.1;
So, I had a few problems to fix after reading and following the docs. With much tinkering with my Tapestry5 (5.4-beta-35) project, I figured out:
  1. To add the Tapestry-jpa dependency
    <dependency> <groupId>org.apache.tapestry</groupId> <artifactId>tapestry-jpa</artifactId> <version>5.4-beta-35</version> </dependency>
        

  2. Hibernate 4.2 instead of EclipseLink
    <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>4.2.20.Final</version> </dependency>
  3. Edit the persistence.xml header so it reads version=2.0 instead of 2.1. This is because my Netbeans IDE can generate the persistence.xml file but defaults to 2.1 version instead of 2.0.
And if you get a javax.persistence.PersistenceExceptionUnable to build EntityManagerFactory error during the build, you probably didn't add a hibernate.dialect property in your persistence.xml configs.

Thursday, October 8, 2015

Tapestry5, JPA, Netbeans and a FilerException

Tapestry5.4 supports JPA out of the box which is nice. Also Netbeans supports JPA, doubly nice. Now if you're using 2 or more Persistence Units, you have probably encountered a FilerException when you try to compile or run your project. This shouldn't be a problem if you're using only single Persistence Unit. I suspect the fix would be the same if you encounter the same exception.

java.lang.RuntimeException: javax.annotation.processing.FilerException: Attempt to recreate a file for type {myclass}

The {myclass} here is always an entity class.

I think, a secondary symptom of this is if all your entity classes are all in a single package. This should be the case if you're doing a standard Tapestry5 web application.

The fix to this is to edit the Persistence Unit and add a  property and explicitly declare the entity classes on the Persistence Unit.

<persistence-unit name="PayrollPU" transaction-type="RESOURCE_LOCAL">  
   <provider>org.hibernate.ejb.HibernatePersistence</provider>  
   <class>cu.ictso.miscapps.entities.Departments</class>  
   <class>cu.ictso.miscapps.entities.Employee</class>  
   <class>cu.ictso.miscapps.entities.Groups</class>  
   <class>cu.ictso.miscapps.entities.ViewAttendance</class>  
   <exclude-unlisted-classes>true</exclude-unlisted-classes>  
   <properties>  
    <property name="javax.persistence.jdbc.url" value="jdbc:sqlserver://localhost;databaseName=DTR-Payroll2;Integrated Security=false"/>  
    <property name="javax.persistence.jdbc.user" value="PayMaster"/>  
    <property name="javax.persistence.jdbc.driver" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>  
    <property name="javax.persistence.jdbc.password" value="123456"/>  
    <property name="javax.persistence.schema-generation.database.action" value="create"/>  
   </properties>  
 </persistence-unit>  

Here's an example Persistence Unit.


Monday, June 9, 2014

Somehow I got it to work: Tapestry 5.4-beta10 and Google App Engine

Aside from a bunch of crummy errors and missing files, I manage to to get Tapestry 5.4-beta10 to run in a Google App Engine instance. Here's what I learned:
  1. Java 8 and Tapestry 5 don't like each other. A bunch of guys reported it like Matt Raible. There's a work around and it isn't exactly safe for production (yet).
  2. YUIcompressor is missing again from the staging repo.
  3. You need to MAKE sure that your GAE instance name matches your appengine-web.xml application name. 
So how to go about getting your Tapestry5 into GAE. 
  1. Start by creating a Tapestry 5 project via maven. Which one though depends on what version of Java is on your dev machine. I'm running Java 8 so I had to use something greater than 5.4-beta4. I just updated the pom.xml and ran mvn clean compile.
  2. Add a appengine-web.xml to the src/main/webapp/WEB-INF folder.
    <?xml version="1.0" encoding="utf-8"?>  
     <appengine-web-app xmlns="http://appengine.google.com/ns/1.0">  
       <application>YOURAPPNAMEHERE</application>  
       <version>1</version>  
       <threadsafe>true</threadsafe>  
       <sessions-enabled>true</sessions-enabled>  
     </appengine-web-app>
    
  3. Edit the pom.xml to include google app engine devserver. Look for the plugins section and add this lines.
    <!-- Run the application using "mvn appengine:devserver" -->  
     <plugin>  
               <groupId>com.google.appengine</groupId>  
               <artifactId>appengine-maven-plugin</artifactId>  
               <version>${appengine.target.version}</version>  
               <configuration>  
                         <enableJarClasses>false</enableJarClasses>  
                         <port>8182</port>  
                         <address>0.0.0.0</address>  
               </configuration>  
     </plugin> 
    
    Also, add these values in the properties node:
            <appengine.target.version>1.9.6</appengine.target.version>
    
  4. Run mvn appengine:devserver
    There is where it gets hairy, if it fails to run, check the stack trace. If it says yuicompressor is missing then comment it out in the pom.xml and then make sure to run the app in dev mode by adding this line in the AppModule.java in the contributeApplicationDefaults method:
       configuration.add(SymbolConstants.PRODUCTION_MODE, false);
    
    There should clear it up. If there are more problems refer to the appengine docs for maven.
  5. If all goes well and you should have a running Tapestry5 app running locally inside a appengine devserver instance. The next step is to upload it to the appengine servers because that's the whole point.
  6. Run mvn appengine:update
    There should open a web page where authentication code to paste into the command line. Also, I do hope you already created a matching app engine project. No? You are a dumbass.
Here is my tapestry5 after all of that.

Saturday, July 20, 2013

DAT Angular

This is part 5 of my Resteasy Tapestry with AngularJS tutorial. This is last part of the tutorial where you combine both. You should end up with a Resteasy-Tapestry5 server with the Ajax APIs and a second web application that uses the APIs via AngularJS. 

Using Ajax APIs with AngularJS is easy if you use Angular-Resource. Angular-Resource is not a default add-on for AngularJS. You don't have to download it since Google's CDNs also serve it.

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>  
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular-resource.js"></script>  
<script type="text/javascript" src="js/tangled.js"></script>  

The tangled.js script there is where we write the interesting parts. Here's a partial of that script:

angular.module('tangled-app',['ngResource'])
    .controller('tangledCtrl', function($scope, $resource){
        var Customer = $resource('http://localhost\\:8080/tangled/rest/customers');
        
        $scope.appName = "Tangled App";             // App name
        $scope.customers = Customer.query();        // Calls our Resteasy-Tapestry REST API /GET customers
    })
........

The script basically just means that I'm working with a Angular app called "tangled-app" and then AngularJS inits to also load the Angular-Resource library so we can use the $resource variable. The whole thing then just hinges on the var Customer = $resource('http://localhost\\:8080/tangled/rest/customers'); line. Think of this line as a "promise", it promises to return data when you call the query or equivalent function.

After the script all you have to do is add the tangled-app value to the ng-app directive and the tangledCtrl to a ng-controller:
 <!DOCTYPE html>  
 <html ng-app="tangled-app">  
   <head>  
......... 
   </head>  
   <body>  
     <div class="container" ng-controller="tangledCtrl">  
       <div class="row-fluid">  
         <h3>Abstract</h3>  
         <p>This a simple customer ajax app with   
           <a href="http://tynamo.org/tapestry-resteasy+guide">Tapestry-resteasy</a> as the backend   
           and <a href="http://angularjs.org/">AngularJS</a> on the front. It also uses   
           <a href="http://twitter.github.io/bootstrap/">Twitter-Bootstrap</a>.</p>  
       </div>  
       <div class="customer-table">  
         <table class="table table-striped table-bordered">  
           <thead>  
             <tr>  
               <th>#</th>  
               <th>Name</th>  
               <th>Address</th>  
               <th>City</th>  
               <th>Country</th>  
               <th>Code</th>  
               <th>Credit Limit</th>  
             </tr>  
           </thead>  
           <tbody>  
             <tr ng-repeat="customer in customers">  
               <td>{{customer.customerNumber}}</td>  
               <td>{{customer.customerName}}</td>  
               <td>{{customer.addressLine1}} {{customer.addressLine2}}</td>  
               <td>{{customer.city}}</td>  
               <td>{{customer.country}}</td>  
               <td>{{customer.postalCode}}</td>  
               <td>{{customer.creditLimit}}</td>  
             </tr>  
           </tbody>  
         </table>  
       </div>  
     </div>  
........
     <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>  
     <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular-resource.js"></script>  
     <script type="text/javascript" src="js/tangled.js"></script>  
   </body>  
 </html>

BTW, if you're using Chrome, you'll get an "access-control-allow-origin" error. To fix this and allow you to continue developing in a local machine then just run Chrome with --disable-web-security. Don't do this though when browsing the internetz.

Tuesday, July 2, 2013

Designing with the end in mind: tuktuk style

This is part 4 of my Resteasy Tapestry with AngularJS tutorial. This is the design part of the tutorial. Now a days, what good is a web applications if it can't look good. I'm not a UI design expert by any kind of measure, in fact, I pretty much suck at it. So, I'm using somebody else's stuff to get me to look half decent, hence I'm using Javi Jimenez's tuktuk CSS framework.


Tapestry5 Layout sources
In Tapestry, the look of the website is a layout component. It works like pretty much like any other templating system you probably have seen or tried. You have a main template where the smaller page specific templates are "added" into. I have also write about tapestry layouts in a previous tutorial.

The first thing we have to do is add the tuktuk assets like the style sheets and scripts. 

1. Copy the style sheets into the layout folder. You should be adding 3 stylesheets: tuktuk.css, tuktuk.icons.css and tuktuk.theme.css

2. If the folder doesn't exist yet, create a "js" folder in the layout folder and copy the tuktuk.js file into it.

With the assets in place, we need to tell Tapestry5 about it. Open up the layout.java source file in the components folder. And right on top of the class declaration is an @import annotation, this what we change so our Tapestry webapp will be using the tuktuk assets. Change it to:

@Import( stylesheet = {"context:layout/tuktuk.css","context:layout/tuktuk.icons.css",
                       "context:layout/tuktuk.theme.css","context:layout/layout.css"},
        library = {"context:layout/js/tuktuk.js"})

That layout.css stylesheet is your own stylesheet seperate from the tuktuk.css. It's a bad idea to add your own style rules to the tuktuk.css. It's better to use your own.

The last step is to edit the layout.tml. The layout.tml is a very simple HTML template file so it should fairly easy to understand. It's a HTML file for christ sakes!

Saturday, June 15, 2013

Easy restful services with tapestry and tynamo-resteasy

This is part 3 of my Resteasy Tapestry with AngularJS tutorial. We will be building the restful api for this tutorial/project. I'll be trying to keep it simple with just CRUD equivalents like GET,  POST, PUT, and DELETE.

First thing first, we will be using Tynamo Resteasy for building the api. We could build our own from scratch but why re-invent the wheel? To add it, you will leverage Maven. Open up your POM file which should be on the root portion of the project and edit it:


<dependency>  
      <groupId>org.tynamo</groupId>  
      <artifactId>tapestry-resteasy</artifactId>  
      <version>0.3.1</version>  
 </dependency>    
 <dependency>  
      <groupId>org.jboss.resteasy</groupId>  
      <artifactId>resteasy-jackson-provider</artifactId>  
      <version>3.0-beta-4</version>  
 </dependency>

We need a JSON marshaller/unmarshaller, hence the resteasy-jackson-provider. We will be working with JSON, no XML. Read the Tynamo-resteasy guide for clarification on this. Now, simply clean and build the project and let Maven do its thing and get all them jars.

The next step is to create the Tapestry service that will respond to AJAX calls. In keeping with Tapestry convention, I'm putting this service inside a package. I called mine: com.demo.tangled.rest. So, it doesn't look so cluttered, I'm just showing the GET method of our restful API inside this service.
package com.demo.tangled.rest;

import com.demo.tangled.dao.contactsDAO;
import com.demo.tangled.entities.Contacts;
import org.apache.tapestry5.ioc.annotations.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import java.util.List;

@Path("/customers")                     // URL for API call     
public class contactsResource{
    
    @Inject                             // Get that DAO service
    private ContactsDAO contactsDAO;
    
    @GET
    @Produces({"application/json"})     // Set output format to JSON
    public List<contacts> getAllContacts(){
        return contactsDAO.getAll();
    }
    .....    
}

The interesting parts of the code are the annotations. The first annotation, @Path, is the URL for API which is appended to rest sub-domain path. So, the address of this resource is http://localhost:8080/tangled/rest/customers if this was running on a local development machine. The other request methods like POST, PUT and DELETE are written in the same manner as the getAllContacts method. Notice the @GET annotation.

I have already wrote about how to make DAO services in my previous Tapestry5 tutorial.

Saturday, June 1, 2013

Creating the project in Netbeans with Maven

Modern software development is partly about the tools you use. Just don't insist yours is better with other programmers unless you want to start a religious war.

I use Maven. Maven is a software project management and comprehension tool. It's easy to install and use and Netbeans, my preferred IDE, can work with it by default unlike the other leading IDE which needs a plugin.  

Once you've installed Maven, you'll need to configure Netbeans to use the installed one unless you want to use the bundled one. You can do that via the menu Tools -> Options -> Java(tab) -> Maven. It should auto-detect the Maven home if you installed Maven correctly. After this, you're pretty much set.

Project from Archetype
Now all you need to do is create our Tapestry5.

Create a new project and choose Maven and then Project from Archetype.

We will be using the quickstart Tapestry5 project to get us rolling fairly quickly. The current version should 5.3.7.

On the "next" window, set the values to the following:
  • Group ID: org.apache.tapestry
  • Version: 5.3.7
  • Artifact ID: quickstart
  • Repository: http://tapestry.apache.org
You should be able to figure out the next page. After you clicked on that Finish button, you should see the your freshly created Tapestry5 project. 

Before you can run a Tapestry5 project, you need to satisfy a sample requirement which is a create a custom  Maven goal. You'll need this if you want to use the Live Reloading feature. I already wrote a how-to on this in an old blog entry. Just do steps 1, 2 and 3. You don't need to do the rest.

The Tapestry5 running
When you're done, you should be able to run the project and be able to open the web application in your favorite browser. See left. 

If you're wondering, I called my project tangled.

Next week, we'll be working on your database.

Resteasy Tapestry with AngularJS tutorial

I have been on a binge with AngularJS and so far I haven't gone to the hospital. And there's Tapestry5. I have been in-love with it for the longest time.

I think its time for a tutorial.

I'm basically gonna build a simple Tapestry-based restful API and use it with AngularJS. It's not that hard as it sounds. We'll be using Netbeans for this.

Here's what I have planned:

Monday, March 18, 2013

Tapestry5 and JasperReports

Creating "reports" are a fact of life for developers. It's sucky work but necessary. With my Tapestry5 application, I decided to use JasperReports for the following reasons:
  1. It's free
  2. It's easy to use
  3. It has a nice plugin that works inside Netbeans: iReport
To get started, you have to add JasperReports dependency in your POM.xml:

 <dependency>  
   <groupId>net.sf.jasperreports</groupId>  
     <artifactId>jasperreports</artifactId>  
   <version>5.0.1</version>  
 </dependency>  
 <dependency>  
   <groupId>commons-collections</groupId>  
     <artifactId>commons-collections</artifactId>  
   <version>3.2.1</version>  
 </dependency>  

The commons-collections might be needed to fix a transitive dependency error that you might get with Tapestry5 and JasperReports.

The next part is the report template (jrxml) needs to be created. I just put it template in the WEB-INF folder so to access it is: /WEB-INF/reports/report1.jrxml

The report template is a rather rich XML file with a lot of moving parts but it shouldn't be that hard since we do have the iReport plugin to "visually" create the reports. Here is an example: test_jasper.jrxml. And here is a full working example.

The next piece of the puzzle is a custom Tapestry5 StreamResponse for PDF files. The idea is after we generate the report, we are going to send it to the client as a PDF file so they can print it.

 import java.io.IOException;  
 import java.io.InputStream;  
 import org.apache.tapestry5.StreamResponse;  
 import org.apache.tapestry5.services.Response;  
 /**  
  *  
  * @author jaypax  
  */  
 public class PDFStreamResponse implements StreamResponse {  
     private InputStream is;  
     private String filename="default";  
     /**  
      * Constructor  
      * @param is  
      * @param args (http://docs.oracle.com/javase/1.5.0/docs/guide/language/varargs.html  
      */  
     public PDFStreamResponse(InputStream is, String... args) {  
         this.is = is;  
         if (args != null) {  
             this.filename = args[0];  
         }  
     }  
     public String getContentType() {  
         return "application/pdf";  
     }  
     public InputStream getStream() throws IOException {  
         return is;  
     }  
     public void prepareResponse(Response arg0) {  
         arg0.setHeader("Content-Disposition", "attachment; filename="  
                 + filename + ".pdf");  
     }  
 }  

Then we can now add a event handler for that button when clicked sends the report. In my scenario, its inside a onSubmit event. This is on a page class that's populated with services injected via Tapestry's IOC. I also injected the report form as an asset.

    @Property
    private int id;

    @Inject
    @Path(value = "context:WEB-INF/reports/report1.jrxml")
    private Asset ReportForm;
    ....
    ....
    ....
    public StreamResponse onSubmitFromReportForm() throws IOException,JRException{
        InputStream is = ReportForm.getResource().openStream();
       
        JRBeanCollectionDataSource ds = new JRBeanCollectionDataSource(someDAO.findAllEntriesById(id));
        
        Map reportParams = new HashMap();
        reportParams.put("TITLE", "SOME REPORT TITLE");

        JasperDesign reportDesign = JRXmlLoader.load(is);
        JasperReport reportCompiled = JasperCompileManager.compileReport(reportDesign);
        JasperPrint reportPrinted = JasperFillManager.fillReport(reportCompiled, reportParams,ds);
           
        ByteArrayInputStream bais = new ByteArrayInputStream(JasperExportManager.exportReportToPdf(reportPrinted));
        
        return new PDFStreamResponse(bais, "Some Report");
    }

Notice the PDFStreamResponse as the return object, without that then browser will mishandle the return stream.

Turning this into a service shouldn't be that hard which I will probably do in a coming iteration.

Tuesday, March 5, 2013

Tapestry 5.3 and the case of Twitter-Bootstrap disabled attributes

Making Tapestry5 use Twitter-Bootstrap as its style is quite easy but not everything is as it seems.

Bootstrap as some nice form style rules. It's quite easy to add them into normal tapestry5 common components. Like if you need some a textfield to look like a "search-query" field then you would write something like:

// Rounded corners
<input t:type="textfield" t:id="keyword" class="search-query" placeholder="Callslip ID"/>  

or if you need a page or event link to look like a button:

// Tapestry5 eventlink styled as a button via Bootstrap
<a t:type="eventlink" t:event="add" class="btn brn-primary" href="#">Add 1</a>  

But I have recently came across a scenario where I need to display a textfield with data that's by default is uneditable but under certain conditions can be edited. Reading the bootstrap documentation, you have to do something like this:

// As shown in the Bootstrap documentation
<input class="input-xlarge" id="disabledInput" type="text" placeholder="Disabled input here..." disabled>

OK, that should be easy to convert into tapestry5 component and use javascript to enable or disable the field. When done via jquery, removeAttr() or prop(). There's an equivalent to add an attribute.

// Tapestry5 is not happy about disabled attribute
<input class="input-xlarge" id="disabledInput" t:type="textfield" placeholder="Disabled input here..." disabled>

And that's this is where the problem crops up. Tapestry5 doesn't like any unpaired attributes. I'm talking about the disabled attribute at the end of the input element above. Fortunately, Peter Wendorff in the Tapestry mailing-list pointed me to a html microsyntax spec document explaining this unique nuance of html. You have to do a disabled="true" attribute to makeTapestry5 happy.

// How Tapestry5 wants it done
<input disabled="true" class="input-xlarge" t:id="name" t:type="textfield" placeholder="${name}" />
Other ways to write this doesn't work, even if the spec doc says its OK.

  • disabled="" -> doesn't work
  • disabled="disabled" or disabled='disabled' or disabled=disalbed -> also doesn't work
  • disabled -> obviously doesn't work

Friday, August 17, 2012

Autobind All Tapestry5 services!

Norman Franke, Dmitry Gusev and a bunch of other guys in the Tapestry5 mailing list show us a very cool way to bind services via package traversal. No more binder declarations for DAO services several miles long.

The technique they showed made me want to smack my older self - Why didn't you think of that!? This autobind technique basically just needs a bit of planning - a "convention" if you will. The idea is to put the "implementation" package as a folder below your DAO interfaces. Typically, you would append an "Impl" tag to your DAO interfaces so for example: UserDAO = UserDAOImpl and then place them in the correct packages: com.myapp.dao for the DAO interfaces and then com.myapp.dao.impl for the DAO implementations. The rest is done in the AppModule of Tapestry5.

...
public static void Bind(ServiceBinder binder) throws ClassNotFoundException  
{  
   autoBindServices(binder, ProjectDAO.class.getPackage());  
   .....  
}  
private static void autoBindServices(ServiceBinder binder, Package interfacePackage) throws ClassNotFoundException  
{  
  List<Class<?>> interfaces = Utils.getClassesForPackage(interfacePackage.getName());  
  for(Class intf : interfaces)  
  {  
     String className = interfacesPackage.getName() + ".impl." + intf.getSimpleName() + "Impl";  
     try  
     {  
         Class impl = Class.forName(className);  
         binder.bind(intf, impl);  
     }  
     catch(ClassNotFoundException e)  
     {  
         logger.warn("Class not found during autobinding: {}", className);  
     }  
  }  
}
Very cool stuff.

Wednesday, July 18, 2012

Tapestry-hibernate and that ValueEncoder exception

I love tapestry5 but sometimes it just does something bad. In this instance, its a ValueEncoder error you get when your are using the default transitive dependencies from Maven. If just followed the "Using Tapestry with Hibernate" you might end up with a HTML 500 error with tapestry5 telling you about a ValueEncoder exception.

A first I thought it was my stuff (Netbeans + MSSQL + Glassfish) but I eventually figured out it wasn't by process of elimination.

The way to fix this is to change something in your POM file. Look for the hibernate dependency entry. You will notice that its using older version of hibernate.

<dependency>  
    <groupId>org.hibernate</groupId>  
    <artifactId>hibernate</artifactId>  
    <version>3.2.5.ga</version>  
</dependency>  

I just changed this to to use the newer version of the hibernate. Also changed it from hibernate to hibernate-core. So the dependency would read:
<dependency>  
    <groupId>org.hibernate</groupId>  
    <artifactId>hibernate-core</artifactId>  
    <version>4.1.4.Final</version>  
</dependency>  

It would be also a good idea to update the rest dependencies.

EDIT: Apparently the Tapestry-hibernate module doesn't like 4.1.4.Final version of Hibernate. The Hibernate Session will fail when you use it. The fix is just to revert Hibernate to anything in the 3.xx branch like 3.4.xx.

Friday, February 3, 2012

.NET MVC3, Web Forms, Tapestry5 and Arnold being dead

They told me to learn .NET MVC3 or get fired and I'm like fine, whatever. I hack java, groovy, javascript, Less CSS and HTML5, how hard .NET MVC3 can be? I mean, Java has a ton of MVC frameworks (Struts, Play, etc.) and I've used (or at least tried) a lot of them. And don't get me started with PHP - CodeIgniter, Zend, etc.

Apparently, it isn't that hard, C# .NET is more or less a direct rip of Java with some syntactic sugar and good ideas. It took me about a week to find a groove with .NET MVC3 and another week to learn Entity Framework (which BTW, looks like Hibernate in Microsoft branding). So, I was chugging out good code and then Arnold told me to try out Web Forms. "It's like Tapestry5." He's words. He's. Last. Words. Then I BEAT HIM to DEATH with my old IBM ceramic keyboard.

Web Forms IS NOT Tapestry5!

Tapestry5 is way, waaaaaayyyyyyyy cleaner than Microsoft's Web forms. Proof? Try making a non-trivial Web Form's based web application and take a look at its "Code Behind" classes and generated HTML. I see spaghetti and it doesn't adhere to Tapestry5 programming tenets of  Single Responsiblity Classes and Seperation of Concerns.

What kind of class code are you writing when it balloons past 1k lines and you can't identity what it actually does from the first 20 or so lines?

Another that rubs me the wrong way with Web Forms  is that I have limited control on how to the HTML is generated. You can argue that I could "override" it, screw that, its more trouble than its worth.

And lastly, Web Forms is a bitch when trying to work in your favorite JavaScript library.

I though writing this blog would be, you know, cathartic. It's just making me angry again.

Where is that keyboard.....

Saturday, October 8, 2011

Writing new Java is Groovy

Way back when - this was when I was in college, you had to choose a programming language. Unfortunately for my instructors, I wasn't the sheep they thought I was. I wasn't about to be cowed into using VB6, which by the way, they were ramming it down the throats of my classmates - well most of my classmates. A few of us decided it was our duty to give them the finger and tell them to shove where the sun don't shine.

So I looked at and tried a lot of stuff during this time.
  1. C/C++. Its like using a chainsaw without the chain guard - its liable to cut of your hand when you do something stupid. Damn overflows and pointers in the wrong addresses.
  2. Pascal and/or Delphi. Very cool IDE but very heavy with the keywords. I also just didn't like the language - this is more of a personal preference rather than something that has technical merit. So, Pass.
  3. xBase languages like Clipper. No IDE during that time so programming was done with notepad and a stash of batch files. No dice.
  4. Powerbuilder. It was the gold standard for rapid software development that time. It just cost a couple thousand bucks for the whole thing. Ouch.
So that just leaves Java, which was still owned by Sun Microsystems. It was free, no pointers, had several IDEs to choose from and language idiom that "spoke" to me. That was 10 years ago and like any good hack, I'm still learning, hacking, tweaking new stuff. And this is where Groovy came into the picture.

I'll be the first one to admit, Java as a programming language is far from perfect. Java code is overly verbose is one such flaw. Groovy is more leaner but its still Java; consider the fact that Almost all Java code ever written is also valid Groovy. 

To make my point, take a look that this standard but rather trivial Java POJO.

import java.util.Date;

public class Person {

    String firstName;
    String lastName;
    Date birthDay;

    public Person() {}

    public Person(String firstName, String lastName, Date birthDay) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.birthDay = birthDay;
    }
    
    public Date getBirthDay() {
        return birthDay;
    }

    public void setBirthDay(Date birthDay) {
        this.birthDay = birthDay;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}


No suprises here. I will concede that this can be generating by the IDE but you can't escape the fact that the code is still extensive. Compare this to the equivalent Groovy code.

class Person {
    String firstName;
    String lastName;
    Date birthDay;
}

Seriously, that’s the whole class. Groovy classes are public by default, as are Groovy methods. Attributes are private by default. Access to an attribute is done through dynamically generated getter and setter methods, so even though it looks like we’re dealing with individual fields, we’re actually going through getter and setter methods.

Also Groovy works with my favorite Java framework, Tapestry5 and is supported by Netbeans since 6.5.

And like Ash in Army of Darkness getting the powered glove in place of his right hand: Groovy.

Tuesday, August 30, 2011

Embedding Images to a Tapestry5 page very easily

OK I'll admit that the title might be problem but then again that's up to you.

Here's the thing, adding dynamic images in Tapestry5 templates is pie. You can read up on it here. But that seems to be best way if you are dealing with a file system or uploaded files. But what if the source is JSON data and doesn't need to be manipulated? Where we'd rather just stick it in directly into the template and display it.

Apparently this is just as easy as pie. First off, I'm dealing with a Base64 string as representation of the image within the JSON data. The image format is JPG, by the way for the just curious. Fortunately, Firefox and Chrome like Base64 strings. Then we can basically do this.

<img src="data:image/jpg;base64,iVBORw0KGgoAAAANS..." />

So what we need then is a webservice that pulls the JSON, process it a bit and pass it into the template.

public interface ICustomerService {

    public JsonObject getCustomerFromWebService(String id);
    
    public Customer getCustomer(String id);
        
    public Map<String, String> getCustomerHistory(String id, String code);
}

The ICustomerService is quite straight-forward. The implementation is where it gets interesting.

//Don't forget to add this into Tapestry5's service registry using binder.bind()    
    @Inject
    private Messages messages;

    JsonSystem system = StandardConfig.createSystem();
    
    public Customer getCustomer(String id) {
        JsonObject obj = getCustomeFromWebService(id);
        Customer customer = new Customer();
        // various gets;
        // lets skip to the interesting parts
        customer.setPhotoB64(obj.getString("ImageB64"));
        return customer;
    }

    public JsonObject getCustomerFromWebService(String id) {
        StringBuilder url = new StringBuilder( messages.get("localhost.mywebservice") );
        url.append(id);
        JsonObject object = system.get(url.toString()).asObject();
        return object;
    }


The first thing here is getting the URL of my web service - which I'm hosting locally. My dilemma was how to do this not in code so if the web service URL changes, I don't have to recompile. My answer was to use the Tapestry5 global properties via the messages catalog. All Tapestry5 web applications have this properties file in the WEB-INF folder. All you need to do is just @Inject the org.apache.tapestry5.ioc.Messages object and then you have now access.

The next bit is using ItemScript. ItemScript is very good JSON toolkit. I think you have noticed that getting JSON data with ItemScript is quite easy. Just create a JsonSystem object and then supply the URL. You should then get a JsonObject which now you can parse for data.

The final parts is just basically now use the new service. We'll start with the Index.java file.

@Inject
    private ICustomerService iCustomerService;
    
    public Customer getCustomer(){
        //let's try out our web service
        return iCustomerService.getCustomer("2033405");
    }
.....


The Index.tml file is just as easy. Add this right after the ${message:greeting} line.

<p>Test Customer</p>
<p> ${Customer.firstname}, ${Customer.lastname}</p>
<img src="data:image/jpg;base64,${Customer.photoB64}" />


Here is a screenshot.
The guy in the image is Aristotle "yoyong" Ucab. He's the guy who wrote the web service.

I don't really know where the heck this photo was taken.

Anyway, have fun with this.

Wednesday, June 2, 2010

Death by Captcha?

Captcha is a type of challenge-response test used in computing to ensure that the response is not generated by a computer. The process usually involves one computer (a server) asking a user to complete a simple test which the computer is able to generate and grade. Because other computers are unable to solve the CAPTCHA, any user entering a correct solution is presumed to be human. Captcha has a lot of uses for web applications.

In this tutorial, we are going I am going to show you one way of doing it. I'll be showing you how to integrate JCaptcha into our Tapestry5 webapp as a web service. Mr. Ship, the creator of Tapestry also a tutorial for this, its up in his blog. He is using Kaptcha.

Now to begin with this tutorial we need to add JCaptcha as a dependency to our project. So, open up pom.xml in project files then add these lines:

 <dependencies>  
 ....  
 ....  
 <dependency>  
    <groupId>com.octo.captcha</groupId>  
    <artifactId>jcaptcha</artifactId>  
    <version>1.0</version>  
 </dependency>  
 </dependencies>  

Don't forget to save it.

The whole magic of our JCaptcha service is the interface which has two methods. In you edu.addressbook.services package create a new interface and name it JCaptchaService. The contents of which would look:

 package edu.addressbook.services;  
 import org.apache.tapestry5.StreamResponse;  
 public interface JCaptchaService {  
   public boolean isValidUserResponse(String verificationCode, String captchaId);  
   public StreamResponse generateImageChallenge(String captchaId);  
 }  

You will notice that our interface has a isValidUserResponse() method - this is where our challenge is invoked. If it returns false then apparently the user on the other side might not human else if its true then we assume that the user is human.

Following the pattern, you'll have to create a edu.addressbook.services.impl package which contains a single class: JCaptchaServiceImpl. You need to understand that we still have to do this since JCaptcha generates a StreamResponse object (via the ImageCaptchaService object) which we can redirect into Tapestry.

 public class JCaptchaServiceImpl implements JCaptchaService {  
   private final ImageCaptchaService serviceInstance;  
   public JCaptchaServiceImpl(ImageCaptchaService serviceInstance) {  
     this.serviceInstance = serviceInstance;  
   }  
   public StreamResponse generateImageChallenge(String captchaId) {  
     BufferedImage imageChallenge = serviceInstance.getImageChallengeForID(captchaId, Locale.US);  
     ByteArrayOutputStream os = new ByteArrayOutputStream();  
     try {  
       ImageIO.write(imageChallenge, "PNG", os);  
       final InputStream is = new ByteArrayInputStream(os.toByteArray());  
       return new StreamResponse() {  
         public String getContentType() {  
           return "image/png";  
         }  
         public InputStream getStream() throws IOException {  
           return is;  
         }  
         public void prepareResponse(Response response) {  
           response.setHeader("Pragma", "no-cache");  
           response.setHeader("Cache-Control", "no-cache");  
           response.setDateHeader("Expires", 0);  
         }  
       };  
     } catch (IOException e) {  
       throw new RuntimeException(e);  
     }  
   }  
   public boolean isValidUserResponse(String verificationCode, String captchaId) {  
     try {  
       return serviceInstance.validateResponseForID(captchaId, verificationCode);  
     } catch (CaptchaServiceException e) {  
       throw new RuntimeException(e);  
     }  
   }  
   public class ImageCaptchaImpl extends ImageCaptcha {  
     public static final long serialVersionUID = 1;  
     public ImageCaptchaImpl(String question, BufferedImage challenge) {  
       super(question, challenge);  
     }  
     public Boolean validateResponse(Object obj) {  
       return Boolean.FALSE;  
     }  
   }  
 }  

And for the final part is positioning it in the front-end. I put this in the index.tml (and its partner index.java). The whole thing is "injected" into the page like this:

   @Inject  
   private JCaptchaService captchaService;  
   @Inject  
   private RequestGlobals requestGlobals;  

and its the public methods are:

   public Link getImageURL() {  
     return resources.createActionLink("image", false);  
   }  
   //executes when image is rendered  
   public Object onImage() {  
     return captchaService.generateImageChallenge(requestGlobals.getHTTPServletRequest().getSession().getId());  
   }  

This way we just make a call to the getImageURL() method in the webpage via an expansion and voila! we have a captcha image to filter which users are human or not. Here is the final line of code to be placed in the index.tml file:

  <p>Captcha Image</p>  
  <t:img src="${imageURL}" align="absmiddle" alt="Dynamic Verification Code" />  

That it.

Now remember that you can get the code at kenai using the SVN: https://svn.kenai.com/svn/addressbook-tap5~svn

The End.

Friday, May 7, 2010

Jetty in the shell but Tomcat in the Ghost

We are wrapping up this course. I am calling this entry "Jetty in the shell but Tomcat in the Ghost" because I am a fan of the "Ghost in the shell" anime series. If that doesn't make sense don't worry, it just means that you are missing a few neurons.

We are going to talk about our deployment options for our Tapestry5 web application. We could continue using Jetty or we could shift to the more familiar Tomcat. You could make a point by arguing for other web application servers like JBoss and such. Well, JBoss uses Tomcat, so the point is moot. This entry is about deploying our seemingly done addressbook with Tomcat. 

The first thing we need to do is pack away our project into a web application archive file or more commonly called a WAR file. Creating a WAR file in Netbeans is easy.


Right-click on our project and select either "Build" or "Clean and Build".

Wait for it to finish - you can track its progress in the output window if you want.

When finish go to the project folder and locate the target folder. Open it and there should be file named AddressBook.war. This is what we will install into Tomcat.

So, how do we deploy (or install) our application into Tomcat. There are two ways of doing this:

1. You can simply copy the AddressBook.war file into the webapps folder within the Tomcat path. Tomcat will detect it and deploy the new web application using the filename as context. So in our case it will be deployed to /addressbook relative to the URL.

2. or use the Tomcat manager to deploy it.

Now this is roughly half the job. The other half is about setting up the mySQL database and configuring Apache. If you have gotten this far, you could do that already.

So, from start to finish - a working Tapestry5 application. I hope you find this useful and inspire you to do great and wonderfull things with Tapestry.