Information portals based upon the Oracle Portal product hold tremendous promise for data publication. In developing portals, a common methodology for implementing the provider and portlet packages involves creating separate PL/SQL packages for each individual portlet. While that approach is ideal for publishing individual portlets to the developer community, it creates severe maintenance problems for organizations using Portal for intranet development involving dozens, and perhaps hundreds, of custom portlets. I'll discuss a way to simplify the structure of portlet libraries in Portal packages.
The Power of Portals
Throughout the history of corporate enterprise, particularly in our age of technology, the lifeblood of any organization is truly information. The dissemination of it — to the right people in the right form — is crucial to providing world-class service to valued clients, keeping employees informed about the company in a timely manner, and presenting compelling product and service information to prospective customers. Yet the underlying technology has not always maximized this potential.
In the case of the Internet, the rapid adoption of the Web provided a worldwide infrastructure for delivering an organization's message. Initially, a manageable number of HTML pages appeared sufficient for publishing limited amounts of static information. But the inevitable growth of Web pages on corporate sites — both public and later private — resulted in innumerable versioning and presentation inconsistencies. These difficulties were proportional to the sophistication and breadth of these Internet and intranet sites, and hence to the extent to which each individual firm took advantage of this new medium. Recent extensions to HTML (such as CSS, XSL, and Dynamic HTML) have helped somewhat, but the technologies aimed at dynamic publishing have invariably resulted in even more moving parts, and a resultant increase in risk, configuration issues, and performance problems. Furthermore, most of these technologies are file-centric, and the capabilities of the database, for data management and dynamic generation, have been under-utilized.
Enterprise portals, however, offer a real opportunity to address these challenges and improve corporate information publishing. Having evolved beyond the simple aggregation of a Web user's customized data feeds, portals now have the capability to serve as focal points for online business data. Portals allow an organization to present information — its own and that garnered from other sources — in pages readable with the ubiquitous Web browser, which has quickly become the most common computer interface. For the typical company, this approach can enhance the accessibility of all their Web-based information, particularly for their dynamic data, in addition to the traditional static data. Specifically, they can more easily unify the interfaces to that information, standardize its format, and better control its security.
Oracle Portal is Oracle's architecture for general portlet and portal development. It was formerly known as WebDB, and included with the Oracle8i Database. It has since been enhanced and renamed, and is now bundled with Oracle9i Application Server (Oracle9iAS). Oracle Portal gives users the ability to create and administer information portals, and in turn, integrate internal as well as public information, customize its look and feel, and more effectively deploy Web-based database applications. Every Portal Web page consists of one or more portlets, each of which accepts and delivers data using any browser-capable technology, including of course the most common one, HTML.
In order to jumpstart Portal development, Oracle offers the Portal Development Kit (PDK), a framework integrated with Oracle Portal, intended for the creation of portlets and services on any platform running an Oracle-based portal. The PDK is designed with autonomous portlets in mind, making it easier for corporate subscribers to pick and choose which portlets to install. Thus, Oracle developers can create new types of portlets with a common distribution mechanism, as set forth in the structure of the sample provider and portlet packages in the PDK. This paper presents a design for these packages that allows developers to incorporate new portlets without incurring the maintenance overhead implicit in a separate PL/SQL package for each one.
The Proliferation of Portlets
When considering the adoption of any new technology such as Oracle Portal, an organization should determine the likely uses for which it can and realistically will be used. Oracle Portal clearly can be leveraged by firms that derive a majority of their business from their public Websites. But this is a minority of companies with any significant IT infrastructure, or, for that matter, companies that make an appreciable use of Oracle products. In fact, not many firms worldwide generate their revenue primarily from e-commerce. Moreover, with the dot-com meltdown, that segment of the corporate world is now even smaller. Thus, for typical organizations to justify the costs of using Oracle Portal, they must be able to demonstrate its value aside from its use for their public sites. (These costs include both the product licensing expenses, hardware and network infrastructure costs, and the development dollars needed for creating and maintaining Portal-based sites.) In other words, the bulk of the usage for this product will derive from its utility in creating corporate intranet sites.
As noted earlier, portals may prove to be the ideal tool for aggregating, standardizing, and controlling the publication of online corporate data, especially internal-only information. If Oracle Portal could be used consistently to gain these advantages, then the use of Oracle Portal can in most cases be financially justified. In fact, such a portal methodology could serve as more than just an architectural framework for Web content. With the proper use of Oracle databases, most if not all of that content could be centrally controlled and dynamically created. Such an approach would bypass the costs, complexity, and structuring issues associated with relying upon a large number of directories and HTML files — as well as Perl and Unix shell scripts, if such are utilized. More important, the content is dynamically generated; hence, the Web pages can easily display the new content as soon as it is refreshed in the Oracle database.
From a source code perspective, one advantage of Oracle Portal software is that the code can be organized in PL/SQL packages. It is critical to note that the default structure of the current version of the PDK is to create a unique package for every portlet. An example of this is the PDK sample provider found in PDK\PLSQL\sample\sample_provider.pkb (within the PDK installation archive file). Throughout this provider code, every call to a portlet subprogram is duplicated for each individual portlet. This is illustrated in the PL/SQL fragment shown in Listing 1, which uses the same approach as the sample PDK code. (To maximize readability, all of the code for this article was written from scratch.) This code would be located in subprograms defined in the provider's package body file, named provider_before.pkb, in our example here.
IF ( p_portlet_id = PORTLET_1 ) THEN RETURN portlet_1_before.get_portlet_info( p_provider_id => p_provider_id, p_language => p_language ); ELSIF ( p_portlet_id = PORTLET_2 ) THEN RETURN portlet_2_before.get_portlet_info( p_provider_id => p_provider_id, p_language => p_language ); ELSIF ( p_portlet_id = PORTLET_3 ) THEN RETURN portlet_3_before.get_portlet_info( p_provider_id => p_provider_id, p_language => p_language ); END IF; Listing 1: Redundant code to call portlet functions.
In this code fragment, retrieving the needed information about a particular portlet involves executing a separate — yet almost identical — (ELS)IF clause, and then calling a separate and nearly identical function in its own portlet package. This duplication of code will result in a higher risk of introducing new bugs into the framework, a greater number of execution paths during unit testing, and the likely repetition of effort when modifications are needed in the future.
Using the traditional PDK approach, each of the three portlets would need its own package, containing subprograms that register, de-register, and show the particular portlet, return its properties, and determine whether it is runnable. An example of this is the PDK sample portlet found in PDK\PLSQL\sample\param_portlet.pkb. Continuing with our code in Listing 1, an almost identical get_portlet_info() function must consequently exist for every portlet. Focusing on the first portlet (for instance), this function would be in the portlet's package body file portlet_1_before.pkb. The resulting get_portlet_info() function is illustrated in Listing 2. Note that it uses a package constant, PORTLET_1, for the portlet ID, which is defined as the value 1 in the provider's package specification.
FUNCTION get_portlet_info( p_provider_id IN INTEGER, p_language IN VARCHAR2 ) RETURN wwpro_api_provider.portlet_record IS portlet wwpro_api_provider.portlet_record; BEGIN portlet.id := provider_before.PORTLET_1; portlet.provider_id := p_provider_id; portlet.name := 'Portlet_1'; portlet.title := 'Portlet One'; portlet.description := 'First portlet'; portlet.image_url := NULL; portlet.thumbnail_image_url := NULL; portlet.help_url := NULL; portlet.timeout := NULL; portlet.timeout_msg := NULL; portlet.implementation_style := NULL; portlet.implementation_owner := NULL; portlet.implementation_name := NULL; portlet.content_type := wwpro_api_provider.CONTENT_TYPE_HTML; portlet.api_version := wwpro_api_provider.API_VERSION_1; portlet.has_show_edit := FALSE; portlet.has_show_edit_defaults := FALSE; portlet.has_show_preview := FALSE; portlet.call_is_runnable := NULL; portlet.call_get_portlet := NULL; portlet.accept_content_type := NULL; portlet.has_show_link_mode := NULL; portlet.language := wwnls_api.AMERICAN; portlet.preference_store_path := NULL; portlet.created_on := SYSDATE; portlet.created_by := wwctx_api.get_user; portlet.last_updated_on := SYSDATE; portlet.last_updated_by := wwctx_api.get_user; RETURN portlet; END get_portlet_info; Listing 2: Portlet function get_portlet_info() duplicated for every portlet.
Clearly, the addition of a new portlet to the provider would entail the creation of another package just for that one portlet. Moreover, all provider subprograms that refer to the portlets would require the insertion of another ELSIF clause in every IF statement that references subprograms in the portlet's package.
As a result of this structure, any organization that creates a significant intranet or Internet site that relies heavily upon custom portlets, and uses the current PDK as a starting point for their portal development, will end up with tens if not hundreds of packages to maintain. It would be a shame to replace the proliferation of HTML files with the equally costly proliferation of Portal packages. But this is precisely what happens with the default PDK approach.
Restructuring the Provider and Portlet Code
To avoid the creation of excessive packages, I extract the redundant code, thus allowing almost all of the portlet subprograms to be free of references to individual portlets. Instead, they can handle all of provider's portlets generically. This approach involves defining and using an index-by table to store all of the portlet-specific information, thereby allowing the processing of multiple portlets in a single package. As before, the portlet IDs are defined as package constants. The code in Listing 3 shows the definition of such a portlet index-by table and the portlet IDs. This would be done in the provider's package specification file, named, for instance, provider_after.pks.
TYPE portlet_type IS RECORD( portlet_id INTEGER, portlet_package VARCHAR2(30), portlet_title VARCHAR2(30), portlet_name VARCHAR2(30), portlet_description VARCHAR2(60), has_show_edit BOOLEAN := FALSE, has_show_edit_defaults BOOLEAN := FALSE, has_show_preview BOOLEAN := FALSE ); TYPE portlet_table_type IS TABLE OF portlet_type INDEX BY BINARY_INTEGER; portlets portlet_table_type; PORTLET_1 CONSTANT INTEGER := 1; PORTLET_2 CONSTANT INTEGER := 2; PORTLET_3 CONSTANT INTEGER := 3; Listing 3: Portlet index-by table and portlet IDs.
The portlet index-by table is initialized in the global section of the provider's package body (in provider_after.pkb), as seen in the block of code shown in Listing 4.
portlets(PORTLET_1).portlet_id := PORTLET_1; portlets(PORTLET_1).portlet_name := 'Portlet_1'; portlets(PORTLET_1).portlet_title := 'Portlet One'; portlets(PORTLET_1).portlet_description := 'First portlet'; portlets(PORTLET_2).portlet_id := PORTLET_2; portlets(PORTLET_2).portlet_name := 'Portlet_2'; portlets(PORTLET_2).portlet_title := 'Portlet Two'; portlets(PORTLET_2).portlet_description := 'Second portlet'; portlets(PORTLET_3).portlet_id := PORTLET_3; portlets(PORTLET_3).portlet_name := 'Portlet_3'; portlets(PORTLET_3).portlet_title := 'Portlet Three'; portlets(PORTLET_3).portlet_description := 'Third portlet'; Listing 4: Initialization of portlet index-by table.
The redesigned get_portlet_info() function is illustrated in Listing 5. It would be in the package body file containing all the portlets, named, in this example, portlets_after.pkb. Its function signature has one modification: The portlet ID is now passed to the function, in addition to the provider ID and language. For the purpose of simplicity, this implementation assumes that the portlet index-by table has been initialized such that every portlet's row number in the index-by table is equal to its portlet ID, as done in Listing 4. If such is not the case in your implementation, then you will need to write and use a lookup function to return the row number of a portlet from the portlet index-by table (given the portlet ID). Another improvement is that the portlet record's language field is no longer hard-coded to wwnls_api.AMERICAN, as is done in the PDK; instead, the parameter p_language is used.
FUNCTION get_portlet_info( p_provider_id IN INTEGER, p_portlet_id IN INTEGER, p_language IN VARCHAR2 ) RETURN wwpro_api_provider.portlet_record IS portlet wwpro_api_provider.portlet_record; BEGIN portlet.id := p_portlet_id; portlet.provider_id := p_provider_id; portlet.name := provider_after.portlets(p_portlet_id).portlet_name; portlet.title := provider_after.portlets(p_portlet_id).portlet_title; portlet.description := provider_after.portlets(p_portlet_id).portlet_description; portlet.image_url := NULL; portlet.thumbnail_image_url := NULL; portlet.help_url := NULL; portlet.timeout := NULL; portlet.timeout_msg := NULL; portlet.implementation_style := NULL; portlet.implementation_owner := NULL; portlet.implementation_name := NULL; portlet.content_type := wwpro_api_provider.CONTENT_TYPE_HTML; portlet.api_version := wwpro_api_provider.API_VERSION_1; portlet.has_show_edit := provider_after.portlets(p_portlet_id).has_show_edit; portlet.has_show_edit_defaults := provider_after.portlets(p_portlet_id).has_show_edit_defaults; portlet.has_show_preview := provider_after.portlets(p_portlet_id).has_show_preview; portlet.call_is_runnable := NULL; portlet.call_get_portlet := NULL; portlet.accept_content_type := NULL; portlet.has_show_link_mode := NULL; portlet.language := p_language; portlet.preference_store_path := NULL; portlet.created_on := SYSDATE; portlet.created_by := wwctx_api.get_user; portlet.last_updated_on := SYSDATE; portlet.last_updated_by := wwctx_api.get_user; RETURN portlet; END get_portlet_info; Listing 5: Generalized portlet function get_portlet_info().
By using a portlet index-by table to contain and centralize all of the portlet-specific information, the procedure get_portlet_info() has been made into a generic subprogram that works for all the portlets. This can easily be done for the other portlet subprograms, such as, deregister(), is_runnable(), and register(). The only exception is the procedure show(), which is used to display the particular portlet on the browser page. More than likely, each portlet will need its own show() procedure, to generate and output the needed markup (portlet text with HTML or XML/XSL tags) to display the specific portlet. But even the call to the show() procedure can be made more generic, as seen in Listing 6.
PROCEDURE show( p_portlet_record IN wwpro_api_provider.portlet_runtime_record ) IS BEGIN IF ( NOT is_runnable( p_provider_id => p_portlet_record.provider_id, p_reference_path => p_portlet_record.reference_path ) ) THEN RAISE wwpro_api_provider.PORTLET_SECURITY_EXCEPTION; ELSIF ( p_portlet_record.exec_mode != wwpro_api_provider.MODE_SHOW ) THEN RAISE wwpro_api_provider.PORTLET_EXECUTION_EXCEPTION; ELSE IF ( p_portlet_record.portlet_id = provider_after.PORTLET_1 ) THEN show_portlet_1( p_portlet_record => p_portlet_record ); ELSIF ( p_portlet_record.portlet_id = provider_after.PORTLET_2 ) THEN show_portlet_2( p_portlet_record => p_portlet_record ); ELSIF ( p_portlet_record.portlet_id = provider_after.PORTLET_3 ) THEN show_portlet_3( p_portlet_record => p_portlet_record ); END IF; END IF; END show; Listing 6: Generalized portlet function show().
The advantages of this overall code restructuring are perhaps best demonstrated by the process of adding a new portlet to a provider. Consider the individual steps that must be taken when using the default design:
- Define a new global constant to represent the portlet ID.
- Write a new PL/SQL package for the portlet, containing five or more new subprograms.
- Add a new ELSIF clause to the provider's deregister_portlet() procedure.
- Add a new ELSIF clause to the provider's get_portlet() procedure.
- Add a new ELSIF clause to the provider's get_portlet_list() procedure.
- Add a new ELSIF clause to the provider's is_portlet_runnable() procedure.
- Add a new ELSIF clause to the provider's register_portlet() procedure.
- Add a new ELSIF clause to the provider's show_portlet() procedure.
This necessitates more effort and changes than this alternative approach, which involves only the following steps:
- Define a new global constant to represent the portlet ID.
- Add a new row of portlet-specific information to the portlet index-by table.
- Create a new show_portlet() procedure.
- Add a new ELSIF clause to the portlet's show() procedure.
The end result of this design is to minimize the work needed to develop and maintain the Portal libraries being developed. Specifically, this design requires less new code, less unit testing, and consequently less risk.
Refactoring Existing Code
The guidelines presented above for generalizing the portlet subprograms, should prove useful if an organization has yet to begin developing their Portal software, or if the Portal packages they have created already are small enough to justify refactoring them along these lines. But an existing portal project whose size prohibits any such rewrite may still be improved, assuming of course that the design mirrors that of the PDK.
In that case, the common code in all of the portlet packages can simply be moved into a central portlet package, thereby eliminating the redundant instances of the identical portlet subprograms. However, it may be tempting to continue this consolidation too far, by moving the common portlet subprograms into the provider package. But this is not recommended, because it violates the spirit of separating the portlets from their provider. In general, the provider and portlet subprograms are best kept physically separated, to reflect their logical separation. Developers tasked to modify such Portal software could be confused by the presence of portlet subprograms in provider packages, and have difficulty locating them initially.
Before development teams begin writing Oracle Portal code for their companies' private and public Websites, it is critical that they select in advance — or at least evolve toward — a methodology that will avoid the unnecessary creation of portlet packages. The design outlined in this paper will avoid individual packages for each portlet and minimize code overhead. In turn, the resulting Portal software will be easier to maintain and extend in the future. The benefits of this approach are most evident when a new portlet needs to be added to the portal.
About the Authors
Michael J. Ross is a database software developer in San Diego, and the Communications Director and Webmaster of the San Diego Oracle Users Group.
Tom Scott is the owner of Scott Consulting, Inc., a San Diego-based consulting firm that specializes in Oracle design and implementation for a variety of industries.