|
|
Creating an Awful Portal Theme - This One's the Best!
How to create a real ugly WebSphere Portal theme. Don' t judge a book by its cover though, this presentation will teach you the ins and out of creating a theme.
This uses WPS 5.0, but the structure of the page really hasn't changed much for 5.1. You can actually use the Portal Project in IRAD to develop
a theme, but this tutorial, with the ugliest and simplest theme ever, will really teach you what a theme is all about.
Besides, how many other portal tutorials, or tutorials in general for that matter, actually reference the patterson film??? Art Bell would be proud.
Please link to our site, support our site, and remember: Happy WebSphere!
Please support our site, link to us, buy some books, and remember:
Happy Java!
|
I'm having a problem with this video in Internet Explorer. Seems to work fine in FireFox.
Themes Tutorial
|
|
|
|
|
The PortletSession If the user is going to be good enough to
provide information to us through html forms and http headers, the least
we could do is keep track of that information, if only for the duration
of the user's visit to our site. To provide a stateful experience for
portal users, portlet developers rely upon the services of the
PortletSession, which, by the way, is remarkably similar to the
HttpSession object from the Servlet API. The PortletSession is easy to
use, and helps maintain a stateful experience for the user, overcoming
the stateless nature of the HTTP protocol. This chapter will look at the
PortletSession, how the PortletSession works, and will even discuss some
of the drawbacks and alternatives to using a PortletSession object.
Canada and the http protocol have one thing in common: they are both
stateless. Canada has ten provinces and three relatively empty
territories, but no states. Of course, they have been eyeing Michigan
for a while. Managing State with the PortletSession Object When a user
visits our site, we often need to keep track of information the user has
provided. Information stored in the PortletRequest or PortletResponse
objects are purged as soon as a response is sent back to the client,
which creates a problem if we want to keep track of information that a
user has provided on previous request-response cycles. To store user
specific information for the duration of the user's interaction with the
server, the Portal API provides a special object called the
PortletSession. Any useful piece of information, in the form of Java
objects, can be stuffed inside of a user's session object. That
information is then available to our portlet on all subsequent
request-response cycles. The PortletSession is effectively tied to the
user for which it is created. The Transient Nature of the PortletSession
One thing to note about the PortletSession is that it is transient ?
it's the hobo of the Portlet API. If the user leaves our site, doesn't
interact with the site for a predetermined amount of time (usually
thirty minutes), or even if the user closes their browser, information
stored in the PortletSession is lost, or at least, the session
containing the information will not longer be tied to the user.
Information stored in the PortletSession is not stored persistently. The
job of the PortletSession is to simply create a stateful experience for
users while interacting with our site. Taking Advantage of the
PortletSession Object Figure 5-1 shows a simple portlet that keeps track
of the number of times the doView method of a portlet is called. The
view count variable is stored in the PortletSession, under the key named
timesvisited. Every time the doView method of the portlet is called, the
view count increases by one. The view count is then displayed back to
the user. Notice how the SimpleSessionPortlet requires some session-data
management. When the portlet is first viewed, there will be no data
stored in the session regarding the view count. On the first iteration,
a timesvisited key, with an assigned value of 1, is placed into the
PortletSession. On subsequent iterations, when the timesvisited key is
present in the session, the associated value is extracted, incremented,
and then stored back in the PortletSession. Figure 5-1 Taking Advantage
of the PortletSession package com.examscam.portlet;import
java.io.*;import javax.portlet.*;public class SimpleSessionPortlet
extends GenericPortlet { protected void doView(RenderRequest request,
RenderResponse response)throws PortletException, IOException {
response.setContentType("text/html"); PrintWriter out =
response.getWriter(); /*grab the PortletSession out of the
PortletRequest*/ PortletSession session = request.getPortletSession();
/*pull the visitCount out of the session. On the first visit, this will
be null*/ String visitCount =
(String)session.getAttribute("timesvisited"); if (visitCount
== null) {
/*if this is the first time viewing the portlet, set the timevisited
value to 1*/ session.setAttribute("timesvisited", ""
+ 1);
out.print("Welcome to our little portlet!");
out.print("Click refresh,
minimize or maximize!!!"); } else { /*if this portlet has been
visited
before, increase the count*/ int newCount = Integer.parseInt(visitCount)
+ 1; session.setAttribute("timesvisited", "" +
newCount);
out.print("Number of times visiting this portlet: ");
out.print(newCount); } }} PortletSessions and User Portlets An
interesting aspect of the PortletSession is the fact that data stored in
the PortletSession, by default, is local and accessible only to the
portlet that created it. By default, data stored in the PortletSession
by one portlet cannot be shared by any other portlets on the same portal
page. If there are four portlets on a portal page, if they all use a
PortletSession object, then each one will have a separate, unshared,
PortletSession namespace. Even if two instances of the same portlet
appear on a portal page, each one will have its own, unshared,
PortletSession namespace. PortletSession and the PORTLET_SCOPE This
default behavior is known as PORTLET_SCOPE, and has been the way the
PortletSession object has behaved for years. However, developers have
done nothing but moan and groan about the fact that it is incredibly
difficult to share information between portlets that exist within the
same portlet application (think war file), so, with the JSR-168 portlet
specification, the Java Gods introduced the concept of a shared,
APPLICATION_SCOPE, for the PortletSession. When a portlet shoves an
object into the PortletSession, and specifies APPLICATION_SCOPE as the
visibility of the data, any portlet that is part of that same portlet
application, can obtain that data by providing the appropriate key when
making a call to the PortletSession's getAttribute method.
PortletSession Constants The JSR-168 API introduces two new constants,
or should I say, final static variables, in the PortletSession class.
These two new contains are called APPLICATION_SCOPE and PORTLET_SCOPE,
and represent the numbers 0x01 and 0x02 respectively. /* constants
representing the PortletSession scopes*/
PortletSession.APPLICATION_SCOPE //0x01 PortletSession.PORTLET_SCOPE
//0x02 Newly Overloaded PortletSession Methods In a typical Servlet and
JSP based application, along with legacy JetSpeed and proprietary
Portlet APIs, the setAttribute method is used to save data to the
PortletSession. The setAttribute method takes a name/value-object pair
as arguments. Data is pulled out of the session with a call to
getAttribute, with the name provided as a parameter; the associated
value-object is then returned to the calling program. However, in the
JSR-168 Portlet API, the setAttribute and getAttribute methods have been
overloaded, with an extra parameter added, that allows for one of the
two new portlet scopes to be specified. /* default mechanism for adding
to a PortletSession */ session.setAttribute(?key?, ?value?); /*
overloaded mechanism for adding to a PortletSession*/
session.setAttribute(?key?, ?value?, PortletSession.PORTLET_SCOPE);
/*placing data into the APPLICATION_SCOPE of a PortletSession*/
session.setAttribute(?key?, ?value?, PortletSession.APPLICATION_SCOPE);
/* default mechanism for pulling from a PortletSession */
session.getAttribute(?key?); /* overloaded mechanism for pulling from a
PortletSession*/ session.setAttribute(?key?,
PortletSession.PORTLET_SCOPE); /*pulling data from the
APPLICATION_SCOPE*/ session.setAttribute(?key?,
PortletSession.APPLICATION_SCOPE); PortletSession and the
APPLICATION_SCOPE When data placed into the PortletSession is given
APPLICATION_SCOPE, the data is visible to all other portlets that are
packaged as part of the same portlet application. Portlets are part of
the same portlet application when they are defined in a common
portlet.xml file, and packaged in a common war file. Many developers
believe that data stored in the APPLICATION_SCOPE of the PortletSession
is available to all of the portlets a users might access on the portal.
This is simply not the case. While APPLICATION_SCOPE allows a degree of
sharing of data, the data is not shared with portlets that are defined
in separate war files. All portlets sharing APPLICATION_SCOPE data must
be defined in a common portlet.xml file. Class Diagram of the
PortletSession ?The PortletSession interface provides a way to identify
a user across more than one request and to store transient information
about that user. A PortletSession is created per user client per portlet
application. A portlet can bind an object attribute into a
PortletSession by name. The PortletSession interface defines two scopes
for storing objects: APPLICATION_SCOPE PORTLET_SCOPE All objects stored
in the session using the APPLICATION_SCOPE must be available to all the
portlets, servlets and JSPs that belongs to the same portlet application
and that handles a request identified as being a part of the same
session. Objects stored in the session using the PORTLET_SCOPE must be
available to the portlet during requests for the same portlet window
that the objects where stored from. Attributes stored in the
PORTLET_SCOPE are not protected from other web components of the portlet
application. They are just conveniently namespaced. The portlet session
is based on the HttpSession. Therefore all HttpSession listeners do
apply to the portlet session and attributes set in the portlet session
are visible in the HttpSession and vice versa.? -Description of the
PortletSession from the IBM/Sun Portal API JavaDocs Reading from the
APPLICATION_SCOPE Our SimpleSessionPortlet defined in Fig. 5-1, places a
hitcount into the session using the key timesvisited. To allow another
portlet to access this hitcount, we could change the scope of the
timesvisited key to APPLICATION_SCOPE, and have a second Portlet, which
we will name SessionViewerPortlet, pull the key out of the
APPLICATION_SCOPE. package com.examscam.portlet; import java.io.*;import
javax.portlet.*; public class SessionViewerPortlet extends
GenericPortlet { protected void doView (RenderRequest request,
RenderResponse response) throws PortletException, IOException {
response.setContentType("text/html"); PrintWriter out =
response.getWriter(); PortletSession session =
request.getPortletSession(); Object count =
session.getAttribute("timesvisited",
PortletSession.APPLICATION_SCOPE);
if (count != null) { out.print("I found the count in the session:
");
out.print(count.toString()); } else { out.print("Couldn't find the
count
in the session."); } } } While the SessionViewerPortlet looks for
the
timesvisited key in the APPLICATION_SCOPE, the lookup will be
unsuccessful until the SimpleSessionPortlet explicitly places the
timesvisited key in the appropriate scope. Figure 5-2 Taking Advantage
of the PortletSession package com.examscam.portlet;import
java.io.*;import javax.portlet.*;public class SimpleSessionPortlet
extends GenericPortlet { protected void doView(RenderRequest request,
RenderResponse response) throws PortletException, IOException {
response.setContentType("text/html"); PrintWriter out =
response.getWriter(); PortletSession session =
request.getPortletSession(); String visitCount =
(String)session.getAttribute( "timesvisited",
PortletSession.APPLICATION_SCOPE); if (visitCount == null) { /*if this
is the first time viewing the portlet, set the timevisited value to 1*/
session.setAttribute("timesvisited", "" + 1,
PortletSession.APPLICATION_SCOPE); out.print("Welcome to our little
portlet!"); out.print("Click refresh, minimize or
maximize!!!"); } else
{ int newCount = Integer.parseInt(visitCount) + 1;
session.setAttribute("timesvisited", "" + newCount,
PortletSession.APPLICATION_SCOPE); out.print("Number of times
visiting
this portlet: "); out.print(newCount); } }} Now that the key
timesvisited is stored in the scope of the application, both the Session
ViewerPortlet, and the SimpleSessionPortlet, can share information
through the PortletSession. But notice how the two portlets are out of
sync, with one showing a count of 2, and the other showing a count of 3?
This very issue will lead us into the next chapter on the action
processing phase of a portlet. Portlet.xml File for PortletSession
Examples
<?xml version="1.0" encoding="UTF-8"?>
<portlet-app
xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"
version="1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd
http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"
id="PortletSessions.8a76e53fe0"> <portlet>
<portlet-name>SessionViewerPortlet</portlet-name>
<display-name>SessionViewerPortlet</display-name>
<portlet-class>
com.examscam.portlet.SessionViewerPortlet </portlet-class>
<supports> <mime-type>text/html</mime-type>
<portlet-mode>view</portlet-mode> </supports>
<portlet-info>
<title>SessionViewerPortlet</title>
</portlet-info> </portlet> <portlet>
<portlet-name>SimpleSessionPortlet</portlet-name>
<display-name>SimpleSessionPortlet</display-name>
<portlet-class>
com.examscam.portlet.SimpleSessionPortlet </portlet-class>
<supports> <mime-type>text/html</mime-type>
<portlet-mode>view</portlet-mode> </supports>
<portlet-info>
<title>SimpleSessionPortlet</title>
</portlet-info> </portlet> </portlet-app>
The NumberGuesser and the PortletSession Our NumberGuesserPortlet could
really use a good dose of the PortletSession object. Currently, our
NumberGuesserPortlet thinks up a number, and asks the user to guess it,
but it doesn't give the user a chance to keep trying until they guess
the magic number correctly. By incorporating the PortletSession object
into the NumberGuesserPortlet, we could store both the magic number and
the number of guessing attempts. We could even give the user a hint as
to whether they should guess higher or lower. When the user guesses
successfully, we can then provide a message that indicates their
success, and subsequently displays the number of attempts it took. Our
improved NumberGuesserPortlet appears in Figure xxx-2 Be aware that
changes will be required in the corresponding JSP in order for the
portlet to function properly. The JSP changes are listed in Figure
6xxx-5. Tips for Manipulating the PortletSession Information is pulled
out of the PortletSession using the getAttribute(String) method. Objects
are put into the PortletSession using the setAttribute(String, Object)
method. The name used to put an object into the session must exactly
match the name used to pull the object out. Spellling mistakes, or even
a different caSinG of letters will cause the return of a null object.
Always try to avoid NullPointerExceptions. J The PortletSession also
includes a method called removeAttribute(String). If you no longer have
use for an object you have stored in the PortletSession, that object
should be removed from the session. The misuse of session data is one of
the most significant performance bottlenecks a portal server, or any
J2EE application server for that matter, will encounter. Eliminating
unnecessary objects from your PortletSession, and avoiding what is known
as ?session bloat,' will help you avoid performance problems at runtime.
Figure 5-3 The New and Improved NumberGuesserPortlet package
com.examscam.portlet;import java.io.*;import javax.portlet.*;public
class NumberGuesserPortlet extends GenericPortlet {protected void doView
(RenderRequest request, RenderResponse response) throws
PortletException, IOException {PortletContext context =
this.getPortletContext();PortletSession session =
request.getPortletSession();if
(session.getAttribute("magicnumber") ==
null) { /* generate a magic number to start the game*/ int magicNumber =
(int) (System.currentTimeMillis() % 9) + 1; /* populate the session with
pertinent Java objects*/ session.setAttribute("magicnumber",
new
Integer(magicNumber)); session.setAttribute("guesses",
"0"); /* place
the message in the session*/ session.setAttribute("message",
"Guess the
number!");} else { /* get the magicNumber and number of guesses out
of
the session*/ /* NOTE: what will happen if the user didn't type anything
in? */ Integer magicNumber = (Integer)
session.getAttribute("magicnumber"); String guesses = (String)
session.getAttribute("guesses"); /* increment the number of
guesses*/
guesses = "" + (Integer.parseInt(guesses) + 1);
session.setAttribute("guesses", guesses); /* what did the user
guess? */
Integer guess = new Integer(request.getParameter("number"));
/* figure
out a message to return to the user*/ if (guess.intValue() >
magicNumber.intValue()) { session.setAttribute("message",
"Guess
lower!"); } else { session.setAttribute("message",
"Guess higher!"); }
if (guess.intValue() == magicNumber.intValue()) { String message =
magicNumber + " is correct. Play again!";
session.setAttribute("message", message); /* purge the session
of
unneeded data*/ session.removeAttribute("magicnumber"); }}/*
forward to
the jsp for display */String url =
"/numberguesser.jsp";context.getRequestDispatcher(url).include(request,
response);}} Inspecting the Better NumberGuesserPortlet The first two
lines of our improved portlet simply declares the PortletSession and
PortletContext objects for easy access later on in the portlet. The next
step is to see if a magic number exists in the session. If there is no
magic number in the session, then we assume the user is looking at this
portlet for the first time, or they are re-starting the guessing
process. In that case, we create a new magic number and stuff that
number into the PortletSession. We also set the number of guesses to
zero, after all, the number guessing game is just starting. Once all of
our objects have been initialized, and stuffed into the PortletSession,
we can forward to a JSP page, but before we do that, we stuff a little
message into the PortletSession. The message simply says ?Guess the
number!!!? This message will be printed out by the JSP. Figure 5-4
"""public class NumberGuesserPortlet extends
GenericPortlet {protected
void doView (RenderRequest request, RenderResponse response) throws
PortletException, IOException {PortletContext context =
this.getPortletContext();PortletSession session =
request.getPortletSession();if
(session.getAttribute("magicnumber") ==
null) { /*generate a magic number to start the game*/ int
magicNumber=(int)(System.currentTimeMillis() % 9)+1; /*populate the
session with pertinent Java objects*/
session.setAttribute("magicnumber", new Integer(magicNumber));
session.setAttribute("guesses", "0"); /*message is
set in the session*/
session.setAttribute("message", "Guess the
number!");}"""} When Form
Data is Present If request.getParameter(?number?) does not return null,
then indeed the user has just submitted a form, trying to guess the
magic number. In this case, we grab the magic number from the session,
then we grab the number of guesses from the session, and finally we grab
the number guessed by the user, using the request.getParameter(?number?)
method. ***Note that we have been lazy so far. If the use clicks submit
without typing anything into the textfield, or types in non-numeric
data, we are going to have a NumberFormatException. If the number is too
high, we send them to the numberguesser.jsp file with a message stuffed
into the session object instructing them to guess lower.
session.setAttribute("message", "Guess lower!"); If
the number is too
low, we send them to the numberguesser.jsp file with a message stuffed
into the request object instructing them to guess higher.
session.setAttribute("message", "Guess higher!"); If
the user guesses
the magic number correctly, we stuff a friendly message in the session
object, indicating that the user has successfully guessed the magic
number. After updating the message, we remove the magic number from the
session. After all, it is not needed now that the number has been
guessed. If the user wants to play again, we'll just generate a new
magic number, and set their number of guesses to zero. Figure 5-5
"""if
(guess.intValue() == magicNumber.intValue()) { String message=
magicNumber + " is correct. Play again!";
session.setAttribute("message", message); //Purge the session
of
unneeded data
session.removeAttribute("magicnumber");}"""
Debriefing the
NumberGuesser's JSP Pages A JSP is used to generate the markup needed to
display the portlet on a portal page. The JSP used in this example is
displayed in Figure 6xxx-5. Figure 5-6 Generating a View with a JSP
<%@
page contentType="text/html"%><%@taglib
uri="http://java.sun.com/portlet"
prefix="portlet"%>
<portlet:defineObjects />
<%=renderRequest.getPortletSession().
getAttribute("message")%>
<FORM action=""><portlet:renderURL />">I'm
thinking of a number between
1 and 10. <BR />
<INPUT name="number" size="10"
type="text" /><INPUT value="Guess!!"
type="submit" /></FORM>
Number of guesses:<%=renderRequest.getPortletSession()
.getAttribute("guesses")%> The biggest change for the JSP
on this
iteration of the NumberGuesserPortlet is the presence of the expression:
<%=renderRequest.getPortletSession().getAttribute(?message?)%>
just
before the input field. Inside the NumberGuesserPortlet, a message is
placed in the session scope, indicating whether a user should guess
higher, lower, or if the user is guessing for the first time, simply to
guess randomly. This message is then printed out just above the
textfield in the numberguesser.jsp page. This script allows a single JSP
page to be used dynamically, and render itself slightly differently,
depending upon the state of the application. Our improved form also
displays to the user how many guesses they have made at the magic
number, based on the guesses key placed into the PortletSession during
the doView method. Number of guesses:
<%=renderRequest.getPortletSession()
.getAttribute("guesses")%> Question
5-1 What type of data is acceptable for the storage in the
PortletSession? ¡ a) any class that inherits from java.lang.Object¡ b)
any String value¡ c) any serializable Java object¡ d) any serializable
String value Question 5-2 Programmatically, what is the process for
storing transient data in the PortletSession? ¡ a) call the
setAttribute(name, value) method of the PortletSession, followed by the
store() method¡ b) call the setAttribute(name, value) method of the
PortletSession, followed by the save() method¡ c) call the
setAttribute(name, value) method of the PortletSession, followed by the
set() method¡ d) call the setAttribute(name, value) method of the
PortletSession Question xxx-3 Information stored in the PortletSession
with the visibility of APPLICATION_SCOPE: ¡ a) will be stored
persistently¡ b) will be stored until the portlet application it is
associated is stopped¡ c) will be destroyed when the user's session
times out ¡ d) will be stored until the portal server is shut down
Question 5-4 Which of the following is true about the JSR-168
PortletSession? ¨ a) Data stored in the PortletSession is also available
to the HttpSession of the Servlet and JSP API¨ b) Data stored in the
HttpSession is also available to Portlets through the PortletSession¨ c)
The PortletSession inherits from the HttpSession of the Servlet and JSP
API¨ d) The PortletSession is based on the HttpSession of the Servlet
and JSP API Question 5-5 A portlet needs to share session data with
another portlet defined in the same portlet.xml file. Which
PortletSession scope should be used? ¡ a) PORTLET_SCOPE¡ b)
SESSION_SCOPE¡ c) APPLICATION_SCOPE¡ d) This type of session sharing is
not possible Answer 5-1 What type of data is acceptable for the storage
in the PortletSession? ¡ a) any class that inherits from
java.lang.Object¡ b) any String value¡ c) any serializable Java object¡
d) any serializable String value Answer c) is correct. While
non-serialized objects can safely be placed in the PortletSession on a
non-distributed environment, in order to have portlet applications
behave properly in a distributed environment that employs multiple JVMs,
you must ensure that any object placed in the PortletSession is
serializable. Answer 5-2 Programmatically, what is the process for
storing transient data in the PortletSession? ¡ a) call the
setAttribute(name, value) method of the PortletSession, followed by the
store() method¡ b) call the setAttribute(name, value) method of the
PortletSession, followed by the save() method¡ c) call the
setAttribute(name, value) method of the PortletSession, followed by the
set() method¡ d) call the setAttribute(name, value) method of the
PortletSession Answer d) is correct. In order to save data in the
PortletSession, you simply call the setAttribute method, providing a
name for the object being placed in the session, along with the object
itself. Only the PortletPreferences object needs an explicit call to
store() in order to ensure that data is saved persistently for the user.
Answer 5-3 Information stored in the PortletSession with the visibility
of APPLICATION_SCOPE: ¡ a) will be stored persistently¡ b) will be
stored until the portlet application it is associated is stopped¡ c)
will be destroyed when the user's session times out ¡ d) will be stored
until the portal server is shut down Answer c) is correct.
PortletSession data, regardless of whether it is APPLICATION_SCOPE or
PORTLET_SCOPE, will be only stored transiently. Answer 5-4 Which of the
following is true about the JSR-168 PortletSession? ¨ a) Data stored in
the PortletSession is also available to the HttpSession of the Servlet
and JSP API¨ b) Data stored in the HttpSession is also available to
Portlets through the PortletSession¨ c) The PortletSession inherits from
the HttpSession of the Servlet and JSP API¨ d) The PortletSession is
based on the HttpSession of the Servlet and JSP API Answers a) and d)
are correct, with d) actually being lifted directly from the
PortletSession JavaDoc. Since the PortletSession is essentially the
HttpSession of the user, data stored in the HttpSession becomes
available to the user through the PortletSession. Answer 5-5 A portlet
needs to share session data with another portlet defined in the same
portlet.xml file. Which PortletSession scope should be used? ¡ a)
PORTLET_SCOPE¡ b) SESSION_SCOPE¡ c) APPLICATION_SCOPE¡ d) This type of
session sharing is not possible Answer c) is correct. The
APPLICATION_SCOPE makes it possible to share session data across
portlets that are part of the same portlet application.
|