A Well Behaved Portlets

  • November 2019
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View A Well Behaved Portlets as PDF for free.

More details

  • Words: 5,392
  • Pages: 17
A well-behaved Jetspeed portlet Design and construct a Jetspeed portlet that plays well with others By Bob Fleischman, JavaWorld.com, 11/29/04 Jetspeed 2.0 is scheduled for release in early 2005. However, that hardly means that there is no more interest in Jetspeed 1.x. Jetspeed, the open source portal venture from the Jakarta Project, has been around for several years and is in use around the world. This article presents a working example of how to construct a Jetspeed portlet that runs efficiently, adheres to the Model 2 architecture, and, by not interfering with additional portlets, plays well with others. In addition, I demonstrate some simple ways to improve performance and point out the mistakes that can cost you days of debugging time. Before I go any further, let's get the prerequisites out of the way. This article's example is based upon Jetspeed 1.5 and has been tested with a development version of Jetspeed 1.6, which has not been finalized. Please note: This example is not compliant with Java Specification Request 168. JSR 168 is at the heart of Jetspeed 2.0 and is beyond this article's scope. However, a subsequent article will demonstrate how to recast this portlet to comply with Jetspeed 2.0. This article's code is compiled with Java 1.4.2. I run Jetspeed on Tomcat 5.x; although the latest of version of Tomcat 4.x will probably function just as well. The HTML and JavaScript have been tested with both Internet Explorer and Firefox. In addition, I assume the reader is familiar with Jetspeed, has Maven installed, and has tried the simple Jetspeed tutorials.

Before we get started Writing portlets is almost like writing servlets, and, in fact, can conform to the Model 2 architecture. However, portlets must function as part of a larger framework, both in terms of operations and the HTML page; therefore, it is critically important that early in your design, you consider the possibility that your portlet might collide with other portlets on the page. For example, if two different portlets both have a JavaScript function called "Submit_Form()", the page will generate a JavaScript error and neither portlet will function. An intranet portal, such as Jetspeed, allows multiple instances of the same portlet on a page, with each assigned a unique portlet ID. Attributes of one instance can be changed without affecting the siblings, if everything is coded correctly. For example, suppose a portlet allows a user to change the background color. If that portlet appears three times on a portal page, a change in one of the Hiren

1

instances' background colors should not affect the other instances—unless, by design, we want them to change. However, without exercising care in coding the view and the controllers, it is easy to confuse attributes and thus display incorrect data. Local data needs to be kept local. This same portlet example exposes another potential problem on the generated HTML page: when the Change Color button is pressed on the first instance, how do we ensure that the JavaScript and the action controller for the second instance are not fired? The opposite of local data is shared data. Imagine a portlet where all instances display the same data and appear identical—for example, a Telephone List portlet. Changing the help-desk phone number in one instance of the portlet should be reflected in all instances. Decide early whether your portlet requires either a local or shared data model. This article's example portlet uses a local data model. (A follow-up article will demonstrate shared data and database access.)

Our portlet For our example portlet, we want to create a better link-farm, or bookmark, portlet. A rudimentary bookmark portlet ships with Jetspeed. However, according to some discussions in the newsgroups, it might be removed from the distribution. A link farm contains a list of hypertext links ( tags) that jump the user to different Websites. A bookmark portlet can be used in many ways: as a list of search engines, local restaurants, or, as developed for a law firm, a list of Websites associated with the California courts. Bringing these sites together in an easy-to-use format makes for an extremely powerful portlet. Experts in different fields in your organization can keep the farms fresh by updating the links, while normal users have readonly access. Though this type of portlet is frequently used on public pages, users can also add a bookmark portlet to their personal homepages as well. Our business problem is fairly simple: Design a portlet that allows users to maintain lists of URLs that are associated with an optional description and image. In addition, only properly authorized users are allowed to add, edit, delete, and reposition the URLs. The portlet should also support two levels of security. Users with Level 1 security are allowed to make cosmetic changes, such as the number of columns of links to display or how much, if any, of the optional text to display. Users with Level 2 security have the right to edit the underlying data, the links themselves. In addition, it must be possible to instantiate this portlet several times on the same portal page—this is not a specific business requirement, but rather a universal Jetspeed design consideration. This requirement may sound simple, but, as I discuss later, if a portlet's presentation is not properly designed, it does not play well with others, resulting in JavaScript and HTML errors. Figure 1 shows a typical Jetspeed page with two instances of the LinkFarm portlet. One instance houses Search Engine links, while the second contains links to entertainment sites.

Hiren

2

Figure 1. A sample page showing two instances of the LinkFarm portlet. Click on thumbnail to view full-sized image.

Our portlet adheres to the Model 2 architecture, an implementation of the Model-View-Controller architecture. MVC not only helps a developer design a portlet in clean, succinct pieces, it also helps a developer more easily distribute responsibilities and apply modifications later. In designing a solution to our business problem, we have chosen the path that requires no third-party software or toolsets, and achieves our goals in the simplest way possible. One of our business prerequisites requires the links to persist between user sessions. We have chosen to store the links in the portlet page's PSML (Portlet Structure Markup Language) file (if you are unfamiliar with PSML see Resources). Storing data on PSML pages has pros and cons: • •

Pros: It is simple. It requires no third-party software such as a database engine, and finally, it requires no knowledge of or reliance upon a directory structure. Cons: While the portlet can be moved on the page, it can't be moved to another page. PSML entries describe the portlets on a particular page. Therefore, creating a stock portlet prepopulated with links inherited from the LinkFarm portlet and allowing people to add it to their own page would prove difficult. This problem has two possible solutions: Implement export/import functionality—that is, teach the portlet how to dump out, or read in, batches of links. Or move to database- or file-based persistence. However, in this article, we forego such functionality and stick with the simple, basic approach.

Parts of the puzzle Our choice of the Model 2 architecture dictates that we break our design into three basic groups: the models, the views, and the controller. Before we can begin writing code, we must outline the components we need to build—one of the major commandments of good portlet construction: "Thou shall design before thou codes." Model

Hiren

3

For the models, we will need the classes listed below. (The full source code is available in Resources. However, I do not recommend jumping to the code just yet. I'll go over it in more detail later.) I've used the class names so you can easily locate the specific code. All models are in the com.automateddocsys.portal.modules.actions.portlets.linkfarm package. •

• •

LinkFarmLink: This

contains the information for a single link. It has the URL, optional URL to an image, name (or short description), and long description. In addition, it has a position property—an integer, which is used to order the links for presentation purposes and identify them for editing. In this model, URL and image link are of the type URI. DisplayLink: To work well with the edit view, we create a class where all of the properties are strings. We use the Builder pattern to create DisplayLink from LinkFarmLinks. LinkFarmLinks: This class, which we inherited from ArrayList, has a clearly defined purpose: it maintains the list of links presented in any given instantiation of our LinkFarm portlet. In addition, it has some convenience methods. Swap() exchanges two links in the list. We use this method when the user moves the items on the presentation editing screen. Our particular implementation relies heavily on the absence of gaps in the link list's position IDs. The position IDs are required to identify a given link for editing and deleting. The Renumber() function ensures the links are consecutively numbered. As described earlier, we decided to persist the LinkFarmLinks to the PSML. PSML files are referred to as registries with registry entries; reading to and reading from PSML is accomplished by setting and getting attributes of the PortletConfigState, which I discuss in more detail when we actually examine the code.



LinkFarmPSMLPersister: This persistence class contains two methods, getLinks() and saveLinks(). In the real world, we would implement this class as an interface, thereby

allowing the controller to change to database, file, or Web service persistence by simply changing the reference to one class.

View Four views make up the lifecycle of our LinkFarm portlet. We start with the basic view (see Figure 2), which presents the links to the user. This view can be modified to vary the number of columns and to include the long description and/or the image. (Note: The actual implementations of the image URLs have been omitted from this example's code because they made it too complex.)

Hiren

4

Figure 2. Jetspeed standard customization view. Click on thumbnail to view full-sized image. Our second view, the AttributesCustomization view, is provided for free from the Jetspeed framework and allows users with proper security to edit parameter attributes not marked as "hidden." (For more on registry attributes, see Resources.) In our example, we expose columns, view, and editable—see the MyPortlets.xreg file in the source code: <parameter name="columns" value="4" type="int" hidden="false" cachedOnName="true" cachedOnValue="true"/> <parameter name="editable" value="true" type="boolean" hidden="false" cachedOnName="true" cachedOnValue="true"/> <parameter name="view" value="condensed" type="style" hidden="false" cachedOnName="true" cachedOnValue="true">

Actually, our view attribute is a bit more complicated then shown here. We want to use a drop-down list-box on the Customize form. See the properties file for a complete example of how to set up the parameters for the drop-down function. A user with proper security can select the small pencil icon in the portlet's upper right-hand corner to bring up the AttributesConfiguration screen. The DataConfiguration view is where the actual link data is displayed for editing, see Figure 3.

Hiren

5

Figure 3. Our portlet in configuration mode. Notice the icons for up, down, edit, and delete. Click on thumbnail to view full-sized image. In addition to merely displaying the data, this view also contains icons for moving the links up and down, editing, and deleting. Plus, another button adds new links. Finally, we have the SingleLink view. Just as it sounds, this view presents and collects data on a single link, see Figure 4.

Figure 4. Adding and editing information for one link. Click on thumbnail to view full-sized image. We could have combined several of these views in the same Velocity file and controlled the output with various #IF, #ELSE, and #END statements. But doing so is simply not good, clean design and would have violated another of the basic commandments: "Thou shall keep the simple things simple. There shall be no more complexity than required." Controller Our portlet's controller has a critical job that it implements on class MyLinkFarmAction, which is inherited from GenericMVCAction. If you are unfamiliar with actions, see Resources. In brief, actions are classes that fulfill the controller role in the Model 2 architecture. Our simple portlet has a single controller, which, via a switchboard and some other techniques, selects the proper view and assembles the models for presentation. A more detailed explanation of this class follows below.

Putting it all together

Hiren

6

Now the fun begins. We know our environment, we've documented our business problem, and we've outlined our design. It's time to begin construction. Maven Maven supports a Jetspeed plug-in for generating the basic framework for creating a new portlet. See Resources for detailed instructions on setting up and using Maven. This article's source code is based upon the directory structure and files created by the Maven plug-in. If you want to follow along with our code, name your instance HomeSpot. Before we start dissecting the code, let's cover some best practices for constructing portlets. Logging Good coding, good code maintenance, and easy debugging all rely on good information. The best way in a Jetspeed portlet to gather information about what the portlet is doing is to use the logging systems. Many are tempted to use System.out to send messages to the console. Don't. While that approach may appear easy, it hogs resources, quickly leads to overly confusing output, and, under the service-based servlet containers such as Tomcat 5.x, no traditional console exists. All of the System.out traffic is captured in a file. So, if you have to find and open a file anyway, use a log file. Using a log file in Jetspeed is easy. Follow these four steps: 1. 2. 3. 4.

Create a log4j properties.merge file Import the correct units into your class Instantiate the logger Make log calls

The Jetspeed Maven plug-in merges files in the WEB-INF/conf with a .merge extension. For example, the file log4j.properties.merge will be combined with the Jetspeed log4j.properties file in the final configuration. Here is our log4j.properties.merge file: # # Filter OneStop Logging # log4j.category.com.automateddocsys = DEBUG, linkfarm, stdout log4j.additivity.com.automateddocsys = false # # linkfarm.log # log4j.appender.linkfarm = org.apache.log4j.FileAppender log4j.appender.linkfarm.file = ${webappRoot}/WEB-INF/log/linkfarm.log log4j.appender.linkfarm.layout = org.apache.log4j.PatternLayout log4j.appender.linkfarm.layout.conversionPattern = %d [%t] %-5p (%F:%L) - %m%n

Hiren

7

log4j.appender.linkfarm.append = false

Include the following lines in your action class, or any other class where you need logging: // The two imports needed import org.apache.jetspeed.services.logging.JetspeedLogFactoryService; import org.apache.jetspeed.services.logging.JetspeedLogger;

Then add this code to your class: Private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger("com.automateddocsys");

Insert the name of your class or your company in place of com.automateddocsys. This name should match the name in your log4j.properties file. Finally, to place entries in the log, use the following code: logger.debug("in addOne") or logger.error("Error in adding link");

The JetspeedLogFactoryService, which appears in the code above, is the default implementation of Jetspeed's logging service. According to the comments in its source code, it "initializes the underlying logging implementation and acts as a factory for loggers." Understand the logging levels—error, warn, info, debug—and use them carefully and correctly. The log4J.properties file specifies what level of logging to commit to the logs and which output device(s) you want to use. Only logging at or above the level specified will actually be written to the log files. Log files are kept in \WEB-INF\logs. Caching Jetspeed, Turbine, and the Servlet API offer several options for storing objects in memory for quick and easy retrieval of instantiated objects. The LinkFarm portlet has simple caching needs: Collect the links from the PSML page and hold them in the LinkFarmLinks object during the life of the session. In addition, we must pass information, such as portlet state and error messages, between iterations of the portlet rendering. The LinkFarm portlet has one additional twist: multiple instances of the LinkFarm portlet exist, each with a different list of links. If the list is stored with the name LinkFarmLinks, the various instances will write over the others' data. When working with portlets, always assume multiple instances of the portlet with different data can occur, as discussed previously. Hiren

8

Jetspeed features the PortletSessionState class to store data for the life of the session. This class offers two methods, setAttibute(Rundata, String Object) and setAttribute(Portlet [VelocityPortlet], Rundata, String, Object). You must use the latter. Both methods work similarly; they store the Object data in the User object. The difference is the name associated with the Object. The second method appends to the name a portlet ID, a unique ID assigned by Jetspeed, thus guaranteeing uniqueness. Our models The classes that make up the model portion in the LinkFarm portlet are Jetspeed-, portlet-, and even servlet-neutral, as good model classes should be. Models encapsulate data and enforce some data integrity. The persistence class is the exception to this rule. Since the design persists the local links data to the PSML file, it becomes necessary to include references to the Portlet, Rundata, and Context objects. The links are stored as attributes for an entry in the PSML file. Every attribute can have a name and a value; the link data is packed into the value using a pipe-delimited format. XML could be used, but it produces a more complicated solution, and, in this situation, simpler is better. Here is an example of the links stored in the PSML file: <parameter name="link0" value="0|Jakarta|http://jakarta.apache.org||"/> <parameter name="link1" value="1|Jetspeed|http://jakarta.apache.org/portals||"/>

One problem remains: how do we know how many links, and how do we retrieve them when we could potentially have an unlimited number of links. We solve this issue by adding an attribute called numberOfLinks and then naming each link according to its number. The following code, clipped from getAllLinks(), shows how the links are retrieved from the PSML file: maxLinkNumber = Integer.parseInt(portlet.getAttribute(NUMBER_OF_LINKS, "0", rundata)); allLinks = new LinkFarmLinks(); // Only process the list if we have any entries if (maxLinkNumber > 0) { for (int index = 0; index < maxLinkNumber; index++) { try { tempLink = LinkFarmLink.readLink( portlet.getAttribute("link" + String.valueOf(index), null, rundata)); } catch (NullPointerException e) { tempLink = null; } } catch (Exception e) { e.printStackTrace(); } if (tempLink == null) { try{ tempLink = new LinkFarmLink( index,

Hiren

9

"MISSING LINK " + String.valueOf(index), "htp://www.badlink.com", "", "MISSING LINK " + String.valueOf(index)); } catch (Exception e) { e.printStackTrace(); }

} logger.debug(tempLink.writeLink()); allLinks.add(tempLink); }

}

Saving the links uses similar code: // First remove all the existing links int numberOfLinks = 0; LinkFarmLink tempLink = null; logger.debug("In saveAllLinks for " + portlet.getID()); numberOfLinks = Integer.parseInt(portlet.getAttribute(NUMBER_OF_LINKS, "0", rundata)); for (int i = 1; i <= numberOfLinks; i++) { portlet.setAttribute("link" + String.valueOf(i), null, rundata); } // Now write out the allLinks array List allLinks = getAllLinks(portlet, context, rundata); numberOfLinks = allLinks.size(); // Save the number of links portlet.setAttribute(NUMBER_OF_LINKS, String.valueOf(allLinks.size()), rundata); for (int i = 0; i < numberOfLinks; i++) { LinkFarmLink link = (LinkFarmLink) allLinks.get(i); portlet.setAttribute("link" + String.valueOf(i), link.writeLink(), rundata); }

Both of these methods are in the LinkFarmPSMLPersister class. Controller and view To understand how the views operate, it is helpful to understand the type and nature of the objects in the context. Therefore, I discuss the controller class before dissecting the views. A brief word on naming conventions: We preface any parameter passed into a method with the letter p to better understand what originated from outside sources. The exception to this rule is Rundata, Context, and Portlet. The Turbine action model requires us to name our action methods doXyyyzzz(), where X is the only allowable uppercase letter in the name. The remainder of the name must be in lowercase letters; for example: doUpdateedit() and doUpdateconfig().

Hiren

10

The MyLinkFarmAction class, the controller, handles all requests for the LinkFarm portlet regardless of the view, with the exception of the Customize view, which Jetspeed handles. There are two schools of thought concerning how to handle a portlet that has several different views; some argue for a separate action class for each view, others prefer to lump them together. We choose the latter. There is enough of an overlap in the code for loading the context, and the simplicity of copying one class file to add the portlet makes the decision easy. The portlet's state—which is determined by the controller, in doBuildNormalConfig()—establishes which view displays. The state is stored in the portlet-specific cache using PortletSessionState.setAttribute(portlet, rundata, PORTLET_STATE, portletState). The ability to maintain state for a portlet instance demonstrates why we must establish a clear line between different instances of the same portlet. For example, setting the state of the restaurants linkfarm to config should not affect the entertainment link-farm. The portlet's state is stored as an attribute with PortletSessionState.setAttribute(portlet, rundata, PORTLET_STATE, portletState). The constant PORTLET_STATE is declared at the top of the class. The portlet state can take on one of four states, which are also defined in constants at the top of the class: public static final String PORTLET_STATE_NORMAL = "normal"; public static final String PORTLET_STATE_CONFIG = "config"; public static final String PORTLET_STATE_EDIT = "edit"; public static final String PORTLET_STATE_ADD = "add";

The method doBuildNormalConfig() uses a switchboard and the portlet state to determine which view to present as shown in the following: if (portletState.equalsIgnoreCase(PORTLET_STATE_NORMAL)) { templateName = TEMPLATE_NORMAL; setTemplate(rundata, TEMPLATE_NORMAL); addLinksToContext(portlet, context, rundata); } else if (portletState.equalsIgnoreCase(PORTLET_STATE_CONFIG)) { templateName = TEMPLATE_CONFIG; setTemplate(rundata, TEMPLATE_CONFIG); // Override the View context.put(VIEW, "condensed"); addLinksToContext(portlet, context, rundata); } else if ((portletState.equalsIgnoreCase(PORTLET_STATE_EDIT)) || (portletState.equalsIgnoreCase(PORTLET_STATE_ADD))) { templateName = TEMPLATE_EDIT; context.put("link", PortletSessionState.getAttribute(portlet, rundata, PORTLET_MODEL_LINK)); setTemplate(rundata, TEMPLATE_EDIT); }

Hiren

11

Portlet lifecycle Let's now take a 20,000-foot view of how our portlet interacts with the world around it. Note: In the following text, references to specific sections of code are indicated by ({code} - #). {code} is a one or two letter code referring to the following:

1. m - MyLinkFarmAction.java 2. lp - LinkFarmPSMLPersister.java 3. vc - linkFarm1-config.vm

The referenced section of source code, which you will find in Resources is marked with // [#]. Let's assume user Jack Portal has already placed the LinkFarm portlet on his homepage. The first time the page containing the portlet is compiled, the Jetspeed page renderer invokes the MyLinkFarm class, which, in turn, invokes the JetspeedLogFactoryService to retrieve the logging class (m-1): private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger("com.automateddocsys");

After invoking the controller class, the buildNormalConfig() method is invoked. buildNormalConfig() reads the attributes from the portlet registry and puts them onto the context in addParamsToContext (m-2). The attributes include the number of columns of links to display, the view style (condensed or expanded), and whether the links are editable (m-3). You will notice a call to PortletConfigState.getConfigParameter. It is important that you supply a default value in case the preferred parameter is absent. In addition, the portlet's unique ID is stored on the context (m-4): context.put(ORIGINAL_PORTLET_ID, portlet.getID());. The view uses this critical ID to ensure that the HTML entities and JavaScript functions generated by the view do not conflict with other elements on the page. Next, the controller looks in the portlet/session-specific cache for the portlet's state (normal, config, edit, or add). Since this is the first invocation and no state can be found, it is set to normal and then stored in the portlet/session-specific cache discussed above (m-5): String portletState = (String) PortletSessionState.getAttribute(portlet, rundata, PORTLET_STATE); if (null == portletState) { portletState = PORTLET_STATE_NORMAL; PortletSessionState.setAttribute(portlet, rundata, PORTLET_STATE, portletState); }

Hiren

12

Because Jack is displaying this portlet for the first time, no errors exist. However, if one of the action events has passed in an error, it is placed on the context. Errors and other information transfer between the events and the buildNormalConfig() method via the same portlet/session-specific cache used for storing the state (m-6): String errMessage = (String) PortletSessionState.getAttribute(portlet, rundata, ERR_MESSAGE); if (null != errMessage) { // If it exists, put it on the context and then remove it from the SessionState context.put(ERR_MESSAGE, errMessage); PortletSessionState.clearAttribute(portlet, rundata, ERR_MESSAGE); }

The error must then be removed from the cache or it will appear in every portlet rendering. Again, because the portlet is being presented for the first time and is the most common presentation, the view template is set to linkfarm-view (m-7): setTemplate(rundata, TEMPLATE_NORMAL);. Different Velocity .vm files are chosen with the setTemplate() method. And finally, the actual links are assembled, via addLinksToContext(), and placed on the context (m-8): private void addLinksToContext(Portlet portlet, Context context, RunData rundata) { context.put(RETRIEVED_LINKS, LinkFarmPSMLPersister.getAllLinks(portlet, context, rundata)); }

In addLinksToContext(), the LinkFarmLinks persistence class (LinkFarmPSMLPersister.getAllLinks(portlet, context, rundata)) is called to retrieve the list of links (lp-9) from either the portlet/session-specific cache or the PSML file via PortletConfigState. Now the portlet displays on the screen together with other portlets. Jack notices the absence of links and sees just a button that says Edit Links. Jack presses that button, which was created with the following HTML (taken from the linkfarm-view.vm file):


Notice the action URL in the form tag. This is critical to ensuring that the correct firing of methods takes place, an issue I referred to earlier. Rather than refer to the portlet by name or by PSML page, the HTML points directly at this instance of the portlet.

Hiren

13

While it is possible to call an action directly, you should be aware that doing so will cause the action event to fire, but it will lack access to the portlet's instance. Therefore, it has no access to any of the data stored with the portlet and retrievable from PortletSessionState. When Jack presses the Edit Links button, an HTTP request is sent to Jetspeed via the Turbine action methodology, which eventually forwards to the MyLinkFarmAction class, where the doUpdate() event is called. This simple method merely sets the portlet's state to config (m-10). Note that the portlet's state is saved using the portlet/session-specific storage PortletSessionState. If we stored the state in the user's temporary storage, via getUser.setTemp(), which is another way to save data between rerenderings, we would need to develop a naming convention to prevent a portlet instance from retrieving another portlet's state. Suppose we used getUser.setTemp("state",portletState). Using PortletSessionState is critical or all instances of the portlet would be set to the configuration mode. Normal rendering then continues, and buildNormalContext() is eventually called. However, because the portlet state is config, a different template is chosen, and the same data displays with a different view (m-11)—see Figure 3. This view, linkfarm1-config.vm, is the portlet's most complicated view and bears a closer look. First, notice that all of the JavaScript functions and the form elements have $!{js_peid} in front of their names (vc-12). This ensures the function name or element is unique within the HTML page namespace. Even with several portlet instances or another portlet with a function or element of the same name, the uniqueness of your function is preserved. This is especially important when your portlet will be used with others and you have no idea what functions they will create and call. Next, three hidden attributes are used to pass back to the controller the action taken (edit/add/delete/up/down) and the link acted upon (vc-13): ## this is our protection against a refresh causing repeated deletes or adds -we check the count against the actual count

Note that the form is submitted via JavaScript rather than with the traditional Submit button, thus avoiding the problem of a Refresh or Back button firing the URL a second time. Jack clicks on the Add Link hyperlink, which passes a parameter of add to the submit_this() function on the page. submit_this() sets the linkMode attribute and submits the page by programmatically pressing the Submit button named "eventSubmit_doUpdateconfig" which, via the Turbine action methodology, makes its way to the doUpdateconfig() method in the controller (m-14). This method removes the three hidden parameters, linkMode, linkID, and linkCount, from the request. linkCount is compared with the actual number of links. If the numbers deviate, the portlet throws an exception. Such a situation might occur if multiple browser windows are open on the same Hiren

14

page and Jack edits the portlet in more than one place at a time. Similarly, a Refresh or Back button could cause the same effect. By passing in the count, the method can throw an exception if the portlet external count, passed in via the view, differs from the internal count maintained by the model. Next, a simple switchboard if statement is processed: // // Switchboard -- redirect to the proper action // if (linkMode.equalsIgnoreCase(ACTION_DELETE)) { doDeletelink(rundata, context, linkid); // After we have deleted, there is nothing else to do } else if (linkMode.equalsIgnoreCase(ACTION_ADD)) { createLink(rundata, context); // Now go into Add State portletState = PORTLET_STATE_ADD; } else if (linkMode.equalsIgnoreCase(ACTION_EDIT)) { getLinkToEdit(rundata, context, linkid); // Now go into Edit State portletState = PORTLET_STATE_EDIT; } else if (linkMode.equalsIgnoreCase(ACTION_UP)) { doUplink(rundata, context, linkid); } else if (linkMode.equalsIgnoreCase(ACTION_DOWN)) { doDownlink(rundata, context, linkid); } else { logger.error("Bad Link in MyLinkFarmAction doUpdateConfig -linkMode: " + linkMode); };

The Add and Edit actions operate almost identically, with the exception that Add generates a new blank link (m-15) and Edit retrieves a link (m-16). This link is then put on the portlet/session safe cache to be passed down the line. Both actions also reset the portlet state. Processing then continues and returns to buildNormalContext(). The other options—delete, up, and down—all act directly on the model and leave the portlet state alone. Thus, after the action, the screen finishes rendering the portlet, still in the config mode. Had Jack pressed the Done button, the doCancelconfig() event would have fired and the portlet state would have been reset to normal: public void doCancelconfig(RunData rundata, Context context) { logger.debug("in MyLinkFarmAction DoCancelConfig"); // Return to the Normal state Portlet portlet = getPortlet(context); String portletState = PORTLET_STATE_NORMAL; PortletSessionState.setAttribute(portlet, rundata, PORTLET_STATE, portletState); }

Hiren

15

With the Add/Edit state set, the buildNormalContext() retrieves the model link from the cache, adds it to the context, and changes the template view to linkfarm-edit (m-17): if ((portletState.equalsIgnoreCase(PORTLET_STATE_EDIT)) || (portletState.equalsIgnoreCase(PORTLET_STATE_ADD))) { templateName = TEMPLATE_EDIT; context.put("link", PortletSessionState.getAttribute(portlet, rundata, PORTLET_MODEL_LINK)); setTemplate(rundata, TEMPLATE_EDIT); }

Jack then sees the page displaying the add/edit link as shown in Figure 4. Upon completion of editing, Jack presses the Update button, and eventually, via the Turbine and Jetspeed action controller sequence discussed above, the doUpdateEdit() is called. Data from the form is scraped from the request and used to build a DisplayLink (m-18). Using the Builder pattern avoids directly accessing the LinkFarmLinks class's properties and is the preferred method of working with business objects. The DisplayLink—dLink—is then used to either insert or edit a link in the LinkFarmLinks. In either event, should an exception be thrown, an error message is placed on the cache, and the erroneous link is replaced so that Jack can have another chance to correct and submit it. Assuming that no errors were thrown (m-19), the doCanceledit() method is called—the same method called by the Cancel button on the portlet in the Edit view mode. This method resets the portlet state to config and the process continues. Jack modifies the data until he is pleased with both the content and data placement, at which time, he presses the Cancel button and returns to the LinkFarm portlet's normal state. Should Jack desire to change the presentation style, he could press the Customize icon, which presents the Customize view. The portlet now displays the standard view, shown in Figure 5:

Figure 5. The portlet in use. Click on thumbnail to view full-sized image. And that ends our tour.

Hiren

16

What did we learn? Having worked through the entire process for Jack, several important lessons become apparent. 1. Design before you code. 2. Keep the simple things simple, and introduce no more complexity then is absolutely necessary. 3. Use the Model 2 architecture and the GenericMVCAction class. 4. Use the portlet ID, passed to your views in the context, to name all of your JavaScript functions and HTML elements. 5. Use JetspeedLoggingFactory.getLogger, and log your error and debugging messages. Do not use System.out. 6. Use PortletSessionState.setAttribute(Portlet [VelocityPortlet], Rundata, String, Object) to store items on a portlet/session-specific memory cache. Do not use setAttribute(Rundata, String, Object).

Jack is happy; the portlet does exactly what he wants. We're happy because the portlet works well within the Jetspeed environment and can be easily ported to other clients. Our developers are happy because the portlet has small, clearly defined parts, and modifying or enhancing the portlet will prove straightforward. Speaking of enhancements, as an exercise, look at the code and design methods to export or import the links.

Hiren

17

Related Documents

Portlets
November 2019 9
Localising Portlets
April 2020 1
Wdk Portlets
December 2019 11
Well A 1
June 2020 4
Well
April 2020 21