 | Hint
This is part 3 of a series on how to develop portlets with GridSphere. Please make sure you read the previous chapters before. See the Portlet Development Guide. |
To show more of the UI beans as well as more features we are going to develop a portlet where you can enter the books your own. This example consists of a portlet class, two jsp's, a book class and a service class (which is a singleton just for simplicity and illustration purposes, it should be something like a spring bean). For simplicity this examples only works if a user is logged in, it will not work properly for a guest user.
Create the following file:
package org.gridsphere.gsexamples.portlets;
import org.gridsphere.gsexamples.services.Book;
import org.gridsphere.gsexamples.services.BookException;
import org.gridsphere.gsexamples.services.BookService;
import org.gridsphere.portletcontainer.DefaultPortletAction;
import org.gridsphere.portletcontainer.DefaultPortletRender;
import org.gridsphere.provider.event.jsr.ActionFormEvent;
import org.gridsphere.provider.event.jsr.RenderFormEvent;
import org.gridsphere.provider.portlet.jsr.ActionPortlet;
import org.gridsphere.provider.portletui.beans.ListBoxBean;
import org.gridsphere.provider.portletui.beans.MessageBoxBean;
import org.gridsphere.provider.portletui.beans.TextAreaBean;
import org.gridsphere.provider.portletui.beans.TextFieldBean;
import javax.portlet.*;
import java.util.Map;
public class BookPortlet extends ActionPortlet {
private String VIEW_PAGE = "bookInput.jsp";
private String BOOK_INFO_PAGE = "bookInfo.jsp";
public void init(PortletConfig config) throws PortletException {
super.init(config);
DEFAULT_VIEW_PAGE = "prepare";
}
private void showBooks(PortletRequest request) {
BookService bookService = BookService.getInstance();
request.getPortletSession().setAttribute("books", bookService.getBooks(getUsername(request)));
}
public String getUsername(PortletRequest request) {
Map userInfo = (Map) request.getAttribute(PortletRequest.USER_INFO);
return (String) userInfo.get("user.name");
}
public void prepare(RenderFormEvent event) throws PortletException {
setNextState(event.getRenderRequest(), VIEW_PAGE);
}
public void mainView(RenderFormEvent event) throws PortletException {
showBooks(event.getRenderRequest());
setNextState(event.getRenderRequest(), VIEW_PAGE);
}
public void saveBook(ActionFormEvent event) throws PortletException {
TextFieldBean title = event.getTextFieldBean("booktitle");
TextFieldBean isbn = event.getTextFieldBean("bookisbn");
ListBoxBean condition = event.getListBoxBean("bookcondition");
TextAreaBean remarks = event.getTextAreaBean("bookremarks");
MessageBoxBean message = event.getMessageBoxBean("message");
ActionRequest request = event.getActionRequest();
message.appendText(this.getLocalizedText(request, "BOOK_ADDED"));
Book book = new Book(title.getValue(), isbn.getValue(),
condition.getSelectedValue(), remarks.getValue());
BookService bookService = BookService.getInstance();
bookService.addBook(getUsername(request), book);
showBooks(request);
setNextState(request, VIEW_PAGE);
}
public void showBook(RenderFormEvent event) throws PortletException {
RenderRequest request = event.getRenderRequest();
DefaultPortletRender action = event.getRender();
String id = action.getParameter("id");
BookService bookService = BookService.getInstance();
try {
Book book = bookService.getBook(getUsername(event.getRenderRequest()), id);
request.getPortletSession().setAttribute("book", book);
} catch (BookException e) {
log.error("Error: " + e.getMessage());
MessageBoxBean error = event.getMessageBoxBean("error");
error.appendText(getLocalizedText(request, "BOOK_NOT_FOUND"));
request.setAttribute("book", null);
}
setNextState(request, BOOK_INFO_PAGE);
}
public void deleteBook(ActionFormEvent event) throws PortletException {
ActionRequest request = event.getActionRequest();
DefaultPortletAction action = event.getAction();
String id = action.getParameter("id");
BookService bookService = BookService.getInstance();
try {
bookService.deleteBook(getUsername(event.getActionRequest()), id);
MessageBoxBean message = event.getMessageBoxBean("message");
message.appendText(getLocalizedText(request, "BOOK_DELETED"));
} catch (BookException e) {
log.error("Error: " + e.getMessage());
MessageBoxBean error = event.getMessageBoxBean("error");
error.appendText(getLocalizedText(request, "BOOK_NOT_FOUND"));
request.setAttribute("book", null);
}
setNextState(request, VIEW_PAGE);
}
}
DataPortlet is the code for the portlet. During init it sets the default action to be processed to be prepare. All it does in showBooks is to get all the books for the current user and save them to the request, after that it forwards to the jsp page.
The method saveBook is called if the action is triggered by the input form. It grabbes the appropriate values for the userinput, creates a book object and saves it. At the end it gets the saved books and forwards to the main page again.
ShowBook will get the book identified by an id from the request and will save it to the request and redirect further handling to jsp/bookInfo.jsp.
DeleteBook is used in jsp/bookInfo.jsp and will delete the book with the given id.
<%@ page import="org.gridsphere.gsexamples.services.Book" %>
<%@ page import="java.util.Iterator" %>
<%@ page import="org.gridsphere.provider.portletui.beans.MessageStyle" %>
<%@ page import="java.util.HashSet" %>
<%@ page import="java.util.Set" %>
<%@ page import="javax.portlet.PortletSession" %>
<%@ taglib uri="/portletUI" prefix="ui" %>
<%@ taglib uri="http: prefix="portlet" %>
<portlet:defineObjects/>
<ui:text key="BOOK_HEADING"/>
<ui:messagebox beanId="message" style="<%=MessageStyle.MSG_SUCCESS%>"/>
<ui:messagebox beanId="error" style="<%=MessageStyle.MSG_ERROR%>"/>
<%
PortletSession ps = renderRequest.getPortletSession(false);
Set books = (Set) ps.getAttribute("books");
if (books==null) books = new HashSet();
%>
<ui:form>
<ui:table>
<ui:tablerow>
<ui:tablecell>
<ui:text key="BOOK_TITLE"/>
</ui:tablecell>
<ui:tablecell>
<ui:textfield beanId="booktitle"/>
</ui:tablecell>
<ui:tablecell>
<ui:text key="BOOK_ISBN"/>
</ui:tablecell>
<ui:tablecell>
<ui:textfield beanId="bookisbn"/>
</ui:tablecell>
<ui:tablecell>
<ui:text key="BOOK_CONDITION"/>
</ui:tablecell>
<ui:tablecell>
<ui:listbox beanId="bookcondition">
<ui:listboxitem key="BOOK_OLD" value="old"/>
<ui:listboxitem key="BOOK_NEW" value="new" selected="true"/>
<ui:listboxitem key="BOOK_USED" value="used"/>
</ui:listbox>
</ui:tablecell>
<ui:tablecell>
<ui:text key="BOOK_REMARKS"/>
</ui:tablecell>
<ui:tablecell>
<ui:textarea beanId="bookremarks"/>
</ui:tablecell>
<ui:tablecell>
</ui:tablecell>
<ui:tablecell>
<ui:actionsubmit action="saveBook" key="BOOK_SAVE"/>
</ui:tablecell>
</ui:tablerow>
</ui:table>
<ui:text key="ALREADY_OWN"/>
<%
Iterator booksIterator = books.iterator();
while (booksIterator.hasNext()) {
Book book = (Book) booksIterator.next();
%>
<ui:renderlink render="showBook" value="<%=book.getTitle()%>">
<ui:param name="id" value="<%=book.getId()%>"/>
</ui:renderlink>
<%
if (booksIterator.hasNext()) {
%>,<%
} else { %>.<% }
}
%>
</ui:form>
The file jsp/bookInput.jsp ist used to display the form to enter the books as well as show the books you already entered in short form. You can click on the name of each book to get detailed information about the book.
<%@ page import="org.gridsphere.provider.portletui.beans.MessageStyle"%>
<%@ page import="org.gridsphere.gsexamples.services.Book" %>
<%@ page import="javax.portlet.PortletSession" %>
<%@ taglib uri="/portletUI" prefix="ui" %>
<%@ taglib uri="http: prefix="portlet" %>
<portlet:defineObjects/>
<h2><ui:text key="DETAILED_VIEW"/></h2>
<%
PortletSession psession = renderRequest.getPortletSession();
Book book = (Book)psession.getAttribute("book");
if (book==null) book = new Book();
%>
<ui:messagebox beanId="message" style="<%=MessageStyle.MSG_SUCCESS%>"/>
<ui:messagebox beanId="error" style="<%=MessageStyle.MSG_ERROR%>"/>
<ui:table>
<ui:tablerow>
<ui:tablecell><ui:text key="BOOK_TITLE"/></ui:tablecell>
<ui:tablecell><%=book.getTitle()%></ui:tablecell>
</ui:tablerow>
<ui:tablerow>
<ui:tablecell><ui:text key="BOOK_ISBN"/></ui:tablecell>
<ui:tablecell><%=book.getIsbn()%></ui:tablecell>
</ui:tablerow>
<ui:tablerow>
<ui:tablecell><ui:text key="BOOK_CONDITION"/></ui:tablecell>
<ui:tablecell><%=book.getCondition()%></ui:tablecell>
</ui:tablerow>
<ui:tablerow>
<ui:tablecell><ui:text key="BOOK_REMARKS"/></ui:tablecell>
<ui:tablecell><%=book.getRemarks()%></ui:tablecell>
</ui:tablerow>
<ui:form>
<ui:renderlink key="BACK" render="mainView"/>
<ui:actionlink key="DELETE_BOOK" action="deleteBook">
<ui:actionparam name="id" value="<%=book.getId()%>"/>
</ui:actionlink>
</ui:form>
</ui:table>
This will simply show the information about the book and gives a link to delete the displayed book.
The last two classes are the org/gridsphere/gsexamples/services/Book.java class and the src/org/gridsphere/gsexamples/BookService.java class. For a real useful examples the BookService class would need to be enhanced to support persistence of course
.
package org.gridsphere.gsexamples.services;
public class Book {
private String title = "";
private String isbn = "";
private String condition = "";
private String remarks = "";
private long id = 0;
public Book() {
super();
this.id = System.currentTimeMillis();
}
public Book(String title, String isbn, String condition, String remarks) {
this.id = System.currentTimeMillis();
this.title = title;
this.isbn = isbn;
this.condition = condition;
this.remarks = remarks;
}
public String getId() {
return Long.toString(id);
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public String getCondition() {
return condition;
}
public void setCondition(String condition) {
this.condition = condition;
}
public String getRemarks() {
return remarks;
}
public void setRemarks(String remarks) {
this.remarks = remarks;
}
}
The Book class is a simple POJO with getters and setters. Nothing fancy.
package org.gridsphere.gsexamples.services;
import java.util.*;
public class BookService {
private static BookService ourInstance = new BookService();
public static BookService getInstance() {
return ourInstance;
}
private BookService() {
}
private Map books = new HashMap();
public void addBook(String user, Book book) {
Set userBooks = new HashSet();
if (books.containsKey(user)) {
userBooks = (Set) books.get(user);
}
userBooks.add(book);
books.put(user, userBooks);
}
public Set getBooks(String user) {
Set result = new HashSet();
if (books.containsKey(user)) result = (Set) books.get(user);
return result;
}
public Book getBook(String user, String bookId) throws BookException {
Book resultBook = new Book();
if (books.containsKey(user)) {
Set userBooks = (Set) books.get(user);
Iterator bookIterator = userBooks.iterator();
while (bookIterator.hasNext()) {
Book book = (Book) bookIterator.next();
if (book.getId().equals(bookId))
resultBook = book;
}
} else {
throw new BookException("BOOK_NOT_FOUND");
}
return resultBook;
}
public void deleteBook(String user, String bookId) throws BookException {
if (books.containsKey(user)) {
Set userBooks = (Set) books.get(user);
Iterator bookIterator = userBooks.iterator();
while (bookIterator.hasNext()) {
Book book = (Book) bookIterator.next();
if (book.getId().equals(bookId)) {
userBooks.remove(book);
}
}
books.put(user, userBooks);
} else {
throw new BookException("BOOK_NOT_FOUND");
}
}
}
src/org/gridsphere/gsexamples/services/BookException is just a regular Exception and is contained in the downloadable sourcecode.
One thing you might have noticed is that in all jsp files I use the key attributte for the tags and in the javacode I use something like getLocalizedText. The ui beans do support internationalization so it is very easy to support different languages in your code. The default keys for the translations are held in webapps/WEB-INF/classes/Portlet.properties. Other languages following the standard naming schema for java locale (e.g. german would be webapps/WEB-INF/classes/Portlet_de.properties).
The sharing of data between the portlet java code and the jsp is achieved by putting the objects into the session.
BOOK_TITLE=Book title
BOOK_HEADING=Your Books
BOOK_ISBN=ISBN
BOOK_CONDITION=Condition
BOOK_NEW=new
BOOK_USED=used
BOOK_OLD=old
BOOK_REMARKS=Remarks
BOOK_SAVE=Save
BOOK_ADDED=The Book was added to your list.
BOOK_NOT_FOUND=This book was not found.
BOOK_DELETED=Book was deleted.
OLD=old
NEW=new
USED=used
ALREADY_OWN=You already own
BACK=Back to main page
DETAILED_VIEW=Detailed View
DELETE_BOOK=Delete Book
Since we do created a new portlet a new sections in the webapp/WEB-INF/portlet.xml needs to be added to make the BookPortlet class available as a portlet to the portlet container.
<portlet>
<description xml:lang="en">
Book Portlet
</description>
<portlet-name>BookPortlet</portlet-name>
<display-name xml:lang="en">Books</display-name>
<portlet-class>
org.gridsphere.gsexamples.portlets.BookPortlet
</portlet-class>
<expiration-cache>0</expiration-cache>
<supports>
<mime-type>text/html</mime-type>
</supports>
<supported-locale>en</supported-locale>
<portlet-info>
<title>Book Portlet</title>
<short-title>Books</short-title>
<keywords>books, book</keywords>
</portlet-info>
</portlet>
Now do an ant deploy to deploy the portlet to GridSphere.
After adding the BookPortlet to the layout it will look like this:

Download the source gsexamples-part3.zip
(indlucing all previous examples).
Hi!
The method org.gridsphere.gsexamples.services.BookService.deleteBook wont work. Should be changed to the following.
Cheers