Getting Started with Pink

Getting Setup

Supported Servers

Pink requires a CDI compatible web container. It has been tested in JBoss AS 7 and TomEE Web profile 1.5.0. Pink will not work in a plain Tomcat server.

Create a Web Project

The fastest way to create a Pink based web project is to use the pink-archetype Maven archetype. For detail instructions read the README of the PinkArchetype GitHub repository.

You can also use Eclipse to create a web project. In that case, first install the Pink archetype following the same instructions as above. Then, in Eclipse, create a new Maven project based on the pink-archetype Maven archetype.

Basic Programming

Model View Controller

A controller is a CDI managed bean that can be referred using a name. Which means, it must be assigned a name using the @Named annotation. Here is a simple controller.

@Named("simple")
@RequestScoped
public class MySimpleController {
    String fullName;
    public String getFullName() {
        return fullName;
    }
    public void setFullName(String fullName) {
        this.fullName = fullName;
    }
    //Action handler method
    public String hello() {
        return "greet";
    }
}

This bean has a name of “simple”. To send a HTTP request to this bean, we will need to map the “/simple/*” path to the Pink Servlet. So, add this to your web.xml:

<servlet-mapping>
 <servlet-name>Pink Servlet</servlet-name>
 <url-pattern>/simple/*</url-pattern>
</servlet-mapping>

Here, hello() is an action handler method that can be invoked by sending a HTTP request. The controller has a property called fullName. You can supply a URL parameter called fullName to set its value. To invoke the hello method, you can request this URI: /simple/hello?fullName=Daffy.

An action handler method returns an outcome string. For a GET request, Pink will forward to a JSP file identified by the outcome. The JSP files are stored in WEB-INF/views/<Bean Name> folder. In case of the hello() method, the JSP will be WEB-INF/views/simple/greet.jsp. Let’s say, the JSP is like this.

<html>
<body>
<p>Hi, ${simple.fullName}!</p>
</body>
</html>

Now, you can send a request to the hello() method, using the URI /simple/hello?fullName=Daffy. For example, if your web app’s context root is SimpleWeb and the server is listening on port 8080, the full URL will be http://localhost:8080/SimpleWeb/simple/hello?fullName=Daffy. This will show a page saying “Hi, Daffy!”.

The URI is very easy to form. Here, “simple” refers to the controller bean by its name. Then “hello” is a path that points to the hello() method. The “fullName” URL parameter sets the fullName property of the bean.

Navigation Control

An action handler method of a controller can return an outcome string to control page flow or navigation. The following rules describe how Pink treats the outcome string returned by the method:

  1. If null is return, Pink does nothing. Only the HTTP reply header is sent back without the body. 
  2. If the request was a GET, then Pink forwards to a JSP identified by the outcome string. The JSP file is identified as /WEB-INF/views/<bean name>/<outcome string>.jsp.
  3. If the request was a POST and there was no validation error, Pink will redirect to the action handler method identified by the outcome string. The outcome can be relative, such as “my_method”. In that case, it will be treated as the name of a method of the same controller that processed the last request. The outcome may be absolute, such as “/another_bean/method”. In that case, Pink will redirect to the method of the bean specified in the outcome.
  4. If the request was a POST and there was at least one validation error after the action handler method returned, then Pink will forward to a JSP identified by the outcome. Details are same as in rule #2.
  5. After a GET request, the action handler method may decide to perform a redirection by prefixing “@” to the outcome string. For example, “@my_method” or “@/another_bean/method”. A redirection is always performed after a successful POST request. Putting a “@” prefix to the outcome in that case has no effect.

More details on navigation is covered in the advanced topics section.

Form Submission Handling

Pink uses simple URL parameter naming convention. As you have seen above, to update the fullName property of the controller, you send a URL parameter called fullName. It’s that simple. A set of custom tags are provided to make your life even easier when designing forms.

Consider a controller:

@Named("account")
@RequestScoped
public class CustomerController {
    Logger logger = Logger.getLogger(getClass().getName());
    Customer customer = new Customer();

    public String register() {
        logger.fine("Registering: " + customer.getFullName() + 
                " Email: " + customer.getEmail());
        //Create customer record in database...

        return "show?customer.id=" + 100;
    }
    public String registerForm() {
        return "new_customer_form"; 
    } 
    public String show() {
        logger.fine("Showing customer: " + customer.id);

        return "display_customer";
    }
    public Customer getCustomer() {
        return customer;
    }
}

public class Customer {
    private String id;
    private String fullName;
    private String email;

    //Getters and setters...
}

Add the bean’s path in your web.xml:

<servlet-mapping>
 <servlet-name>Pink Servlet</servlet-name>
 <url-pattern>/account/*</url-pattern>
 <url-pattern>/simple/*</url-pattern>
</servlet-mapping>

The registerForm method shows the new customer entry form page. The register() method creates a new customer in the database.

The form needs to be created in /WEB-INF/views/account/new_customer_form.jsp since the outcome returned by registerForm() is “new_customer_form”. The JSP will look like this:

<%@ taglib uri="http://mobiarch" prefix="p" %>
<html>
<body>

<p:form action="customers/register" method="post">
  Name: <p:input type="text" name="customer.fullName"/><br/>
  E-mail: <p:input type="text" name="customer.email"/><br/>
  <input type="submit" value="Add Customer"/>
</p:form>

</body>
</html>

The action of the form is “customers/register”. This will invoke the the register() method of our bean. The name attribute of the p:input tag refers to the property names of the bean.

To view the form, send a GET request for the URI: /account/registerForm. After the form is submitted, register() will be called using a POST request. The method returns the outcome “show?customer.id=100”. As a best practice, POST should result in a redirection. Pink enforces that behavior by default. This outcome will redirect the browser to /account/show?customer.id=100. This time show() will be called which will load /WEB-INF/views/account/display_customer.jsp.

Right now, we are using one method – registerForm() – to show the form and another method – register() – to handle submission. You can do both from the same method for a more compressed self contained code.

@Inject
Context context; //Pink request context
public String register() {
   if (context.isPostBack()) {
        logger.fine("Registering: " + customer.getFullName() + 
            " Email: " + customer.getEmail());
        //Create customer record in database...

        return "show?customer.id=" + 100;
    } else {
        return "new_customer_form";
    }
}

Here, we are first getting a reference to the Pink request context object. The register() method then checks if this is a POST or GET request and acts differently. Now, you can show the form and create a customer using the same URI – /app/customers/register.

Form Validation

Pink uses JSR 303 for form validation. We can annotate the fields of the Customer class to set the validation rules.

@Size(min=3, max=15, message="The name must be 3 to 15 characters long")
private String fullName;
@Size(min=3, max=25, message="Please enter a valid e-mail")
private String email;

Now, the register method should re-display the form if validation fails. We check for validation failure by calling context.isValidationFailed().

public String register() {
   if (context.isPostBack() && !context.isValidationFailed()) {
        logger.fine("Registering: " + customer.getFullName() + 
            " Email: " + customer.getEmail());
        //Create customer record in database...

        return "show?customer.id=" + 100;
    } else {
        return "new_customer_form";
    }
}

Finally, show the error messages above the form using <p:errors>.

<p:errors style="color: red"/>
<p:form action="customers/register" method="post">
...
</p:form>

You can set any attribute for p:errors. Here we set the style attribute.

SEO Friendly Clean URL

Right now, we are invoking the show() method by requesting the URI “/account/show?customer.id=100”. This is awkward and search engines will not crawl this link. Pink allows you to set any URL parameter in the URI path. Here, we will make a few changes so that we can call the show() method using the URI /account/show/100.

Annotate the register() method with com.mobiarch.nf.Path.

@Path("customer.id")
public String show() {
    ...
}

That’s it. Now, Pink will get the path segment after the method name – show – and set it as the value of the customer.id property of the bean. @Path let’s you set any number of properties separated by “/”. The old query string based URI will continue to work. If a parameter appears in both URL parameter as well as in the URI path, the latter takes precedence.

Although optional, you should probably change the redirected URI in your code.

public String register() {
    if (context.isPostBack() && context.isValidationFailed() == false) {
	...

        return "show/" + 100;
    } else {
        return "new_customer_form";
    }
}

By default, the path for a method is same as its name. If the method name is too long, or you do not wish to expose the method name, you can assign a different path by setting an absolute path. For example:

@Path("/show/customer.id")
public String showCustomerDetails() {
    ...
}

Here, we are assigning the path “show” to the method showCustomerDetails.

Also, note that a method with camel case can be invoked vi a dash separated URI. For example:

public String showCustomerDetails() {
    ...
}

Can be invoked using the method URI “show-customer-details”.

JSON Response

Pink makes it very easy to send a JSON document back in response. You can use this feature to aid Ajax communication.

Normally, an action handler method of a controller bean returns an outcome string. This is used to either redirect the browser or load a JSP. If, on the other hand, the method returns a non-String object, Pink converts it into a JSON document and sends it back to the browser. The content type is set to “application/json” in that case. Example:

@Path("customer.id")
public Customer get() {
    logger.fine("get() called with id: " + customer.getId());
    Customer c = ...; //Load Customer from database...

    return c;
}

You can send a GET request for the URI /account/get/100. This will return something like:

{"id":"100","fullName":"Daffy Duck","email":"daffy@wb.com"}

Advanced Topics

Data Conversion

HTTP request always sends input data as string. Pink converts this data to appropriate Java types of the bean properties. Similarly, when rendering an input field, Pink converts the Java type of the bound property into String. Conversion is supported for these data types:

  • Simple types like int, float, double, boolean.
  • Class equivalents of the simple types, like Integer, Float and Boolean.
  • java.util.Date and java.sql.Date

For number and date properties, input in the default format for user’s locale is supported. That means, user can use her own locale’s decimal and thousand separator.

For date input, you can customize the expected input format using the @Format annotation. For example, by default, in US the data format is 10/27/2012. If you wish to accept it as 10-27-2012 then annotate the field as follows:

@Format(pattern="MM-dd-yyyy", message="Please enter a date in the mm-dd-yyyy format")
private Date birthDay;

This will also affect how date is converted to String when rendering the input.

Similarly, @Format can be used to affect conversion of numerical data. For example, to show only two digits after the decimal place, use:

@Format(pattern="#.##", message="Please enter a valid salary")
private float salary;

If Pink fails to convert input data, an error message is queued the same way as input validation failure. You can customize this error message using the message attribute of @Format annotation.

File Upload

In the world of HTML5, the preferred way to do file upload is using Ajax Level2. However, if you do decide to do file upload using the old multi-part way, Pink has excellent support for it. Here is a sample form – upload_page.jsp:

<p:form action="simple/upload" enctype="multipart/form-data">
Notes: <p:input name="notes"/><br/>
Upload File: <p:input type="file" name="fileToUpload"/><br/>
<input type="submit" value="Upload">
</p:form>

The enctype of the form has to be set to “multipart/form-data”. Use a <p:input> of type file in the form.

The controller can be defined this way:

@Named
@RequestScoped
public class Simple {
    private String notes;
    private InputStream fileToUpload;
    @Inject
    Context context;

    //Getters and setters...

    //Action handler
    public String upload() {
        if (context.isPostBack()) {
            dumpFile();
            return "upload";
        } else {
            return "upload_page";
        }
    }

    private void dumpFile() {
        try {
            logger.info("Notes: " + notes);
            BufferedReader br = new BufferedReader(
                new InputStreamReader(fileToUpload));
            String line;

            while ((line = br.readLine()) != null) {
                logger.info(line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

In the example above, an InputStream property has been mapped to the file input field. Alternatively, to obtain more information about the uploaded file, such as size and MIME type, you can use a javax.servlet.http.Part property. For example:

    private Part fileToUpload;
    //Getters and setters...

    private void dumpFile() {
        try {
            logger.info("Notes: " + notes);
            logger.info("File type: " + fileToUpload.getContentType());
            logger.info("File size: " + fileToUpload.getSize());

            BufferedReader br = new BufferedReader(
                new InputStreamReader(fileToUpload.getInputStream()));
            String line;

            while ((line = br.readLine()) != null) {
                logger.info(line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Servlet 3.0 API provides no direct way to get the name of the uploaded file. This is something that can be easily obtained by parsing the Content-Disposition header of a part. See this StackOverflow answer for details.

To limit the size of an uploaded file and other configurations, see this link.

Redirecting After a GET

Pink automatically does a redirection after a POST. But, after a GET, it forwards to a JSP page indicated by the outcome string. In some situations, you may need to do a redirection after a GET. In that case, prefeix the outcome string with a “@”. For example:

@Named("customers")
@RequestScoped
public class CustomerController {
...

    @Path("customer.id")
    public String delete() {
        logger.fine("Deleting: " + customer.getId());

        //Delete customer from database

        return "@list";
    }

    public String list() {
        //Get customer list from database...

        return "customer_list";
    }
}

You can send a request for delete() using /app/customers/delete/100. The response will redirect thebrowser to /app/customers/list. This will execute the list() method. Based on its outcome, Pink will load /customers/customer_list.jsp.

A redirection is always performed after a POST. For that, pre-fixing the outcome with “@” has no impact. This comes handy when a method needs to redirect irrespective of the request method.

public String register() {
    if (session expired) {
        return "@login"; //Redirect no matter if it is GET or POST request
    }
    if (context.isPostBack() && context.isValidationFailed() == false) {
	...
        return "show/" + 100;
    } else {
        return "new_customer_form";
    }
}

Redirect to a Different Controller Bean

By default, a redirection outcome is relative to the current bean. For example, when register() returns “show”, the browser is redirected to “/app/customers/show”. In some situations, a bean may need to navigate to another bean. In that case, use an absolute outcome value. For example:

@Named
@RequestScoped
public class BeanA {
    public String aMethod() {
        return "/beanB/bMethod";
    }
}

@Named
@RequestScoped
public class BeanB {
    public String bMethod() {
        return "form";
    }
}

The aMethod() method of BeanA returns an absolute redirection outcome. If this method is invoked after a POST request, Pink will redirect the browser to /app/beanB/bMethod. This time, bMethod() of BeanB will be called. This method will load /beanB/form.jsp.

Load a JSP from Any Folder

By default, Pink loads a JSP file relative to the current bean. To load a JSP from any other folder, return an absolute outcome. For example:

@Named("customers")
@RequestScoped
public class CustomerController {
...

    public String list() {
        //Get customer list from database...

        return "/dir1/dir2/customer_list";
    }
}

If list() is called after a GET request, Pink will load WEB-INF/views/dir1/dir2/customer_list.jsp.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s