Sunday, November 25, 2012

Printing files directly from web page

Introduction

We want to print "remote" documents directly to our local default printer without having to download the documents manually and without showing a print dialogue box.
In our case the application is integrated with Google Apps, our documents are stored in Google Drive. But the approach below works for all documents that can be accessed via the URL class in Java. In the web application a list of documents is display as shown below:
By clicking on the print icon behind one of the documents the document is printer directly to the default printer of the user without a dialogue box.
A much used technique is using the window.print() method with JavaScript but this approach also prints the web page details in the header and footer. In our case we want to print the document as it is. For this we use a good old Applet, this way we can use JavaScript to trigger the printing by the Applet. The applet accesses the default printer and streams the remote document to it. As we use Google Drive we can choose any format of document to be exported as PDF, this PDF is streamed to the printer.

Print Applet

The actual code that does the printing is pretty straight forward:

PrintService service = PrintServiceLookup.lookupDefaultPrintService();
if (service != null) {
    DocFlavor psFormat = DocFlavor.INPUT_STREAM.PDF;
    PrintRequestAttributeSet attributes = new HashPrintRequestAttributeSet();    
    DocPrintJob job = service.createPrintJob();
    Doc pdfDoc = new SimpleDoc(new URL(fileUrl).openStream(),psFormat, null);
    job.print(pdfDoc, attributes);         
}
The default printer is fetched by calling PrintServiceLookup.lookupDefaultPrintService(). Since we convert all our documents to PDF before printing we use the DocFlavor.INPUT_STREAM.PDF. Next a print job is created and using the remote document as an input stream.

Integrate Print Applet in web page

The next step is to publish the applet inside the web application, the jar file containing the PrintApplet class is located in a folder /applet in the root of the web application. To include the applet we add the following html code: where:

  • id: id of the applet to be used in JavaScript
  • code: className of applet
  • width / height: dimension of applet, in Chrome the applet will not work if width/height =0
  • codebase: the location of the jar file containing the applet class, this is relative to the page the applet tag is embedded in
  • archive: name of jar containing the applet

Calling the applet from JavaScript

Due to java security settings for applets a dialogue box will pop-up every time you want to print:

To prevent this we have to change two things:
  • sign the jar containing the PrintApplet class
  • change permissions/privileges of the PrintApplet

Sign the jar containing the applet

To sign the jar we first need a certifcate, this can be created using the keytool. First we create a public/private key:
Next we generate a self signed certificate:
Now we have a keystore 'delinet' containg the certificate, we will use this keystore to sign the jar file with maven:
Next we generate a self signed certificate:
After redeploying the application and accessing the page with the applet we get the following warning:
Check both boxes. Calling the JavaScript to print a document still generates a pop up. For this to go away we have to change the permissions or priveleges of the applet.

Change the permissions or priveleges of the applet

There are several options to prevent this:
  • change java.policy file in JRE
  • change .java.policy file in users home directory
  • modify the applet to use AccessControler
For the first two options add the following extract: Since the first two options involve changes on the client side we prefer the third option, wrapping the printing inside the AccessController.doPrivileged method:
After redeploying the application, restarting the web browser, accessing the page again and clicking on the print button the document is printed to the printer without any pop ups.

Monday, May 14, 2012

Install PLJava Postgresql 8.4 on Ubuntu LTS 10.04 Lucid Lynx

Before you can use java stored procedures on PostreSQL you have to install PLJava. The following assumes you already have Java 6 (Sun/Oracle or OpenJdk) installed on your machine.

Download the PLJava binary

This binary can be found on the pljava page of PgGoundry. Since we are running a 64 bit server with PostgreSQL 8.4 we used this distribution.

Next we extract this tar.gz file in the newly created directory /usr/src/pljava.

Setup PLJava

Next we create a file /etc/ld.so.conf.d/postgres.conf with the following content:

 /usr/lib/jvm/java-6-sun/lib
 /usr/lib/jvm/java-6-sun/jre/lib/amd64/server
Now run:
sudo ldconfig
Next edit the /etc/postgresql/8.4/main/postgresql.conf and add the following lines:
custom_variable_classes = 'pljava' 
pljava.classpath = '/usr/lib/postgresql/8.4/lib/pljava.jar'
Now copy the files pljava.jar and pljava.so to /usr/lib/postgresql/8.4/lib:
sudo cp /usr/src/pljava/pljava.jar /usr/lib/postgresql/8.4/lib
sudo cp /usr/src/pljava/pljava.so /usr/lib/postgresql/8.4/lib
Finally restart postgres:
sudo /etc/init.d/postgresql-8.4 restart

Install PLJava

Go to the /usr/src/pljava directory and run:

sudo su postgres -c "/usr/bin/psql -d template1 -f /usr/src/pljava/install.sql"
Where template1 is the database you want to install pljava. The result should look simular to this:
CREATE SCHEMA
GRANT
CREATE FUNCTION
CREATE LANGUAGE
CREATE FUNCTION
CREATE LANGUAGE
psql:/usr/src/pljava/install.sql:23: NOTICE:  CREATE TABLE will create implicit sequence "jar_repository_jarid_seq" for serial column "jar_repository.jarid"
psql:/usr/src/pljava/install.sql:23: NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "jar_repository_pkey" for table "jar_repository"
psql:/usr/src/pljava/install.sql:23: NOTICE:  CREATE TABLE / UNIQUE will create implicit index "jar_repository_jarname_key" for table "jar_repository"
CREATE TABLE
GRANT
psql:/usr/src/pljava/install.sql:32: NOTICE:  CREATE TABLE will create implicit sequence "jar_entry_entryid_seq" for serial column "jar_entry.entryid"
psql:/usr/src/pljava/install.sql:32: NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "jar_entry_pkey" for table "jar_entry"
psql:/usr/src/pljava/install.sql:32: NOTICE:  CREATE TABLE / UNIQUE will create implicit index "jar_entry_jarid_key" for table "jar_entry"
CREATE TABLE
GRANT
ALTER TABLE
psql:/usr/src/pljava/install.sql:43: NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "classpath_entry_pkey" for table "classpath_entry"
CREATE TABLE
GRANT
psql:/usr/src/pljava/install.sql:50: NOTICE:  CREATE TABLE will create implicit sequence "typemap_entry_mapid_seq" for serial column "typemap_entry.mapid"
psql:/usr/src/pljava/install.sql:50: NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "typemap_entry_pkey" for table "typemap_entry"
CREATE TABLE
GRANT
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
You are now ready to use Java stored procedures on PostreSQL 8.4 !!!

Test PLJava

To verify that it all works we are going to install a java stored procedure. Open your favorite SQL-editor and connect to the database and run:

CREATE FUNCTION getsysprop(VARCHAR)
  RETURNS VARCHAR
  AS 'java.lang.System.getProperty'
  LANGUAGE java;
Now run:
SELECT getsysprop('user.home');
This should return something like:
 getsysprop
 -------------------
 /var/lib/postgresql

Many thanks to BANYM'S BLOG for the inspiration for this blog.

Saturday, May 12, 2012

Authentication in Seam using Google OAuth 2.0 for Google Apps integration

This blog explains how you can use Google's OAuth 2.0 so users can login to your Seam application using their google account. Using this technique it is possible to integrate your Seam application with Google Apps.

Click here to go to the demo application

Currently there are several ways to authenticate users in your seam application with a google account. Unfortunately the OAuth 1.0 protocol is depricated since april 2012. If you want to integrate your Seam application with Google Apps it's best to use OAuth 2.0.

Setup Google API in your Goolge account

Before you can use Google's OAuth 2.0 for your own web application you have to setup a client ID for your web application. This can be done via API console.

The client ID and Client Secret are used by the web application to call the Google Apps Api.

Setup Seam security

In the WEB-INF/compenents.xml we add the authentication-method:

<security:identity authenticate-method="#{oAuth2Action.checkGoogleApiAuthorization}"/>
This method is triggered from the #{identity.login} action in the login screen. In the WEB-INF/pages.xml we define a login-view-id and the pages that require login:
<?xml version="1.0" encoding="UTF-8"?>
<pages xmlns="http://jboss.com/products/seam/pages" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.2.xsd"
 login-view-id="/viewsPublic/login.xhtml">
 
 <page view-id="/oauth2.xhtml" action="#{oAuth2Action.getGoogleApiAuthorizationToken}" /> 
 ... 
 <page view-id="/views/*" login-required="true" />

</pages>
All pages in the /views folder require login, calling a page in this folder when the user is not logged in, will trigger a call to the viewsPublic/login.xhtml page. The view /oauth2.xhtml is called by Google after successful authentication on the Google website. In the viewsPublic/login.xhtml we define the login screen:
...
<h:commandLink title="#{messages['login']}" action="#{identity.login}" >
 <h:graphicImage value="/images/logo_google.png" />
</h:commandLink>
...
Clicking on this link in the login page will start the authentication process:
  • The #{identity.login} action will call the #{oAuth2Action.checkGoogleApiAuthorization} method, this method redirects the user to the Google login screen
  • After successful authentication on the Google website the user is redirected to the /oauth2.seam view, this will call #{oAuth2Action.getGoogleApiAuthorizationToken} method. This method retrieves a Google Token to communicate with the Google API, this token is used to fetch the User Profile information.

Setup Google API in your application

Google provides Java classes to call the Google API, more information can be found here. In this demo we only use the following dependencies:

<dependency>
 <groupId>com.google.api-client</groupId>
 <artifactId>google-api-client-servlet</artifactId>
 <version>1.8.0-beta</version>
 <exclusions>
  <exclusion>
   <groupId>javax.servlet</groupId>
   <artifactId>servlet-api</artifactId>
  </exclusion>
  <exclusion>
   <groupId>javax.transaction</groupId>
   <artifactId>transaction-api</artifactId>
  </exclusion>
 </exclusions>
 <scope>compile</scope>
</dependency>
<dependency>
 <groupId>com.google.apis</groupId>
 <artifactId>google-api-services-calendar</artifactId>
 <version>v3-rev3-1.5.0-beta</version>
 <scope>compile</scope>
</dependency>
<dependency>
 <groupId>com.google.apis</groupId>
 <artifactId>google-api-services-oauth2</artifactId>
 <version>v2-rev3-1.5.0-beta</version>
 <scope>compile</scope>
</dependency>
In this demo application we have abstracted all calls to the Google API in one service class.
import java.io.IOException;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;

import com.google.api.client.auth.oauth2.AuthorizationCodeFlow;
import com.google.api.client.auth.oauth2.AuthorizationCodeResponseUrl;
import com.google.api.client.auth.oauth2.MemoryCredentialStore;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.services.oauth2.Oauth2;
import com.google.api.services.oauth2.model.Userinfo;

@Name("googleApiService")
@Scope(ScopeType.APPLICATION)
@AutoCreatepublic class GoogleApiService implements Serializable {

 private static final long serialVersionUID = -4366524069518491233L;
 
 private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
 private static final JsonFactory JSON_FACTORY = new JacksonFactory(); 
 private static final List SCOPES = Arrays.asList(
   "https://www.googleapis.com/auth/userinfo.profile",
   "https://www.googleapis.com/auth/userinfo.email");

 private static AuthorizationCodeFlow authorizationCodeFlow;
 private static String clientId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
 private static String clientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxx";
 
 static {
  authorizationCodeFlow = new GoogleAuthorizationCodeFlow.Builder(
    HTTP_TRANSPORT, JSON_FACTORY, clientId, clientSecret, SCOPES)
    .setCredentialStore(new MemoryCredentialStore()).build();
 }
 
 public boolean hasCredentials(String userId) {
  if (userId != null) {
   return authorizationCodeFlow.loadCredential(userId) != null;
  }
  return false;
 }
 
 public String getAuthorizationUrl(String redirectUrl) {
  return authorizationCodeFlow.newAuthorizationUrl().setRedirectUri(redirectUrl).build();
 }
 
 public void setCredentials(HttpServletRequest request, String redirectUrl, String userId) throws GoogleAuthorizationException {
  
  StringBuffer fullUrlBuf = request.getRequestURL();
  if (request.getQueryString() != null) {
   fullUrlBuf.append('?').append(request.getQueryString());
  }
  AuthorizationCodeResponseUrl authResponse = new AuthorizationCodeResponseUrl(
    fullUrlBuf.toString());
  if (authResponse.getError() != null) {
   throw new GoogleAuthorizationException(authResponse.getError());
  } else {
   try {
    GoogleTokenResponse response = new GoogleAuthorizationCodeTokenRequest(
      HTTP_TRANSPORT, JSON_FACTORY, clientId, clientSecret,
      authResponse.getCode(), redirectUrl).execute();
    authorizationCodeFlow.createAndStoreCredential(response,
      userId);    
   } catch (IOException e) {
    throw new GoogleAuthorizationException(e);
   }
  }
 }
 
 public void removeCredentials(String userId) {
  authorizationCodeFlow.getCredentialStore().delete(userId, authorizationCodeFlow.loadCredential(userId));
 }
 
 public Userinfo getUserInfo(String userId) throws GoogleAuthorizationException {
  Oauth2 userInfoService = Oauth2.builder(HTTP_TRANSPORT, JSON_FACTORY)
    .setHttpRequestInitializer(authorizationCodeFlow.loadCredential(userId)).build();
  Userinfo userInfo = null;
  try {
   userInfo = userInfoService.userinfo().get().execute();
  } catch (IOException e) {
   throw new GoogleAuthorizationException(e);
  }
  if (userInfo != null && userInfo.getId() != null) {
   return userInfo;
  } else {
   throw new GoogleAuthorizationException("No userinfoId exception");
  }
 }

}
This GoogleApiService uses the client ID and client Secret provided by the Google API Console. This service is used by the OAuth2Action.class:
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;

import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.annotations.Out;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.faces.FacesManager;
import org.jboss.seam.faces.Redirect;
import org.jboss.seam.security.Identity;
import org.jboss.seam.security.SimplePrincipal;
import org.jboss.seam.web.ServletContexts;

import com.google.api.services.oauth2.model.Userinfo;

@Name("oAuth2Action")
@Scope(ScopeType.SESSION)
public class OAuth2Action implements Serializable {

 private static final long serialVersionUID = -4366524069518498663L;
 
 @Out(scope=ScopeType.SESSION, required=false)
 private Userinfo userinfo;

 @In
 protected GoogleApiService googleApiService;
 
 @In
 protected Identity identity; 

 public String returnToUrl() {
  FacesContext context = FacesContext.getCurrentInstance();
  HttpServletRequest request = (HttpServletRequest) context
    .getExternalContext().getRequest();

  try {
   URL returnToUrl;
   if (request.getServerPort() == 80) {
    returnToUrl = new URL("http", request.getServerName(), context
      .getApplication().getViewHandler()
      .getActionURL(context, "/oauth2.xhtml"));
   } else {
    returnToUrl = new URL("http", request.getServerName(),
      request.getServerPort(), context.getApplication()
        .getViewHandler()
        .getActionURL(context, "/oauth2.xhtml"));

   }
   return returnToUrl.toExternalForm();
  } catch (MalformedURLException e) {
   throw new RuntimeException(e);
  }
 }

 public void checkGoogleApiAuthorization() throws IOException,
   GoogleAuthorizationException {
  
  String url = googleApiService.getAuthorizationUrl(returnToUrl());
  if (url != null) {
   Redirect redirect = Redirect.instance();
   redirect.captureCurrentView();
   FacesManager.instance().redirectToExternalURL(url);
  } else {
   throw new GoogleAuthorizationException(
     "No authorizationUrl definied");
  }
 }

 public String getGoogleApiAuthorizationToken() throws GoogleAuthorizationException {
  ExternalContext context = javax.faces.context.FacesContext
    .getCurrentInstance().getExternalContext();
  HttpServletRequest request = (HttpServletRequest) context.getRequest();
  
  googleApiService.setCredentials(request, returnToUrl(),
    ServletContexts.instance().getRequest().getSession().getId());
  
  userinfo = googleApiService.getUserInfo(ServletContexts.instance().getRequest().getSession().getId());
  
  identity.acceptExternallyAuthenticatedPrincipal(new SimplePrincipal(userinfo.getName()));

  return "userinfo";

 }
 
 @Observer(Identity.EVENT_LOGGED_OUT)
 public String logout() {
  googleApiService.removeCredentials(ServletContexts.instance().getRequest().getSession().getId());
  return "start";
 }

}