Skip Headers

Oracle® Application Server Portal Developer's Guide 10g Release 3 (10.1.3) B25601-01 |
| Home | Solution Area | Contents | Index

Previous | Next |

# 8 Creating PL/SQL Portlets

Note: In general, Oracle recommends that you build your portlets using Java rather than PL/SQL. For more information on choosing a technology for building your portlets, refer to Chapter 2, "Portlet Technologies Matrix". For more information on building your portlets using Java, refer to Chapter 6, "Creating Java Portlets".

The OracleAS Portal PL/SQL APIs are implemented as a set of PL/SQL packages and objects. Database providers and portlets are deployed to a database schema as PL/SQL packages. This chapter explains how to create PL/SQL portlets based on the Oracle Application Server Portal Developer Kit-PL/SQL (PDK-PL/SQL). To make effective use of this chapter, you should already know PL/SQL and have some familiarity with the PL/SQL Web Toolkit.

This chapter contains the following sections:

- Guidelines for Creating PL/SQL Portlets

- Building PL/SQL Portlets with the PL/SQL Generator

- Building PL/SQL Portlets Manually

- Implementing Information Storage

- Using Parameters

- Accessing Context Information

- Implementing Portlet Security

- Improving Portlet Performance with Caching

- Implementing Error Handling

- Implementing Event Logging

- Writing Multilingual Portlets

- Enhancing Portlets for Mobile Devices

- Registering Providers Programmatically

The source code for many of the examples referenced in this chapter is available as part of PDK-PL/SQL. You can download PDK-PL/SQL from the OracleAS Portal Developer Kit (PDK) page on Oracle Technology Network (OTN):

    http://www.oracle.com/technology/products/ias/portal/pdk.html

When you unzip PDK-PL/SQL, you will find the examples in:

    ../pdk/plsql/starter
    ../pdk/plsql/sample
    ../pdk/plsql/cache
    ../pdk/plsql/sso
    ../pdk/plsql/svcex

You can find the reference for PDK-PL/SQL in:

    ../pdk/plsql/doc

## 8.1 Guidelines for Creating PL/SQL Portlets

When you write your portlets in PL/SQL, you should follow the best practices described in this section:

- Portlet Show Modes

- Recommended Portlet Procedures and Functions

- Guidelines for Mobile Portlets

### 8.1.1 Portlet Show Modes

Just like a Java portlet, a PL/SQL portlet has a variety of Show modes available to it. A Show mode is an area of functionality provided by a portlet. The available Show modes are described more fully in Chapter 6, "Creating Java Portlets":

- Shared Screen mode is described in Section 6.1.1.1, "Shared Screen Mode (View Mode for JPS)"

- Edit mode is described in Section 6.1.1.2, "Edit Mode (JPS and OracleAS Portal)".

- Edit Defaults mode is described in Section 6.1.1.3, "Edit Defaults Mode (JPS and OracleAS Portal)".

- Preview mode is describe in Section 6.1.1.4, "Preview Mode (JPS and OracleAS Portal)".

- Full Screen mode is described in Section 6.1.1.5, "Full Screen Mode (OracleAS Portal)".

- Help mode is described in Section 6.1.1.6, "Help Mode (JPS and OracleAS Portal)".

- About mode is described in Section 6.1.1.7, "About Mode (JPS and OracleAS Portal)".

- Link mode is described in Section 6.1.1.8, "Link Mode (OracleAS Portal)".

To check for the selected Show mode, you can use the constants in the `wwpro_api_provider` package. These constants are listed with their corresponding Show mode in Table 8-1.

Table 8-1 Show Mode Constants in wwpro_api_provider

Show mode | Constant
--- | ---
Shared Screen |
    MODE_SHOW
Edit |
    MODE_SHOW_EDIT
Edit Defaults |
    MODE_SHOW_EDIT_DEFAULTS
Preview |
    MODE_SHOW_PREVIEW
Full Screen |
    MODE_SHOW_DETAILS
Help |
    MODE_SHOW_HELP
About |
    MODE_SHOW_ABOUT
Link |
    MODE_SHOW_LINK

### 8.1.2 Recommended Portlet Procedures and Functions

The primary goal of the portlet's code is to generate the HTML output that displays on a page for all of the Show modes required by OracleAS Portal. Although it is possible to implement the portlet as a set of separate PL/SQL stored program units, organizing the portlet's code into a PL/SQL package is the best way of encapsulating related portlet code and data as a single unit in the database. You also achieve better database performance and ease of portlet maintenance.

As you may recall from Section 2.4, "Deployment Type", requests from OracleAS Portal for a particular portlet go through the portlet's provider. To communicate with its portlets, the provider contains a set of required methods that make calls to the portlet code.

When implementing a portlet as a PL/SQL package, it is a good idea to organize the portlet code in parallel with the provider code. For example, when the provider needs to retrieve information about one of its portlets, it uses its `get_portlet` function. Hence, it makes sense for the portlet to contain a `get_portlet_info` function that returns the requested information when called by the provider's `get_portlet` function. Similarly, it is logical for the provider's `show_portlet` procedure to call the portlet's `show` procedure, which produces the HTML output for a requested Show mode and returns it to the provider.

Table 8-2 describes the recommended procedures and functions for a PL/SQL portlet to communicate effectively with the database provider.

Table 8-2 Recommended Functions and Procedures for PL/SQL Portlets

Procedure/Function Name | Purpose
--- | ---
    get_portlet_info
| Returns the portlet record to the provider.
    show
| Produces HTML output for a requested Show mode and returns it to the provider.
    register
| Initializes the portlet at the instance level. The register procedure should not contain any transaction closing statements, such as COMMIT, ROLLBACK, or SAVEPOINT. OracleAS Portal handles the closing of the transactions itself.
    deregister
| Enables cleanups at the instance level. The deregister procedure should not contain any transaction closing statements, such as COMMIT, ROLLBACK, or SAVEPOINT. OracleAS Portal handles the closing of the transactions itself.
    is_runnable
| Determines whether the portlet can be run. Security checks can be performed in this function.
    copy
| Copies the personalized and default values of portlet preferences from one portlet instance to a new portlet instance when OracleAS Portal makes a copy of the page.
    describe_parameters
| Returns a list of public portlet parameters.

### 8.1.3 Guidelines for Mobile Portlets

OracleAS Portal is capable of rendering its pages for both HTML and non-HTML (mobile) devices. When designing a portlet for a mobile device, you must consider some additional guidelines. The guidelines for mobile portlets are described fully in Section 6.1.3, "Guidelines for Mobile Portlets".

For information on how to build mobile-enabled portlets, refer to Section 8.12, "Enhancing Portlets for Mobile Devices".

## 8.2 Building PL/SQL Portlets with the PL/SQL Generator

To facilitate the development of database providers and PL/SQL portlets, you can use the PL/SQL Generator, a utility that creates installable PL/SQL code for a database provider and its portlets. The PL/SQL Generator is a standalone Web application that receives the provider and portlet definitions in the form of an XML file (similar in format to the `provider.xml` file). The XML tags used for the provider and portlet definition are a subset of the XML tags used for defining Web providers with PDK-Java. The output of the PL/SQL Generator is a SQL script that can be run from SQL*Plus. The script contains SQL commands for installing the provider and portlet packages in the correct order.

You can download the PL/SQL Generator along with its installation instructions from:

    http://www.oracle.com/technology/products/ias/portal/files/plsqlgenerator.zip

The general model for working with the PL/SQL Generator is as follows:

1. Create an XML file that defines the provider and portlets that you want to build, as described in Section 8.2.1, "Creating the Input XML File".

2. Run the PL/SQL Generator using the XML file as input, as described in Section 8.2.2, "Running the PL/SQL Generator".

3. Publish the generated PL/SQL portlet, which includes the following steps:

   - Install the provider generated by the PL/SQL Generator into the database, as described in Section 8.2.3.1, "Installing the Packages in the Database".

   - Register the database provider with the Oracle Application Server, as described in Section 8.2.3.2, "Registering the Database Provider".

   - Add the generated portlet to a page, as described in Section 8.2.3.3, "Adding Your Portlet to a Page".

### 8.2.1 Creating the Input XML File

The source XML file starts and ends with the `<provider>` and `</provider>` tags, and can include one or more portlet definitions. Each portlet definition is bracketed by the `<portlet>` and `</portlet>` tags. A portlet definition includes the XML tags that specify values for the portlet record attributes and enable the links in the portlet header. For example, the `<name>` tag specifies the portlet name in the provider domain and the `<title>` tag specifies the portlet display name or title. When set to true, the `<showEdit>` tag enables the Edit mode for the portlet and the corresponding link in the portlet header. Table 8-3 lists the available XML tags for PL/SQL Generator input.

Table 8-3 XML Tags for PL/SQL Generator Input

XML Tag | Definition | Value Type
--- | --- | ---
    provider
| Encloses provider definition tags. | Not applicable
    portlet
| Encloses portlet definition tags. | Not applicable
    id
| Specifies the portlet ID in the provider. This value must be unique within the provider. | string
    name
| Specifies the portlet name. The name should not contain any spaces. The generator uses the information provided in the `name` tag for the portlet package name. | string
    title
| Specifies the portlet display name. | string
    shortTitle
| Specifies the portlet short display name. This tag is useful for mobile portlets. | string
    description
| Specifies the portlet description. | string
    defaultLocale
| Specifies the language the portlet renders by default. The value is the two letter ISO language and country codes expressed as `language``.``country`. | string
    timeout
| Specifies the portlet's timeout interval in seconds. | number
    timeoutMsg
| Specifies the message to display when the portlet times out. | string
    showEdit
| Indicates whether the portlet supports Edit mode, which enables the user to personalize the portlet's properties. | Boolean
    showEditDefault
| Indicates whether the portlet supports the Edit Defaults mode, which enables page administrators to personalize the default values of the portlet's properties. | Boolean
    showDetails
| Indicates whether the portlet can be viewed in Full Screen mode. In this mode, the entire browser window is dedicated to the portlet. Full screen mode enables the portlet to show more details than when it shares the page with other portlets. | Boolean
    showPreview
| Indicates whether the portlet supports the Preview mode. | Boolean
    hasHelp
| Indicates whether the portlet supports the Help mode. | Boolean
    hasAbout
| Indicates whether the portlet supports the About mode. | Boolean
    language
| Defines the portlet's default language (for example, `en`). | string
    contentType
| Indicates the default content type supported by the portlet. The tag can take one of the following values:
    wwpro_api_provider.CONTENT_TYPE_HTML
    wwpro_api_provider.CONTENT_TYPE_XML
    wwpro_api_provider.CONTENT_TYPE_MOBILE
| string
    apiVersion
| Specifies the version of the OracleAS Portal PL/SQL API to which the portlet conforms. The value should be `wwpro_api_provider.API_VERSION_1`. | string
    callIsRunnable
| Indicates whether OracleAS Portal must check for the user's credentials before displaying the portlet. The default value is `true`. | Boolean
    callGetPortlet
| Indicates whether the portal can use the portlet record data stored in the Portlet Metadata Repository (PMR) instead of contacting the provider for the portlet record. If the portlet record (specified by provider `id`, portlet `id`, and `language`) returned by a provider does not change, then the provider should set the value for `call_get_portlet` to false. This tells the portal to use the PMR instead of making calls to the provider's `get_portlet` and `get_portlet_list` functions. An example of when a provider would not want the portal to use portlet metadata from the PMR is when the value of the portlet records is different for logged on users. The default value is `true`. | Boolean
    acceptContentType
| Specifies a comma-delimited list of content types that the portlet can produce. For example, if a portlet can produce content of both HTML and MOBILEXML type, then the tag value is:
    text/html,text/vnd.oracle.mobilexml
| string
    hasShowLinkMode
| Indicates whether the portlet implements the Link mode. If the value is `false`, the portlet uses its short or full title to display a link label that references the portlet content in a mobile device. Otherwise, a personalized link can be generated in the portlet code. The default value is `false`. | Boolean
    mobileOnly
| Indicates whether the portlet is available only to mobile devices. The default value is `false`. | Boolean
    preferenceStorePath
| Specifies the base preference store path where the provider has stored the portlet personalization information. This path is used when exporting portlets. | string
    createdOn
| Defines the portlet creation date. The default value is `sysdate`. | date
    createdBy
| Identifies the user who created the portlet record. | string
    lastUpdatedOn
| Defines the most recent date on which the portlet record was changed. The default value is `sysdate`. | date
    lastUpdatedBy
| Identifies the user who most recently changed the portlet record. | string
    passAllUrlParams
| Indicates parameter passing behavior in the portlet. If the tag value is `true`, then OracleAS Portal passes all parameters in the URL to the portlet. If the tag value is `false`, then the portlet receives only those parameters that are intended for the portlet. The default value is `true`. | Boolean
    cacheLevel
| Indicates a portlet's cache level. It can take one of the following values:
    wwpro_api_provider.CACHE_LEVEL_SYSTEM
    wwpro_api_provider.CACHE_LEVEL_USER
    wwpro_api_provider.CACHE_LEVEL_PTL_SESSION
| string
    rewriteUrls
| Indicates whether or not URL rewriting will be performed on the output from a portlet render request. The default value is `false`. | Boolean

Following is a sample of the input XML for the PL/SQL Generator. Mandatory information is shown in bold.

    <!-- This is a sample provider.xml file for the PLSQL Generator 1.2 -->
    <provider>
        <portlet>
            <id>1</id>
            <name>Test_Portlet</name>
            <title>Test Portlet Title</title>
            <shortTitle>Short portlet title</shortTitle>
            <description>This is a Test portlet</description>
            <timeout>30</timeout>
            <timeoutMsg>Test Portlet Timed Out</timeoutMsg>
            <showEdit>true</showEdit>
            <showEditDefault>true</showEditDefault>
            <showDetails>true</showDetails>
            <showPreview>true</showPreview>
            <hasHelp>true</hasHelp>
            <hasAbout>true</hasAbout>
            <language>en</language>
            <contentType>wwpro_api_provider.CONTENT_TYPE_HTML</contentType>
            <apiVersion>wwpro_api_provider.API_VERSION_1</apiVersion>
            <callIsRunnable>true</callIsRunnable>
            <callGetPortlet>true</callGetPortlet>
            <acceptContentType>'text/html'</acceptContentType>
            <hasShowLinkMode>false</hasShowLinkMode>
            <mobileOnly>false</mobileOnly>
            <passAllUrlParams>true</passAllUrlParams>
            <cacheLevel>wwpro_api_provider.CACHE_LEVEL_USER</cacheLevel>
            <rewriteUrls>true</rewriteUrls>
        </portlet>
    </provider>

### 8.2.2 Running the PL/SQL Generator

After you have created a valid XML input file, you can run the PL/SQL Generator to generate the provider and portlet packages in the form of a SQL file:

1. If you have not already done so, install the PL/SQL Generator according to the instructions that came with the download.

2. From your browser, go to the URL for the PL/SQL Generator. It should look something like the page shown in Figure 8-1.

   Figure 8-1 PL/SQL Generator Page

   Description of "Figure 8-1 PL/SQL Generator Page"

3. Click Browse and select the source XML file for the Source XML File field. Refer to Section 8.2.1, "Creating the Input XML File" for more information on creating the XML file.

4. In the Provider Name field, enter the name of the provider. The provider name must not contain any spaces. The generator uses the value entered in this field for the provider package name.

5. Click Generate to generate the SQL file that contains the installable PL/SQL code for the provider and portlet packages. When the browser prompts you to save or open the file, choose Save.

6. In the Save dialog box, change the file extension to `.sql` and revise the file name as you wish.

7. Save the file.

### 8.2.3 Publishing the Generated PL/SQL Portlet

After you have run the PL/SQL Generator and obtained a SQL file, you still need to perform the following tasks to make the provider and portlets available to OracleAS Portal:

- Installing the Packages in the Database

- Registering the Database Provider

- Adding Your Portlet to a Page

#### 8.2.3.1 Installing the Packages in the Database

To install the generated provider and portlet packages into the database where you installed OracleAS Portal, perform the following steps:

1. Start a SQL*Plus session and log in to the PORTAL schema.

2. Create a new database schema, the provider schema, to store the generated provider and portlet packages by entering the following commands in SQL*Plus:

       create user provider_schema identified by provider_schema_password;
       grant resource, connect to provider_schema;

3. Grant the EXECUTE privilege for the OracleAS Portal APIs to the provider schema by running the `provsyns.sql` script that is located in the `MID_TIER_ORACLE_HOME/portal/admin/plsql/wwc` directory as follows:

       @provsyns.sql provider_schema

4. Log in to the provider schema and run the generated SQL file. It will create the provider and portlet packages in the database.

#### 8.2.3.2 Registering the Database Provider

After creating the provider and portlet packages in the database, you must register the provider with OracleAS Portal before adding the PL/SQL portlet to a portal page:

1. Log in to OracleAS Portal as an administrator.

2. From the Portal Builder, click the Administer tab then the Portlets tab.

3. In the Remote Providers portlet, click Register a Provider.

4. Fill in the Name, Display Name, Timeout, and Timeout Message as desired.

5. From the Implementation Style, list choose Database.

6. Click Next and complete the remainder of the wizard.

7. When you complete the wizard, click Finish.

8. From the Portlet Repository portlet, click Display Portlet Repository.

9. Browse the repository and find the provider that you just registered. Typically, new providers appear in the Portlet Staging Area of the repository.

10. Once you find the provider, confirm that it contains all of the portlets you created in the provider. If the provider or its portlets do not appear, then retrace the steps in this section and the preceding sections (Section 8.2.3.1, "Installing the Packages in the Database", Section 8.2.1, "Creating the Input XML File", and Section 8.2.2, "Running the PL/SQL Generator") to ensure that you correctly created and registered your provider and portlet.

#### 8.2.3.3 Adding Your Portlet to a Page

Once your provider and its portlets appear in the repository, you can add it to a page. To add your portlet to a page, follow the instructions in the Oracle Application Server Portal User's Guide.

## 8.3 Building PL/SQL Portlets Manually

This section describes how to build a basic PL/SQL portlet using the `hello world` sample contained in the `starter` provider sample. The `starter` provider sample, located in `..\pdkplsql\pdk\plsql\starter` in PDK-PL/SQL (`pdkplsql.zip`), consists of the following files:

- `starter_provider.pks` is the package specification of the `starter` provider.

- `starter_provider.pkb` is the package body of the `starter` provider.

- `helloworld_portlet.pks` is the package specification of the `hello world` portlet.

- `helloworld_portlet.pkb` is the package body of the `hello world` portlet.

- `snoop_portlet.pks` is the package specification of the `snoop` portlet.

- `snoop_portlet.pkb` is the package body of the `snoop` portlet.

- `insintpr.sql` is the installation script for the `starter` provider.

The general model for building PL/SQL portlets manually is as follows:

1. Modify the `hello world` portlet package specification and body to create your own portlet package, as described in Section 8.3.1, "Implementing the Portlet Package".

2. Modify the `starter` provider package specification and body to add your new portlet to a provider, as described in Section 8.3.2, "Implementing the Provider Package".

3. Add your portlet to a page, as described in Section 8.3.3, "Adding Your Portlet to a Page".

### 8.3.1 Implementing the Portlet Package

To modify `helloworld_portlet.pks` and `helloworld_portlet.pkb` to create your own portlet package, perform the following steps:

1. Make copies of the package specification, `helloworld_portlet.pks`, and body, `helloworld_portlet.pkb`.

2. Rename the copies to `my_first_portlet.pks` and `my_first_portlet.pkb`, respectively.

3. Open `my_first_portlet.pks` in an editor and change the name of the package to `my_first_portlet`:

       CREATE OR REPLACE
       package my_first_portlet
       is
       ...

       end my_first_portlet;

4. Open `my_first_portlet.pkb` in an editor and repeat the change that you made in the previous step; that is, change the name of the package to `my_first_portlet`.

5. In `my_first_portlet.pkb`, find the function named `get_portlet_info` and modify it as follows:

       function get_portlet_info
       (
            p_provider_id in integer
           ,p_language in varchar2
       )
       return wwpro_api_provider.portlet_record
       is
           l_portlet      wwpro_api_provider.portlet_record;
       begin
           l_portlet.id := starter_provider.PORTLET_FIRST;
           l_portlet.provider_id := p_provider_id;
           l_portlet.title := 'My First Portlet';
           l_portlet.name := 'My_First_Portlet';
           ...

6. In `my_first_portlet.pkb`, find the procedure named `show` and modify it as follows:

       procedure show
       (
          p_portlet_record        wwpro_api_provider.portlet_runtime_record
       )
       is
           l_portlet      wwpro_api_provider.portlet_record;
           l_text_name in varchar2(100);
           l_text in varchar2(200);
       begin
       ...
               /*
               Display the content of the portlet in the show mode.
               Use the wwui_api_portlet.portlet_text() API when
               generating the content of the portlet so that the
               output uses the portlet CSS.
               */
               htp.p(wwui_api_portlet.portlet_text(
                    p_string   =>  'Hello World - Mode Show'
                   ,p_level    =>  1
                   ));
               /*
               Add the functionality you want here. In this case we are adding
               a welcome message addressed to the current user.
               */
               l_text_name := 'Welcome to my first portlet ' || wwctx_api.get_user;
               l_text := wwui_api_portlet.portlet_text(
                   p_string   =>  l_text_name,
                   p_level    =>  1        );
               htp.p(l_text);        htp.para;
               if (p_portlet_record.has_border) then
                   wwui_api_portlet.close_portlet;
               end if;
       ...

7. Save `my_first_portlet.pkb`.

### 8.3.2 Implementing the Provider Package

After you implement the portlet package, you must add your portlet to a provider. To modify `starter_provider.pks` and `starter_provider.pkb` to add your new portlet to a provider, perform the following steps:

1. Make copies of the package specification, `starter_provider.pks`, and body, `starter_provider.pkb`.

2. Rename the copies to `starter_provider2.pks` and `starter_provider2.pkb`, respectively.

   Note: If you want to create a new, empty provider, remove all references to the `hello world` and `snoop` portlets from `starter_provider2.pks` and `starter_provider2.pkb` before performing the steps that follow.

3. Open `starter_provider2.pks` in an editor.

4. Add a constant called `PORTLET_FIRST`. This constant is used as the identifier for the portlet within the provider. Hence, the constant's value must be unique within the provider.

       CREATE OR REPLACE
       package STARTER_PROVIDER
       is
         /**
           * This package is used as an example to show how providers can be created
           * in the portal system.
           *
           * This provider contains the following portlets:
           *
           * Hello World (PORTLET_HELLOWORLD)
           * Snoop (PORTLET_SNOOP)
           *
           */
           PORTLET_HELLOWORLD constant integer := 1;
           PORTLET_SNOOP      constant integer := 2;
           PORTLET_FIRST      constant integer := 3;

5. Save `starter_provider2.pks`.

6. Open `starter_provider2.pkb` in an editor.

7. In `starter_provider2.pkb`, add a call for the new portlet's `get_portlet_info` function in the `get_portlet` function of the provider package. This step entails adding the call `my_first_portlet.get_portlet_info` in the `get_portlet` function. The `get_portlet` function allows the portal to retrieve information for the portlet when necessary.

       function get_portlet
            p_provider_id in integer
           ,p_portlet_id in integer
           ,p_language in varchar2
       )
       return wwpro_api_provider.portlet_record
       is
       begin
           if (p_portlet_id = PORTLET_HELLOWORLD) then
               return helloworld_portlet.get_portlet_info(
                    p_provider_id  => p_provider_id
                   ,p_language     => p_language
                   );
           elsif (p_portlet_id = PORTLET_SNOOP) then
               return snoop_portlet.get_portlet_info(
                    p_provider_id  => p_provider_id
                   ,p_language     => p_language
                   );
           elsif (p_portlet_id = PORTLET_FIRST) then
               return my_first_portlet.get_portlet_info(
                    p_provider_id  => p_provider_id
                   ,p_language     => p_language
                   );
           else
               raise wwpro_api_provider.PORTLET_NOT_FOUND_EXCEPTION;
           end if;
       end get_portlet;

8. In `starter_provider2.pkb`, add the new portlet to the list of portlets returned by the provider. This step entails adding the new portlet to the `get_portlet_list` function of the provider. The `get_portlet_list` function tells the portal which portlets the provider implements.

       function get_portlet_list
       ...
       begin
           l_cnt := 0;
           if (p_security_level = false ) then            l_cnt := l_cnt + 1;
                   l_portlet_list(l_cnt) := get_portlet(
                        p_provider_id  => p_provider_id
                       ,p_portlet_id   => PORTLET_HELLOWORLD
                       ,p_language     => p_language
                       );
                   l_cnt := l_cnt + 1;
                   l_portlet_list(l_cnt) := get_portlet(
                        p_provider_id  => p_provider_id
                       ,p_portlet_id   => PORTLET_SNOOP
                       ,p_language     => p_language
                       );
                   l_cnt := l_cnt + 1;
                   l_portlet_list(l_cnt) := get_portlet(
                        p_provider_id  => p_provider_id
                       ,p_portlet_id   => PORTLET_FIRST
                       ,p_language     => p_language
                       );
           else
               if (helloworld_portlet.is_runnable(
                    p_provider_id     =>  p_provider_id
                   ,p_reference_path  =>  null)
               ) then
                   l_cnt := l_cnt + 1;
                   l_portlet_list(l_cnt) := get_portlet(
                        p_provider_id  => p_provider_id
                       ,p_portlet_id   => PORTLET_HELLOWORLD
                       ,p_language     => p_language
                       );        end if;
               if (snoop_portlet.is_runnable
                    p_provider_id     =>  p_provider_id
                   ,p_reference_path  =>  null)
               ) then
                   l_cnt := l_cnt + 1;
                   l_portlet_list(l_cnt) := get_portlet(
                        p_provider_id  => p_provider_id
                           ,p_portlet_id   => PORTLET_SNOOP
                           ,p_language     => p_language
                           );
               end if;
               if (my_first_portlet.is_runnable(
                    p_provider_id     =>  p_provider_id
                   ,p_reference_path  =>  null)
               ) then
                   l_cnt := l_cnt + 1;
                   l_portlet_list(l_cnt) := get_portlet(
                        p_provider_id  => p_provider_id
                       ,p_portlet_id   => PORTLET_FIRST
                       ,p_language     => p_language
                       );
               end if;
           end if;
           return l_portlet_list;
       end get_portlet_list;

9. In `starter_provider2.pkb`, modify the `is_portlet_runnable` function to add a call to the `is_runnable` function of the new portlet.

       function is_portlet_runnable
       (
           p_portlet_instance in wwpro_api_provider.portlet_instance_record
       )
       return boolean
       is
       begin
           if (p_portlet_instance.portlet_id = PORTLET_HELLOWORLD) then
               return helloworld_portlet.is_runnable(
                    p_provider_id     =>  p_portlet_instance.provider_id
                   ,p_reference_path  =>  p_portlet_instance.reference_path
                   );
           elsif (p_portlet_instance.portlet_id = PORTLET_SNOOP) then
               return snoop_portlet.is_runnable(
                    p_provider_id     =>  p_portlet_instance.provider_id
                   ,p_reference_path  =>  p_portlet_instance.reference_path
                   );
           elsif (p_portlet_instance.portlet_id = PORTLET_FIRST) then
               return my_first_portlet.is_runnable(
                    p_provider_id     =>  p_portlet_instance.provider_id
                   ,p_reference_path  =>  p_portlet_instance.reference_path
                   );
           else
               raise wwpro_api_provider.PORTLET_NOT_FOUND_EXCEPTION;
           end if;
       end is_portlet_runnable;

10. Repeat step 9 according to the information in Table 8-4.

    Table 8-4 Changes to starter_provider2.pkb

    Procedure/Function | Addition
    --- | ---
        procedure register_portlet
        elsif (p_portlet_instance.portlet_id =
          PORTLET_FIRST) then
        my_first_portlet.register(p_portlet_instance)
        procedure deregister_portlet
        elsif (p_portlet_instance.portlet_id =
          PORTLET_FIRST) then
        my_first_portlet.deregister
          (p_portlet_instance)
        function describe_portlet_parameters
        elsif (p_portlet_id =
          PORTLET_FIRST) then
        return my_first_portlet.describe_parameters
          (p_provider_id, p_language);
        procedure show_portlet
        elsif (p_portlet_record.portlet_id =
          PORTLET_FIRST) then
        my_first_portlet.show(p_portlet_record)
        procedure copy_portlet
        elsif (p_copy_portlet_info.portlet_id =
          PORTLET_FIRST) then
        my_first_portlet.copy(p_portlet_record)

11. Save and close `starter_provider2.pkb`.

12. Log in to OracleAS Portal as you normally would.

13. From the Portal Builder, click the Administer tab then the Portlets tab.

14. From the Portlet Repository portlet, click Display Portlet Repository.

15. Browse the repository and find the starter provider (typically it will appear in the Portlet Staging Area of the repository). It should contain its two original portlets: `hello world` and `snoop`.

16. From a command line prompt, start SQL*Plus and connect as the owner of the `starter` provider schema.

17. Compile the new and modified PL/SQL packages in the following order:

    - `starter_provider2.pks`

    - `my_first_portlet.pks`

    - `starter_provider2.pkb`

    - `my_first_portlet.pkb`

18. If any compilation errors occur, fix and recompile them until all of the packages compile successfully.

19. From the Portlet Repository portlet, click Display Portlet Repository.

20. Browse the repository and find the `starter` provider again. It should now contain your new portlet, `my_first_portlet`, in addition to its original portlets.

    Note: If you make changes to an existing provider or the portlet record, you need to refresh your provider before seeing the changes reflected in your OracleAS Portal instance.

### 8.3.3 Adding Your Portlet to a Page

Your portlet should now be available for adding to pages like any other portlet in the Portlet Repository. To add your portlet to a page, follow the instructions in Oracle Application Server Portal User's Guide.

## 8.4 Implementing Information Storage

OracleAS Portal provides APIs for storing and retrieving individual portlet preferences, and storing and manipulating temporary data for the current session:

- Implementing a Preference Store

- Implementing a Session Store

### 8.4.1 Implementing a Preference Store

OracleAS Portal provides a set of APIs for storing and retrieving individual preferences for each unique portlet instance in a persistent manner. It provides a unique identifier for each individual, a preference store automatically mapped by user, and access mechanisms for storing and retrieving personalization information in your PL/SQL portlets.

By default, when you enable end-user personalization, Personalize appears on the title bar of your portlet. This link displays a form where users can choose settings for that portlet.

End-user personalization options are available through the `wwpre_api_name` and `wwpre_api_value` packages.

#### 8.4.1.1 Using a Preference Store

In general, you can set up preference storage as follows:

1. Create the preference path via `wwpre_api_name.create_path`.

2. Create the preference with `wwpre_api_name.create_name`.

3. Set the preference values by providing the preference name and scoping level for which you want to set the value. Use `wwpre_api_value.set_value_as_varchar2`, `set_value_as_number`, or `set_value_as_date` for this purpose.

4. Get preference values by providing the preference name and path whenever you want to retrieve the preference value. Use `wwpre_api_value.get_value_as_varchar2`, `get_value_as_number`, or `get_value_as_date` for this purpose.

#### 8.4.1.2 Creating and Accessing a Preference Store

The services example, located in `..\pdkplsql\pdk\plsql\svcex` in PDK-PL/SQL (`pdkplsql.zip`), illustrates how you can implement preference storage. The objective is to achieve the following functionality:

- When a user clicks Personalize, they can enter text in two fields.

- The first field prompts for personalized text. The second prompts for a personalized portlet title.

- The values the user enters for these two fields are stored in the preference store.

- The personalized text and portlet titles are retrieved whenever that user invokes the portlet instance.

You can browse through this example as follows to see how to create the preference store, store values in it, and retrieve values from it:

1. Open the `services_portlet.pkb` file in an editor.

   The portlet path and preference names are provided with aliases in the constants part of your portlet definition.

       DOMAIN           constant varchar2(30) := 'provider';
       SUBDOMAIN        constant varchar2(32) := 'services';
       PORTLET_PATH     constant varchar2(256):= 'oracle.portal.pdk.servicesportlet';
       PREFNAME_STRING  constant varchar2(30) := 'services_string';
       PREFNAME_TITLE   constant varchar2(30) := 'services_title';

2. Find the `register` procedure. Your portlet needs to create a path for storing preferences. To do so, it calls `wwpre_api_name.create_path` for creating the preference path. It then calls `wwpre_api_name.create_name` for creating the preference name, taking the portlet path, name, and description as input parameters. Another input parameter is the `p_type_name` that indicates special value types. The `NLSID` type indicates that the value stored is an NLS id. The functions for setting and retrieving this type treat it as a number value. Apart from that, when a preference store value of this type is exported or copied, so are its associated strings. The last input parameter, the language, is obtained from a context API.

       procedure register
       (
           p_portlet_instance in wwpro_api_provider.portlet_instance_record
       )
       is
       begin
           --
           -- Create a path for the portlet instance.  This is used to create
           -- the preferences for the portlet instance in the preference store.
           --
           wwpre_api_name.create_path(
               p_path => PORTLET_PATH || p_portlet_instance.reference_path
               );
           --
           -- Create the names to store the portlet preferences.
           --
           wwpre_api_name.create_name(
               p_path        => PORTLET_PATH
                   || p_portlet_instance.reference_path,
               p_name        => PREFNAME_STRING,
               p_description => 'Single custom row in '
                   || 'Introductory Example portlet.',
               p_type_name   => 'NLSID',
               p_language    => wwctx_api.get_nls_language);
           wwpre_api_name.create_name(
               p_path        => PORTLET_PATH
                   || p_portlet_instance.reference_path,
               p_name        => PREFNAME_TITLE,
               p_description => 'Single custom row in '
                   || 'Introductory Example portlet.',
               p_type_name   => 'NLSID',
               p_language    => wwctx_api.get_nls_language);
       exception
           when others then
               raise;
       end register;

3. The deregister procedure must eliminate the preference store with a call to `wwpre_api_name.delete_name`.

       procedure deregister
       (
           p_portlet_instance in wwpro_api_provider.portlet_instance_record
       )
       is
       begin
            --
            -- Delete the path used by the portlet instance.  This will delete
            -- all the names and all the values associated with the path.
            --
            wwpre_api_name.delete_path(
               p_path => PORTLET_PATH || p_portlet_instance.reference_path
               );
       exception
           when others then
               raise;
       end deregister;

4. The portlet must also get and set the values in the preference store using `wwpre_api_value.set_value` and `wwpre_api_value.get_value`. Find the `get_default_preference` function. Notice how this function loads the system level default values from the preference store. The default preferences are associated with an instance. The language strings are set in the database.

       function get_default_preference
       ...
       begin
           --
           -- Try to find a previously entered portlet instance string preference,
           -- if any.
           -- A portlet instance string preference is stored in the preference
           -- store and has a level of SYSTEM_LEVEL_TYPE.
           --
               p_path       => PORTLET_PATH || p_reference_path,
           l_prefs.string_id := to_char(wwpre_api_value.get_value_as_number(
               p_name       => PREFNAME_STRING,
               p_level_type => wwpre_api_value.SYSTEM_LEVEL_TYPE
               ));
           --
           -- If the value returned above is null it is an indication that there
           -- is no default string yet.  Initialize the string id to 0 to indicate
           -- this and load the default string value.
           --
           if (l_prefs.string_id is null or to_number(l_prefs.string_id) = 0) then
               wwpre_api_value.set_value_as_number(
                   p_path       => PORTLET_PATH || p_reference_path,
                   p_name       => PREFNAME_STRING,
                   p_level_type => wwpre_api_value.SYSTEM_LEVEL_TYPE,
                   p_level_name => null,
                   p_value      => 0
                   );
       ...
       end get_default_preference;

5. Find the `show` procedure. Notice the behavior when the portlet is in Edit Defaults or Edit mode. Note also how `p_action` is populated when the user clicks APPLY, CANCEL, or OK. Once the form is submitted, the `show` procedure of the portlet is called again and, if the `p_action` parameter is not null, then the `save_prefs` procedure is called to save the personalizations and redirect to the relevant page.

       procedure show
       (
           p_portlet_record        wwpro_api_provider.portlet_runtime_record
       )
       is
           l_str varchar2(32000);
           l_pref_record preference_record;
           l_action   varchar2(10);
           l_names    owa.vc_arr;
           l_values   owa.vc_arr;
       begin
       ...
           elsif (p_portlet_record.exec_mode =
                         wwpro_api_provider.MODE_SHOW_EDIT)
               or (p_portlet_record.exec_mode =
                         wwpro_api_provider.MODE_SHOW_EDIT_DEFAULTS)
           then
               wwpro_api_parameters.retrieve(l_names, l_values);
               for i in 1..l_names.count loop
                   if (upper(l_names(i)) = upper('p_string')) then
                       l_pref_record.string := l_values(i);
                   elsif l_names(i) = 'p_title' then
                       l_pref_record.title_string := l_values(i);
                   elsif l_names(i) = 'p_action' then
                       l_action := l_values(i);
                   end if;
               end loop;
               if (l_action in (ACTION_OK,ACTION_APPLY,ACTION_CANCEL)) then
                   if (p_portlet_record.exec_mode =
                                         wwpro_api_provider.MODE_SHOW_EDIT) then
                       save_prefs(p_string => l_pref_record.string,
                                  p_title  => l_pref_record.title_string,
                                  p_action => l_action,
                                  p_level  => wwpre_api_value.USER_LEVEL_TYPE,
                                  p_portlet_record  => p_portlet_record);
                   else
                       save_prefs(p_string => l_pref_record.string,
                                  p_title  => l_pref_record.title_string,
                                  p_action => l_action,
                                  p_level  => wwpre_api_value.SYSTEM_LEVEL_TYPE,
                                  p_portlet_record  => p_portlet_record);
                   end if;
               else
                   show_edit(p_portlet_record  => p_portlet_record);
               end if;
       ...
       end show;

6. The `show_edit` procedure renders the page for Edit or Edit Defaults mode. It renders two text fields that allow the user to change the personalizable values in a form with three buttons (Apply, OK, and Cancel). Note that this function uses the `wwpro_api_adapter.open_form` to create the HTML form with the correct action attribute for the `<FORM>` tag and with the correct hidden fields. It is important to use this procedure to create the `<FORM>` tag if you want to use the portlet with the Federated Portal Adapter from remote OracleAS Portal instances.

       procedure show_edit
       (
           p_portlet_record in wwpro_api_provider.portlet_runtime_record
       )
       is
           l_prefs    preference_record;
           l_text_prompt_string  varchar2(30);
           l_title_prompt_string varchar2(30);
       begin
       ...
           htp.centeropen;
           htp.tableOpen(cattributes => 'BORDER="1" WIDTH=90%');
           htp.tableRowOpen;
           htp.p('<TD>');
           --
           --  This procedure call creates the <FORM> tags with a set of
           --  standard parameters.  Using this procedure makes the
           --  personalization page work through the pl/sql http adapter.
           --
           wwpro_api_adapter.open_form(p_formattr => 'NAME="services"',
                                       p_prr      => p_portlet_record);
           htp.p('</TD>');
           htp.tableRowClose;
           htp.tableClose;
           htp.centerclose;
           htp.formclose;
       end show_edit;

7. Review the following procedures and functions, which are related to the preference storage implementation in this example:

   - `get_user_preference` retrieves the user personalized string and title for the portlet.

   - `save_prefs` is invoked to save the preferences to the preference store when the user clicks OK or Apply after making personalization changes.

   - `entered_text_is_valid` checks to see if the text entered in the personalizable text fields is valid.

8. Optionally, if you want to see this portlet on a page and it is not already in the Portlet Repository, refer to the instructions in Section 8.3.2, "Implementing the Provider Package" for information on how to add it.

9. Once your portlet appears in the repository, you can add it to a page to test it. To add your portlet to a page, follow the instructions in Oracle Application Server Portal User's Guide.

### 8.4.2 Implementing a Session Store

The services example, located in `..\pdkplsql\pdk\plsql\svcex` in PDK-PL/SQL (`pdkplsql.zip`), illustrates how you can implement a session store. The objective is to achieve the following functionality:

- When a user invokes this portlet, it displays text that reads: "This portlet has rendered `x` times in this session." `x` is the number of times the portlet has been rendered.

- Every time the user invokes the portlet, the counter increases by 1.

- Clicking Details in the portlet enables the user to reset the counter via Clear. After clearing the counter, the counter starts again from zero.

#### 8.4.2.1 Creating and Accessing a Session Store

You can browse through this example as follows to see how to create the session store, store values in it, and retrieve values from it:

1. Open the `services_portlet.pkb` file in an editor.

   The domain and subdomain definitions for your session object are provided with aliases in the constants part of your portlet definition.

       DOMAIN           constant varchar2(30) := 'provider';
       SUBDOMAIN        constant varchar2(32) := 'services';
       PORTLET_PATH     constant varchar2(256):= 'oracle.portal.pdk.servicesportlet';
       PREFNAME_STRING  constant varchar2(30) := 'services_string';
       PREFNAME_TITLE   constant varchar2(30) := 'services_title';

2. Find the `clear_count` procedure. `clear_count` is called from the `show` procedure when the user clicks Clear to reset the counter. `clear_count` calls `wwsto_api_session.load_session` to load the session object. Then, it calls `wwsto_api_session.set_attribute` to set the counter to zero. Lastly, it saves the session object by calling `save_session`.

       procedure clear_count
       (
           p_action          in varchar2,
           p_back_url        in varchar2,
           p_reference_path  in varchar2
       )
       is
           ex_counter integer;
           session_parms &&1..wwsto_api_session;
       begin
           --
           -- Clear the display counter.
           --
           if (p_action = ACTION_CLEAR) then
               --
               -- Load the session object that contains the display counter
               --
               session_parms :=
                   &&1..wwsto_api_session.load_session (DOMAIN,SUBDOMAIN);
               ex_counter :=
                   session_parms.get_attribute_as_number(
                       'ex_counter' || p_reference_path);
               --
               -- Reset the display counter.
               --
               ex_counter := 0;
               session_parms.set_attribute(
               'ex_counter' || p_reference_path, ex_counter);
               --
               -- Save the changes to the database immediately to avoid any
               -- data consistency problems with the data stored in the
               -- session object.
               --
               session_parms.save_session;
           end if;
           owa_util.redirect_url(curl=>p_back_url);
       end clear_count;

3. Find the `show_contents` procedure. `show_contents` is called from the `show` procedure to retrieve the counter, increment it by one, and save the value in the session store. Notice how it retrieves the session object to display the number of times the user has rendered the portlet. It also retrieves the counter value with `get_attribute_as_number` and increments the counter for every invocation of this procedure.

       procedure show_contents
       (
           p_portlet_record  wwpro_api_provider.portlet_runtime_record
       )
       is
           l_prefs preference_record;
           session_parms &&1..wwsto_api_session;
           ex_counter integer;
           l_portlet wwpro_api_provider.portlet_record;
           l_str varchar2(32000);
       begin
           --
           -- In this mode a session counter is used to indicate
           -- the number of invocations of this portlet during the
           -- current session.  The counter is stored in the session
           -- store.
           --
           session_parms :=
               &&1..wwsto_api_session.load_session(DOMAIN,SUBDOMAIN);
           ex_counter    :=
               session_parms.get_attribute_as_number(
                   'ex_counter' || p_portlet_record.reference_path);
           if (ex_counter is null) then -- first invocation
               session_parms.set_attribute(
                   'ex_counter' || p_portlet_record.reference_path,1);
               ex_counter := session_parms.get_attribute_as_number(
                   'ex_counter' || p_portlet_record.reference_path);
           else -- on every invocation increase by 1
               ex_counter := ex_counter + 1;
               session_parms.set_attribute(
                   'ex_counter'
                   || p_portlet_record.reference_path, ex_counter);
           end if;    session_parms.save_session;
       ...
       end show_contents;

4. Optionally, if you want to see this portlet on a page and it is not already in the Portlet Repository, refer to the instructions in Section 8.3.2, "Implementing the Provider Package" for information on how to add it.

5. Once your portlet appears in the repository, you can add it to a page to test it. To add your portlet to a page, follow the instructions in Oracle Application Server Portal User's Guide.

## 8.5 Using Parameters

The functionality of portlets can be extended with the help of parameters. The business logic implemented by portlets may produce different HTML output depending on the parameters passed to the page. By using portlet parameters, you can navigate within the portlet in Shared Screen mode without changing the current page. Portlets can also communicate with each other through parameters.

Portlet parameters are structured as name-value pairs. These pairs map directly to the URL parameter passing format by using the `GET` submission method or can use the HTTP message body by using the `POST` submission method. Portlets can also expose their parameters to OracleAS Portal. When added to a page, these portlets can accept values in the form of page parameters created by the page designer.

Note: Portlet parameter names should not start with an underscore (_) because those parameters are reserved for internal use by OracleAS Portal and are not passed to the portlet.

Portlets do not have direct access to the URL, the HTTP message body, or the page parameters. To retrieve the parameter values, portlets must call the OracleAS Portal PL/SQL parameter APIs provided in the `wwpro_api_parameters` package.

OracleAS Portal offers the following types of parameters:

Caution: You cannot mix the usage of public and private parameters in a portlet. To enable public parameters for your portlet, you must take steps that preclude the usage of private parameters and vice versa.

- Private portlet parameters enable the implementation of internal navigation in your portlet.

- Public portlet parameters let you pass control over the data flow of your portlet to the page designer. The page designer can map the public portlet parameters to their page parameters, provide default values, and allow users to personalize those values.

- Page parameters are defined in a simple user interface by page designers. These page parameters can be mapped to public portlet parameters in order for the page designer to pass parameter values from the page to the portlets on it.

For more information about parameters, refer to Section 2.12, "Public Portlet Parameters Support" and Section 2.13, "Private Portlet Parameter Support".

### 8.5.1 Passing Private Parameters

You can use either `GET` or `POST` HTML submission methods when passing private portlet parameters. The `GET` method uses the URL to pass the parameters, whereas the `POST` method places the parameters in an HTTP message body. For both methods, you must specify the portlet instance on the portal page, how the parameter is called, and the value of the parameter.

There are two types of private portlet parameters:

- Qualified parameters ensure that a private portlet parameter is not read by any other portlet on the page. The reference path, which is assigned when the portlet is added to a page, is the unique prefix of the parameter. For example, `http://page_url?277_MAP_368673.region=Europe`. The qualified parameter's reference path is `277_MAP_368673`, the name is `region`, and the value is `Europe`. For private parameters, we strongly recommend that you always use qualified parameters.

- Unqualified parameters have no information about the portlet instance and can be read by any portlet on the page. For example,`http://page_url?region=Europe`. The unqualified parameter's name is `region` and its value is `Europe`. For private parameters, we strongly recommend that you avoid unqualified parameters.

### 8.5.2 Passing Page Parameters and Mapping Public Portlet Parameters

Public portlet parameters enhance the flexibility of your portlets by enabling page designers to reuse your portlets on multiple pages. As a result, page designers do not have to ask you to make changes to the portlet code when adding the portlet to different pages. By using public portlet parameters, any portlet on a page can easily receive its value from the mapped page parameter, regardless of the portlet parameter name.

For example, suppose you have a page parameter named `dept_id`. Three portlets need this value, but one portlet calls it `dept`, another calls it `deptno`, and still another `department_id`. Mapping the page parameter enables all three portlets to receive the value from the `dept_id` parameter and place it in the appropriate portlet parameter. Furthermore, the page designer may set a default value (for example, department 20) that can be personalized by users (for example, department 30) and applied to all three portlets.

The general model for passing public and page parameters is as follows:

1. Enable public parameters in the portlet record by setting `pass_all_url_params` to `false`. This ensures that the portlet is only passed parameters intended for that portlet.

2. Declare the public parameters in the provider's `describe_portlet_parameters` function. For each of the portlets that belong to the provider, this procedure should return a list of the parameters that the portlet accepts in the form of a PL/SQL table of records of the type:

       type portlet_parameter_table is table ofportlet_parameter_record index by binary_integer;

3. Provide descriptive information for the parameters in the portlet's `describe_parameters` function. For example:

       function describe_parameters
          (p_provider_id in integer, p_language in varchar2)
       return wwpro_api_provider.portlet_parameter_table
          is
       l_params wwpro_api_provider.portlet_parameter_table;
          begin
            l_params(1).name := 'dept_id';
            l_params(1).datatype := wwpro_api_provider.STRING_TYPE;
            l_params(1).description := 'Defines a department ID';
            l_params(1).display_name := 'Department ID';
            return l_params;
       end describe_parameters;

4. Assign values to the public parameters. Public parameters typically get their values through page parameters. Page parameters are usually assigned default values by the page designer and the user can then personalize the value at runtime. Alternatively, page parameter values can be assigned in the calling URL. For more information about how page designers can use page parameters, refer to the Oracle Application Server Portal User's Guide.

### 8.5.3 Retrieving Parameter Values

Regardless of whether you are using private or public parameters, you use the same APIs to retrieve their values. Portlets obtain their parameters by calling the PL/SQL parameter APIs in the `wwpro_api_parameters` package:

- `wwpro_api_parameters.get_value` returns the parameter value that is specified by a given parameter name. Parameter names are not case sensitive, whereas parameter values are case sensitive. For example:

      l_region := wwpro_api_parameters.get_value
         (p_name => 'region',
          p_reference_path => p_portlet_record.reference_path);

- `wwpro_api_parameters.get_values` returns an array of parameter values. This function returns all the values that are associated with a single parameter name or an empty list if no matches are found. Some business logic may require multiple selections, when multiple values are passed to the portlet by using the same parameter name. Portlets can take one or more values of the same parameter. For example:

      l_region_values owa.vc_arr;
      ...
      l_region_values := wwpro_api_parameters.get_values
         (p_name = 'region',
          p_reference_path => p_portlet_record.reference_path);

- `wwpro_api_parameters.get_names` returns the names of the parameters that are passed on to a specified portlet that is identified by the reference path. The returned list is a PL/SQL table of the `owa.vc_ar` type that is defined as follows:

      type vc_arr is table of varchar2(32000) index by binary_integer;

  Note: Portlet parameter names should not start with an underscore (_) because those parameters are reserved for internal use by OracleAS Portal and are not passed to the portlet.

  For example:

      l_names owa.vc_arr;
      ...
      l_names := wwpro_api_parameters.get_names
         (p_reference_path => p_portlet_record.reference_path);

- `wwpro_api_parameters.retrieve` returns the names and values of all of the portlet's parameters. For example:

      procedure show_portlet
         ( p_portlet_record in out
               wwpro_api_provider.portlet_runtime_record )
      is
         l_names owa.vc_arr;
         l_values owa.vc_arr;
      ...
      begin
      ...
         wwpro_api_parameters.retrieve (l_names, l_values);
         for i in 1..l_names.count loop
           htp.p('Parameter Name: '||l_names(i));
           htp.p('Parameter Value: '||l_values(i));
           htp.br;
         end loop;
      ...
      end show_portlet;

## 8.6 Accessing Context Information

Whenever a user accesses a page in OracleAS Portal, a public session is established. When the user logs in to OracleAS Portal, the public session becomes an authenticated session. This session contains several pieces of context information about the user, such as user name, current session ID, IP address, and language preference. It also includes supporting information such as the OracleAS Portal schema currently in use.

Session context services return information about a user's session and are available through the `wwctx_api` package.

### 8.6.1 Using Context Information

The general model for working with the session context is as follows:

1. Identify the piece of information you require for your functionality.

2. Use the appropriate method from `wwctx_api` to get and optionally set this value.

Table 8-5 lists the function calls used to obtain the various pieces of session information.

Note: For more information on the context APIs, see the PL/SQL API Reference. The API Reference can be found on Portal Center (`http://portalcenter.oracle.com`) or, if you downloaded PDK-PL/SQL (`pdkplsql.zip`), in `..\pdkplsql\pdk\plsql\doc`.

Table 8-5 Context Information Function Calls

Session Information | Function Call
--- | ---
Current user | `wwctx_api.get_user`
Login status of user | `wwctx_api.is_logged_on`
Login time | `wwctx_api.get_login_time`
Language | `wwctx_api.get_nls_language`
Current session id | `wwctx_api.get_sessionid`
IP address of user client | `wwctx_api.get_ip_address`
User schema | `wwctx_api.get_db_user`
OracleAS Portal schema | `wwctx_api.get_product_schema`
OracleAS Portal version | `wwctx_api.get_product_version`

### 8.6.2 Using wwctx_api to Obtain Context Information

The services example, located in `..\pdkplsql\pdk\plsql\svcex` in PDK-PL/SQL (`pdkplsql.zip`), illustrates how you can obtain session information using the `wwwctx_api` package. You can browse through this example as follows to see how the function calls are implemented in a portlet:

1. Open the `services_portlet.pkb` file in an editor.

2. Find the `get_portlet_info` function.

3. Notice the usage of `wwctx_api.get_user` to derive the user information and set that value in the portlet information record:

       ...
           l_portlet.timeout              := null;
           l_portlet.timeout_msg          := null;
           l_portlet.created_on           := to_date('10/19/2000', 'MM/DD/YYYY');
           l_portlet.created_by           := wwctx_api.get_user;
           l_portlet.last_updated_on      := to_date('10/19/2000', 'MM/DD/YYYY');
           l_portlet.last_updated_by      := wwctx_api.get_user;
           l_portlet.has_show_edit_defaults := true;
           l_portlet.has_show_preview     := true;
           l_portlet.preference_store_path := PORTLET_PATH;
       ...

4. `wwctx_api.get_user` is used similarly in various places throughout `services_portlet.pkb`. Search the code for other occurrences of `wwctx_api.get_user`.

5. Another example of getting context information occurs in the `is_runnable` function:

       function is_runnable
       (
            p_provider_id in integer
           ,p_reference_path in varchar2
       )
       return boolean
       is
       begin
           --
           -- Portlet security check.  It allows the portlet to be visible
           -- if the user is logged on, i.e. the current session is not a
           -- public session.
           --
           return wwctx_api.is_logged_on;
       end is_runnable;

6. In the `register` procedure, `wwctx_api.get_nls_language` is used to get the language:

           --
           -- Create the names to store the portlet preferences.
           --
           wwpre_api_name.create_name(
               p_path        => PORTLET_PATH
                   || p_portlet_instance.reference_path,
               p_name        => PREFNAME_STRING,
               p_description => 'Single custom row in '
                   || 'Introductory Example portlet.',
               p_type_name   => 'NLSID',
               p_language    => wwctx_api.get_nls_language);
           wwpre_api_name.create_name(
               p_path        => PORTLET_PATH
                   || p_portlet_instance.reference_path,
               p_name        => PREFNAME_TITLE,
               p_description => 'Single custom row in '
                   || 'Introductory Example portlet.',
               p_type_name   => 'NLSID',
               p_language    => wwctx_api.get_nls_language);

7. Close `services_portlet.pkb`. You can implement session context similarly but based upon your own functional requirements.

8. Optionally, if you want to see this portlet on a page and it is not already in the Portlet Repository, refer to the instructions in Section 8.3.2, "Implementing the Provider Package" for information on how to add it.

9. Once your portlet appears in the repository, you can add it to a page to test it. To add your portlet to a page, follow the instructions in Oracle Application Server Portal User's Guide.

## 8.7 Implementing Portlet Security

Portlet security refers to the techniques and methods used by portlets to control their access by end users. The portlets leave authentication to OracleAS Portal and trust that the portal will return them to the correct, validated user upon request.

OracleAS Portal strictly controls access to information and applications by assigning specific privileges to users and groups. Portal security services allow you to specify access control programmatically and check for the appropriate privileges at runtime. Security mechanisms used by portlets ensure that only authorized users gain access to these portlets. These security services are available through the `wwsec_api` package.

Portlet security is invoked when a portlet is displayed on a portal page and when a portlet is returned in a portlet list by the `get_portlet_list` function for database providers. Security services in the Portal framework have the following key features:

- Portlet Display: Before a portlet is displayed on a page, the provider checks for the portlet's access privileges. The provider needs to define the `is_portlet_runnable` function which calls the portlet's `is_runnable` function to check access privileges.

- User Group: You can find which default group a user belongs to by using the `wwsec_api.get_defaultgroup` function.

- Check Privileges: You can find whether a user or group has the required privileges to personalize a portlet by using the `wwsec_api.has_privilege` function.

- Highest Privilege: You can find the highest available privilege of a user across all groups by using the `wwsec_api.get_privilege_level` function.

- Accessible Objects: You can find all the objects to which a user has access, given a privilege level, by using the `wwsec_api.accessible_objects` function. You can find other similar associated functions in the API documentation. The API Reference can be found on Portal Center (`http://portalcenter.oracle.com`) or, if you downloaded PDK-PL/SQL (`pdkplsql.zip`), in `..\pdkplsql\pdk\plsql\doc`.

### 8.7.1 Using Security

To implement PL/SQL portlet security, the portal requires the function `is_portlet_runnable` be implemented by database providers. The actual implementation of this function is up to the application; that is, the security scheme that determines whether the current user has enough privileges to access the portlet is defined by the individual portlet implementation. The portal also requires the function `get_portlet_list` for database providers to return the set of portlets that are accessible by the current user.

#### 8.7.1.1 Guidelines for Using the Security APIs

The portlet security mechanism may use the context and security subsystem APIs and infrastructure. The context APIs can be used to retrieve information about the current user. The security subsystem can be used to check the privileges of the current user.

Note: For more information on the context and security subsystem APIs, see the PL/SQL API Reference. The API Reference can be found on Portal Center (`http://portalcenter.oracle.com`) or, if you downloaded PDK-PL/SQL (`pdkplsql.zip`), in `..\pdkplsql\pdk\plsql\doc`.

While using these APIs, keep in mind the following:

- Only authorized users should be able to see your portlet in the Add Portlet dialog. This objective can be accomplished by implementing the `is_portlet_runnable` function in the provider. You can also allow public access to your portlet.

- If a portlet does not want to render itself to a user, it should return no HTML or return an exception that the page engine will ignore. It should not return an error message. Doing so adds unnecessarily to the error stack, which has its limits. Refer to Section 8.9, "Implementing Error Handling" for more information.

- Portlet security allows the portlet to perform a runtime security check to ensure that the current user has the necessary authorization to access the portlet.

- When a portlet is rendered in Show mode, it may call the `is_runnable` method for database providers to determine whether the portlet should be displayed for the currently logged on user. The portal does not make the call to this function directly. It is not a requirement, however, for the portlet to make this call. The portlet should make this call in its Show mode only if it implements portlet security.

- The result of the call to `is_runnable` determines whether the portlet is actually displayed. If the result is `true`, the portlet displays; otherwise it does not display. The portlet is rendered in Show mode when it is displayed in a portal page.

- When a portlet is returned in a portlet list by a call to the provider function `get_portlet_list`, the value of the `p_security_level` parameter determines the purpose of the function call. When the call is made from the Portlet Repository refresh operation in order to retrieve the master list of portlets that the provider implements, the parameter `p_security_level` has a value of `false`. This setting indicates to the provider that no portlet security check should be made and a master list of all the portlets that the provider implements must be returned. The master list of portlets returned in this case is used to populate the Portlet Repository for that provider.

- If the value of `p_security_level` is `true`, then it is up to the provider implementation to decide whether portlet security should be performed. If portlet security is implemented, the provider may return a different list of portlets depending on the current user.

- When the Portlet Repository is displayed, OracleAS Portal calls the `is_portlet_runnable` function for database providers for each of the portlets that exist in the Portlet Repository. This step is done to display only the portlets that the currently logged on user is authorized to see. One example where the Portlet Repository is displayed is in the Add Portlets dialog.

### 8.7.2 Coding Security

The services example, located in `..\pdkplsql\pdk\plsql\svcex` in PDK-PL/SQL (`pdkplsql.zip`), illustrates how you can implement security. You can browse through this example as follows to see how the security functions are implemented in a portlet:

1. Open the `services_provider.pkb` file in an editor.

2. Find the `is_portlet_runnable` function. This function calls the security implementation through the portlet's `is_runnable` function to check portlet access privileges.

       function is_portlet_runnable
       (
           p_portlet_instance in wwpro_api_provider.portlet_instance_record
       )
       return boolean
       is
       begin
           if (p_portlet_instance.portlet_id = SERVICES_PORTLET_ID) then
               return services_portlet.is_runnable(
                    p_provider_id     =>  p_portlet_instance.provider_id
                   ,p_reference_path  =>  p_portlet_instance.reference_path
                   );
           else
               raise wwpro_api_provider.PORTLET_NOT_FOUND_EXCEPTION;
           end if;
       end is_portlet_runnable;

3. Find the `get_portlet_list` procedure. `get_portlet_list` allows the portlet to be included in the list of portlets implemented by this provider. `get_portlet_list` first checks the security flag (`p_security_level`) to find out whether security is enabled. If the flag is set to true, `get_portlet_list` uses `is_runnable` to check whether the portlet is accessible. The value of the `p_security_level` parameter indicates whether to perform security checks before returning a portlet in the list. When a portlet repository refresh operation retrieves the master list of portlets implemented by the provider, `p_security_level` has a value of `false`. A value of `false` means the provider does not need to perform a security check and that a master list of all of the portlets implemented by the provider must be returned. The master list of portlets returned is used to populate the portlet repository for that provider. If the value of `p_security_level` is `true`, then the provider implementation decides whether to perform portlet security checks. If portlet security is implemented, the provider may return a different list of portlets depending on the currently logged on user.

       function get_portlet_list
       ...
           if (p_security_level = false) then
               l_cnt := l_cnt + 1;
               l_portlet_list(l_cnt) := get_portlet(
                    p_provider_id  => p_provider_id
                   ,p_portlet_id   => SERVICES_PORTLET_ID
                   ,p_language     => p_language
                   );
           else
               if (services_portlet.is_runnable(
                    p_provider_id     =>  p_provider_id
                   ,p_reference_path  =>  null)
               ) then
                   l_cnt := l_cnt + 1;
                   l_portlet_list(l_cnt) := get_portlet(
                        p_provider_id  => p_provider_id
                       ,p_portlet_id   => SERVICES_PORTLET_ID
                       ,p_language     => p_language
                       );        end if;
       ...
       end get_portlet_list;

4. Open the `services_portlet.pkb` file in an editor.

5. Find the `show` procedure. Before displaying a portlet, the `show` procedure runs a security check to determine whether the current user is allowed to see the portlet.

       procedure show
       ...
           -- Perform a security check
           if (not is_runnable(
                p_provider_id     =>  p_portlet_record.provider_id
               ,p_reference_path  =>  p_portlet_record.reference_path)
           ) then
               wwerr_api_error.add(
                         DOMAIN, SUBDOMAIN,
                         'securityerr', 'services_portlet.show');
               raise wwpro_api_provider.PORTLET_SECURITY_EXCEPTION;    end if;
       ...
       end show;

6. Find the `is_runnable` function. `is_runnable` is the place where you implement your security checks. In this example, the security check is quite simple. If the user is logged on (that is, not in a public session), then the function returns `true` and the portlet is displayed to the user. For your own purposes, you could, of course, code much more complex security checks in the `is_runnable` function.

       function is_runnable
       (
            p_provider_id in integer
           ,p_reference_path in varchar2
       )
       return boolean
       is
       begin
           --
           -- Portlet security check.  It allows the portlet to be visible
           -- if the user is logged on, i.e. the current session is not a
           -- public session.
           --
           return wwctx_api.is_logged_on;
       end is_runnable;

7. Optionally, if you want to see this portlet on a page and it is not already in the Portlet Repository, refer to the instructions in Section 8.3.2, "Implementing the Provider Package" for information on how to add it.

8. Once your portlet appears in the repository, you can add it to a page to test it. To add your portlet to a page, follow the instructions in Oracle Application Server Portal User's Guide.

## 8.8 Improving Portlet Performance with Caching

OracleAS Portal provides for the caching of PL/SQL portlets. This functionality permits PL/SQL portlets to cache their Web content on the middle tier. Subsequent requests for the content may be retrieved from the cache, with or without validation from the database, decreasing the database workload.

OracleAS Portal provides three types of caching for your PL/SQL portlets:

- Validation-based caching compares a key value to check whether the contents of the cache are still valid. If the key value does not change, it uses the cached content. Otherwise, it makes a round trip to the portal node to fetch the portlet content.

- Expiry-based caching uses a given expiration period for the contents of the cache when rendering the portlet. This form of caching is useful for content that changes infrequently or at very regular intervals (for example, every day at the close of business).

- Invalidation-based caching is the most complex form of caching but also the most flexible. The objects in OracleAS Web Cache are considered valid as long as they are not invalidated explicitly. You can also combine invalidation-based caching with either expiry-based or validation-based caching.

Because OracleAS Portal supports user personalization of pages and portlets, the view of a page can vary from one user to another. OracleAS Portal's caching is designed to allow content to vary on a per-user basis, even if the URL is the same across all users. Therefore, portal objects can be cached at either the user level or the system level:

- User-level caching is for a specific user. The cache entries are unique for that user and cannot be accessed by other users.

- System-level caching is for all users. One cache entry is used for all users. Examples of content that might be suitable for system-level caching are page banners and news portlets.

When a database provider issues a request for a portlet, the request is sent to the portlets's `show` procedure. This procedure accepts the `portlet_runtime_record` as a parameter. This record structure contains fields that can be examined and set by the portlet to enable caching. The caching control fields of this record are:

- `caching_key`: This value is communicated in the `ETAG` header for this request and returned back to the portlet provider in subsequent requests. Setting this field enables validation-based caching.

- `caching_period`: This field enables expiry-based caching. The value is the number of minutes the content should be held in the cache. This mode overrides validation-based caching. If a value is set for this field, then the `caching_key` field is ignored.

- `caching_level`: This field defines whether the content is meant for general use or for a specific user. The valid values are `SYSTEM` and `USER`.

### 8.8.1 Using Caching

The general model for working with portlet caching varies according to the type of caching you choose. To a great extent, the type of caching you choose depends on the portlet content. If the portlet content changes at fairly regular intervals (for example, at the close of business every day), then it probably makes sense to use expiry-based caching. If the portlet content changes at irregular intervals, then validation- or invalidation-based caching is probably best.

#### 8.8.1.1 Validation-Based Caching

If you choose validation-based caching, the general model is as follows:

1. Set the `caching_key` field of the `portlet_runtime_record` parameter. Add a check to compare the value of the current key with the value of the `caching_key` field of the `portlet_runtime_record` parameter. Note that the first time the `show` procedure is called, the key is null and its value must be set.

2. Determine whether you want to use system or user level caching. Set the `caching_level` field of the `portlet_runtime_record` parameter accordingly.

#### 8.8.1.2 Expiry-Based Caching

If you choose expiry-based caching, the general model is as follows:

1. Set the `caching_period` field of the `portlet_runtime_record` parameter to the desired interval for the cache (in minutes).

2. Determine whether you want to use system or user level caching. Set the `caching_level` field of the `portlet_runtime_record` parameter accordingly.

#### 8.8.1.3 Invalidation-Based Caching

If you choose invalidation-based caching, the general model is as follows:

1. Indicate to OracleAS Portal that it must generate specific headers for OracleAS Web Cache by calling `wwpro_api_provider.USE_INVALIDATION`.

2. Determine whether you want to use system or user level caching. Set the `caching_level` field of the `portlet_runtime_record` parameter accordingly.

3. Optionally, set up validation- or expiry-based caching as well.

4. Add invalidation logic to your portlet where needed (for example, when the portlet is personalized) and make appropriate calls to `wwpro_api_invalidation`.

### 8.8.2 Configuring and Monitoring the Cache

The Oracle Application Server Portal Configuration Guide describes how to configure caching as well as how to monitor and tune performance.

### 8.8.3 Implementing Validation-Based Caching

The caching example, located in `..\pdkplsql\pdk\plsql\cache` in PDK-PL/SQL (`pdkplsql.zip`), illustrates how you can implement validation and expiry-based caching. You can browse through this example as follows to see how the validation-based functions are implemented in a portlet:

1. Open the `validcache_portlet.pkb` file in an editor.

2. At the very top of the file, notice the aliases for the caching level constants.

       CREATE OR REPLACE
       package body VALIDCACHE_PORTLET
       is
           -- Caching Constants
           CACHE_LEVEL_SYSTEM constant varchar2(10) := 'SYSTEM';
           CACHE_LEVEL_USER   constant varchar2(10) := 'USER';

3. Find the `show` procedure. Notice first that the `p_portlet_record` is an `in` and `out` parameter for this procedure.

       procedure show
       (
           p_portlet_record   in out     wwpro_api_provider.portlet_runtime_record
       )

4. In the procedure's security check, the caching fields of `p_portlet_record` are set to null if the security check fails.

       begin
           if (not is_runnable(
                p_provider_id      => p_portlet_record.provider_id
               ,p_reference_path   => p_portlet_record.reference_path)
           ) then
               -- Set it to null so that cache does not get used even if exists
               p_portlet_record.caching_level := null;
               p_portlet_record.caching_key := null;
               raise wwpro_api_provider.PORTLET_SECURITY_EXCEPTION;    end if;

5. After that, the procedure calls the `get_cache_key` function to get the cache key's value and assign it to a temporary value:

           --
           -- CACHE IS VALID?
           --
           l_cache_key := get_cache_key();

6. Find the `get_cache_key` function, which is referenced from the `show` procedure. This function generates a key for the portlet. You can implement your own logic here based upon your portlet's requirements.

       function get_cache_key
       return varchar2
       is
           l_date date;
       begin
           select sysdate into l_date from  dual;
           return trim(substr(to_char(l_date, 'YYYY:MM:DD:HH:MI:SS'),1,18));
       exception
           when others then
               null;
       end get_cache_key;

7. Now return to the `show` procedure. Notice how the code checks your `portlet_runtime_record` parameter for the current values of the `caching_key` and the `caching_level`. This same piece of code can compare your `caching_key` values.

           if p_portlet_record.caching_level = CACHE_LEVEL_SYSTEM then
               if l_cache_key is not null then
                   -- Cache exists for the user, overwrite it
                   p_portlet_record.caching_level := CACHE_LEVEL_USER;
                   p_portlet_record.caching_key := l_cache_key;
               else
                   return; -- System cache is still valid.
               end if;
           elsif p_portlet_record.caching_level = CACHE_LEVEL_USER then
               if p_portlet_record.caching_key != l_cache_key then
                   -- cache has expired. reset it
                   p_portlet_record.caching_key := l_cache_key;
               else
                   return; -- User cache is good as gold
               end if;
           elsif p_portlet_record.caching_level is null then
               if p_portlet_record.caching_key is not null then
                   -- Cache does not exists for the user, create it
                   p_portlet_record.caching_level := CACHE_LEVEL_USER;
                   p_portlet_record.caching_key := l_cache_key;
               else
                   -- Define a sytem cache. This can happen only once!
                   -- the first time the portlet is rendered.
                   p_portlet_record.caching_level := CACHE_LEVEL_SYSTEM;
                   p_portlet_record.caching_key := 'MY_INITIAL_CACHE_KEY';
               end if;
           else
               p_portlet_record.caching_level := CACHE_LEVEL_SYSTEM;
               p_portlet_record.caching_key := 'MY_INITIAL_CACHE_KEY';
           end if;

8. Optionally, if you want to see this portlet on a page and it is not already in the Portlet Repository, refer to the instructions in Section 8.3.2, "Implementing the Provider Package" for information on how to add it.

9. Once your portlet appears in the repository, you can add it to a page to test it. To add your portlet to a page, follow the instructions in Oracle Application Server Portal User's Guide.

### 8.8.4 Implementing Expiry-Based Caching

The caching example, located in `..\pdkplsql\pdk\plsql\cache` in PDK-PL/SQL (`pdkplsql.zip`), illustrates how you can implement expiry-based caching. You can browse through this example as follows to see how the expiry-based functions are implemented in a portlet:

1. Open the `expirycache_portlet.pkb` file in an editor.

2. At the very top of the file, notice the aliases for the caching level constants.

       CREATE OR REPLACE
       package body VALIDCACHE_PORTLET
       is
           -- Caching Constants
           CACHE_LEVEL_SYSTEM constant varchar2(10) := 'SYSTEM';
           CACHE_LEVEL_USER   constant varchar2(10) := 'USER';

3. Find the `show` procedure. Notice first that the `p_portlet_record` is an `in` and `out` parameter for this procedure.

       procedure show
       (
           p_portlet_record   in out     wwpro_api_provider.portlet_runtime_record
       )

4. In the procedure's security check, the caching fields of `p_portlet_record` are set to null if the security check fails.

       begin
           if (not is_runnable(
                p_provider_id      => p_portlet_record.provider_id
               ,p_reference_path   => p_portlet_record.reference_path)
           ) then
               -- Set it to null so that cache does not get used even if exists
               p_portlet_record.caching_level := null;
               p_portlet_record.caching_key := null;
               raise wwpro_api_provider.PORTLET_SECURITY_EXCEPTION;    end if;

5. After that, the procedure sets the value of the caching period in minutes in a temporary variable. The `get_cache_key` function to get the cache key's value and assign it to a temporary value:

          -- Set the Caching Period to one minute
          l_cache_period := 1;

6. Next, notice how the code checks `your portlet_runtime_record` parameter for the current values of the `caching_period` and sets the `caching_period` accordingly. This same piece of code can compare your `caching_period` values.

          if p_portlet_record.caching_level = CACHE_LEVEL_SYSTEM then
                   -- Cache does not exists for the user, create it
                   p_portlet_record.caching_level := CACHE_LEVEL_USER;
                   p_portlet_record.caching_period := l_cache_period;
          elsif p_portlet_record.caching_level = CACHE_LEVEL_USER then
                   -- Cache exists for the user, overwrite it
                   p_portlet_record.caching_period := l_cache_period;
          elsif p_portlet_record.caching_level is null then
               if p_portlet_record.caching_period  is not null then
                   -- Cache does not exists for the user, create it
                   p_portlet_record.caching_level := CACHE_LEVEL_USER;
                   p_portlet_record.caching_period := l_cache_period;
               else
                   -- Define a sytem cache. This can happen only once!
                   p_portlet_record.caching_level := CACHE_LEVEL_SYSTEM;
                   p_portlet_record.caching_period := l_cache_period;
               end if;
          else -- p_portlet_record.caching_level value is messed up!
               p_portlet_record.caching_level := CACHE_LEVEL_SYSTEM;
               p_portlet_record.caching_period := l_cache_period;   end if;

7. Optionally, if you want to see this portlet on a page and it is not already in the Portlet Repository, refer to the instructions in Section 8.3.2, "Implementing the Provider Package" for information on how to add it.

8. Once your portlet appears in the repository, you can add it to a page to test it. To add your portlet to a page, follow the instructions in Oracle Application Server Portal User's Guide.

### 8.8.5 Implementing Invalidation-Based Caching

Suppose you have a portlet that displays a map of the world, `map_portlet.pkb` and `map_portlet.pks`. You would go about adding invalidation-based functions to it as follows:

1. In the `show` procedure, you need to add a call to `wwpro_api_provider.use_invalidation`. This call indicates to OracleAS Portal that the portlet content should be cached by OracleAS Web Cache. Note that we have also specified that the content be cached at the user level and that expiry-based caching be used as well (that is, an expiration interval of one minute has been set).

       procedure show
       ...
           if (p_portlet_record.exec_mode = wwpro_api_provider.MODE_SHOW) then
               p_portlet_record.caching_invalidation :=
                 wwpro_api_provider.use_invalidation;
               p_portlet_record.caching_level := 'USER';
               p_portlet_record.caching_period := 1;
       ...

2. Create a procedure in your `map_portlet.pkb` file that invalidates the cache. For example:

       procedure map_invalidation
       (
       p_provider_id in number,
       p_portlet_id in number,
       p_instance_id in varchar2,
       p_page_url in varchar2
       )
       is
       begin
        wwpro_api_invalidation.invalidate_by_instance
         (p_provider_id => p_provider_id,
          p_portlet_id =>  p_portlet_id,
          p_instance_id => p_instance_id,
          p_user => wwctx_api.get_user);
        owa_util.redirect_url(p_page_url);
       end map_invalidation;

3. In the `show` procedure, add a link for refreshing the portlet before the code that draws the map. For example:

       /* Draw the Refresh Me link */
       htp.anchor(
         curl => wwctx_api.get_user||
           '.map_invalidation?p_provider_id='||p_portlet_record.provider_id||
           '&p_portlet_id='||p_portlet_record.portlet_id||
           '&p_instance_id='||p_portlet_record.reference_path||
           '&p_page_url='||utl_url.escape(
                           url => p_portlet_record.page_url,
                           escape_reserved_chars => TRUE),
         ctext => wwui_api_portlet.portlet_text(
           p_string =>'Refresh Me',
           p_level => 1)
       );

4. Optionally, if you want to see this portlet on a page and it is not already in the Portlet Repository, refer to the instructions in Section 8.3.2, "Implementing the Provider Package" for information on how to add it.

5. Once your portlet appears in the repository, you can add it to a page to test it. To add your portlet to a page, follow the instructions in Oracle Application Server Portal User's Guide.

## 8.9 Implementing Error Handling

OracleAS Portal provides the capability for you to trap erroneous input and return meaningful error messages. It manages the internal error stack by tracking the raised exceptions and retaining information about them. OracleAS Portal also includes a set of APIs for presenting errors in a standardized way.

Error handling services are available through the `wwerr_api_error` and `wwerr_api_error_ui` packages. These error handling services include the following key features:

- Error stack. OracleAS Portal uses an error stack to keep track of the error messages. When an error occurs, the error message is pushed onto an error stack. Whenever procedures or function calls are nested, the error stack keeps track of all the error messages. You can choose to retrieve only the top error (or most recent error) by using the `wwerr_api_error.get_top` method. Alternatively, you can get all the error messages on the stack using the `wwerr_api_error.get_errors` function. The stack can also be checked for the presence of any errors by calling the `wwerr_api_error.is_empty` function.

- Error messages. Error handling services provide a way to define meaningful error messages. To define your own error messages, you need to define their name space. The name space consists of the following:

  - Name is the error name.

  - Domain is the area of the product where the error occurred.

  - Subdomain is the subsystem where the error occurred.

  - Context is the name of the function where the error occurred.

  The name space uniquely identifies your error message. If it does not do so, a `wwc-0000` error message is generated.

  The default domains include the portal (`WWC`), application (`WWV`), and page groups (`WWS`). Each domain is further classified into subdomains, which define the object types. The portal domain includes the portlet, page, and document object types. The application domain includes object types such as forms, menus, reports, and charts. The page group domain includes object types such as pages, items, categories, and perspectives. If you need to define an error that does not fall within these classifications, you can define your own domain with subdomains for your errors.

- Message parameters. The other language strings that you create for your errors can take substitution parameters for your messages. The `p1`, `p2`, `p3`... parameters can be used to pass substitution parameters to the error messages. For example, for this string:

      (domain='yahoo', subdomain='provider', name='generalerror', string='Error: %1')

  an error can be added as follows:

      wwerr_api_error.add(p_domain=>'yahoo',  p_sub_domain=>'provider',
        p_name=>'generalerror',  p_context=>'yahoo.show',  p1=> sqlerrm);

- Error display. The `wwerr_api_error_ui` package provides a means to generate a standard user interface for displaying the errors in OracleAS Portal. The error messages can be displayed in two different ways:

  - Full screen user interface: These error messages are displayed in a full screen mode. You may want to display full screen errors when the system encounters fatal or show-stopper errors.

  - Inline user interface: These error messages are displayed within the current page itself. You may use inline errors for minor errors or warnings.

  Additionally, you can choose the output format of the display (HTML, XML, or ASCII text).

### 8.9.1 Using Error Handling

In general, you set up error handling as follows:

1. On detecting error conditions, add the error message, with an appropriate domain and sub-domain combination, to the stack using the `wwerr_api_error.add` procedure.

2. When necessary (for example, at the end of a routine), expose the error messages using the `wwerr_api_error_ui` procedures. To display full screen messages, use the procedures `show_html`, `show_xml`, or `show_text` depending on your preferred output type. To display inline messages, use the procedures `show_inline_html`, `show_inline_xml`, or `show_inline_text`, depending on the output type you desire.

#### 8.9.1.1 Guidelines for Error Handling

While implementing error handling, keep in mind the following:

- While defining your own error messages, use your own error domain for these messages. Never use the `WWC`, `WWV`, or `WWS` domain for your error messages. You will need to write a small loader script to load these into the other language tables.

- Avoid unnecessary error messages. If you do not want to do anything in a function, just return `null` rather than an error. For example, suppose you are coding a `copy_portlet` procedure for your portlet because the provider calls it for all of its other portlets. If you do not wish the `copy_portlet` procedure for this particular portlet to do anything, then simply have it return `null`. If you return errors, it will unnecessarily disrupt the portlet functionality.

- A maximum of ten error messages is kept on the stack. Beyond ten, messages are ignored when a call to `wwerr_api_error.add` is made.

- Use the API as a programmatic way of finding the problem. You can use the non-user-interface format for this purpose. For example, when you programmatically register a provider, the exception block can use `get_text_stack` to get the error messages and print them. This approach helps when debugging calls to public APIs since all of them add errors to the stack for exceptions.

- Remember to seed the other language strings for your error messages. For more information, refer to Section 8.11, "Writing Multilingual Portlets".

- The standard user interface for error messages provides a navigation link back to the previous page. It also includes a Help icon for the specified help URL.

### 8.9.2 Adding Error Handling

The services example, located in `..\pdkplsql\pdk\plsql\svcex` in PDK-PL/SQL (`pdkplsql.zip`), illustrates how you can implement error handling. You can browse through the following example to see how the error handling functions are implemented in a portlet:

1. Open the `services_portlet.pkb` file in an editor.

2. The domain and subdomain definitions for your error messages are provided with aliases in the constants part of your portlet definition.

       DOMAIN           constant varchar2(30) := 'provider';
       SUBDOMAIN        constant varchar2(32) := 'services';
       PORTLET_PATH     constant varchar2(256):= 'oracle.portal.pdk.servicesportlet';
       PREFNAME_STRING  constant varchar2(30) := 'services_string';
       PREFNAME_TITLE   constant varchar2(30) := 'services_title';

3. Find the `show` procedure. This procedure performs a security check and, if an error condition arises, it calls `wwerr_api_error.add` to push the `securityerr` error message onto the stack.

       procedure show
       (
           p_portlet_record        wwpro_api_provider.portlet_runtime_record
       )
       is
       ...
       begin
           -- Perform a security check
           if (not is_runnable(
                p_provider_id     =>  p_portlet_record.provider_id
               ,p_reference_path  =>  p_portlet_record.reference_path)
           ) then
               wwerr_api_error.add(
                         DOMAIN, SUBDOMAIN,
                         'securityerr', 'services_portlet.show');
               raise wwpro_api_provider.PORTLET_SECURITY_EXCEPTION;
           end if;

4. The `show` procedure also checks for any other kind of execution mode and generates an appropriate error message for an invalid display mode.

       if (p_portlet_record.exec_mode = wwpro_api_provider.MODE_SHOW) then
       ...
       elsif (p_portlet_record.exec_mode = wwpro_api_provider.MODE_SHOW_EDIT)
       ...
       else
           wwerr_api_error.add(DOMAIN, SUBDOMAIN,
               'invaliddispmode', 'services_portlet.show');
           raise wwpro_api_provider.PORTLET_EXECUTION_EXCEPTION;
       end if;

5. Lastly, the `show` procedure implements a general error message in the exception handler to catch any errors not trapped by the preceding conditions.

       exception
           when others then
               wwerr_api_error.add(
                   DOMAIN, SUBDOMAIN,
                   'generalerr', 'services_portlet.show');
               raise wwpro_api_provider.PORTLET_EXECUTION_EXCEPTION;
       end show;

6. Error handling is also implemented in the `save_prefs` and `save_default_prefs` procedures. They check whether the error stack is empty and, if it is not, the portlet makes a call to `wwerr_api_error.show_html` to display the error in full screen mode.

       exception
           when INVALID_TEXT_EXCEPTION then
                l_information := l_user||'%'||l_time
                    ||'%INVALID_TEXT_EXCEPTION%'||p_string;
                l_action      := LOG_FAILED;
                wwlog_api.log (p_domain      => DOMAIN,
                               p_subdomain   => SUBDOMAIN,
                               p_name        => l_user,
                               p_action      => l_action,
                               p_information => l_information,
                               p_url         => l_url,
                               p_row_count   => 0,
                               p_elapsed_time=> l_elapsed_time);
                wwerr_api_error.add(DOMAIN, SUBDOMAIN,
                    'invalid_text', 'services_portlet.save_prefs');
            if (not wwerr_api_error.is_empty) then
                wwerr_api_error_ui.show_html;
            end if;
       end save_prefs;

7. Optionally, if you want to see this portlet on a page and it is not already in the Portlet Repository, refer to the instructions in Section 8.3.2, "Implementing the Provider Package" for information on how to add it.

8. Once your portlet appears in the repository, you can add it to a page to test it. To add your portlet to a page, follow the instructions in Oracle Application Server Portal User's Guide.

## 8.10 Implementing Event Logging

OracleAS Portal can log events that occur during transactions with its objects. It stores these logs in the database, which makes them available through standard SQL calls and reporting tools.

You can choose the events you would like to log and organize them categorically based on user-defined domains and subdomains. For the logged events, you can view information about the event, the time the event started and stopped, the host or IP address of the remote user, the browser type, and the language.

Event logging services are available through the `wwlog_api` and `wwlog_api_admin` packages. These services include the following key features:

- Event logs are useful for tracking specific usage of the portal. To track such information, you create a log event. Log events require a name space that consists of the following:

  - Name, which is the event name.

  - Domain, which is the area of the product where the event occurred.

  - Subdomain, which is the subsystem that generated the event.

  The default domains include the portal (`WWC`), application (`WWV`), and page group (`WWS`). Each domain is further classified into subdomains that define the object types. The portal domain includes the portlet, page, and document object types. The application domain includes object types such as forms, menus, reports, and charts. The page group domain includes object types such as folders, items, categories, and perspectives. Events themselves could be of types such as add, delete, personalize, hide, copy, execute, and export. If you need to define an event that does not fall within these classifications, you can define your own domain with subdomains for your events.

- Logs can track information in two different ways:

  - Interval logging calculates the elapsed time for the action performed (for example, the time taken to render a portlet).

  - Event logging logs the occurrence of a single step event you care about (for example, whenever a user personalizes a portlet).

- Log switching enables you to set a switch interval that defines how long you want to maintain your existing log records. The log information stored in the database uses two different tables. The log records are purged based on the value entered for the Activity Log Interval in the Configuration tab of Global Settings (accessible from the Services portlet in the Portal subtab of the Administer tab). When the log interval (in days) is reached, the logging switches between the two logging tables in the database (for example, A and B). Logs first go into A. When the log interval is reached the first time, the logs are written to B. When the log interval is reached again, the logs go back to A. A is emptied in preparation to store the new log records. If you set your log interval to 14 (the default setting), the logs will switch every 14 days, thus preserving for you, at any point in time, records dated between 14 and 28 days old.

### 8.10.1 Using Event Logging

In general, you can set up event logging as follows:

1. Add the event object, with an appropriate domain and subdomain combination, using `wwlog_api_admin.add_log_event`. Adding the event ensures that lists of values and other user interface components invoked when the user is monitoring the events show this new event in their lists.

2. Register the log event record by using `wwlog_api_admin.add_log_registry`. The log registry record represents the events you want to log in the future and provides a means to filter the events that need to be logged.

3. Use `start_log` and `stop_log` to mark the events you want to log in your code. Alternatively, for entering single step event log information, just call the log method to mark that event.

#### 8.10.1.1 Guidelines for Event Logging

While implementing event logging, keep in mind the following:

- Log only what you really care about to improve performance. You don't want to flood the system with log messages that are irrelevant to you. If events are logged in Show mode, then multiple instances of these portlets mean additional hits to the database.

- Choose your domain, subdomain, and log events carefully. While using the log APIs, do not use the OracleAS Portal domains such as `WWC`, `WWV`, or `WWS` for your log messages. Organize your domains and subdomains hierarchically ensuring that they are unique across portlets. If other portlets happen to use the same domains or subdomains, you will see those log messages interspersed with your own.

- Create log events that show up in the pop-up lists of values monitoring the logs. You can simply create log registry records that filter the events that would actually be logged, either by specifying particular events or using the generic filters with wild cards (`%`). Apart from creating log registry records, we recommend that you create log events for events that you want to monitor. This way the lists of values in the user interface show these records for additional functions such as monitoring.

- Provide required privileges to users or user groups who need to monitor the logs. Any logs created by a user can be viewed by that user, the Portal Administrator, and any user with the Edit privilege on the `ANY_LOGS` object type.

### 8.10.2 Adding Event Logging

The services example, located in `..\pdkplsql\pdk\plsql\svcex` in PDK-PL/SQL (`pdkplsql.zip`), illustrates how you can implement event logging. You can browse through this example as follows to see how the event logging functions are implemented in a portlet:

1. Open the `services_portlet.pkb` file in an editor.

   The domain and subdomain definitions for your log messages are provided with aliases in the constants part of your portlet definition.

       DOMAIN           constant varchar2(30) := 'provider';
       SUBDOMAIN        constant varchar2(32) := 'services';
       PORTLET_PATH     constant varchar2(256):= 'oracle.portal.pdk.servicesportlet';
       PREFNAME_STRING  constant varchar2(30) := 'services_string';
       PREFNAME_TITLE   constant varchar2(30) := 'services_title';

2. Find the `save_prefs` procedure. This procedure provides personalizable functionality where you can personalize text and the portlet title in Edit mode. `save_prefs` stores these personalizations in the database. While saving the changes, you should also log them. Hence, this procedure provides an ideal example of implementing the logging service. A single step event is logged using `wwlog_api.log`. The first instance of `wwlog_api.log` logs the event of personalizing text. The second instance logs the event of personalizing the portlet title.

       procedure save_prefs
       ...
       begin
       ...
           if (l_prefs.string_id is null or to_number(l_prefs.string_id) = 0)
           then
               l_action := LOG_INSERT;
       ...
           else  -- string exists in at least one language so update it
               l_action := LOG_UPDATE;
       ...
           end if;
       -- Log this transaction
       l_information := l_user||'%'||l_time||'%completed%'||p_string;
               wwlog_api.log (p_domain      => DOMAIN,
                              p_subdomain   => SUBDOMAIN,
                              p_name        => l_user,
                              p_action      => l_action,
                              p_information => l_information,
                              p_url         => l_url,
                              p_row_count   => l_row_count,
                              p_elapsed_time=> l_elapsed_time);
       ...
           if (l_prefs.title_id is null or to_number(l_prefs.title_id) = 0)
           then
              l_action := LOG_INSERT;
       ...
           else
              l_action := LOG_UPDATE;
       ...
       -- Log this transaction       l_information := l_user||'%'||l_time||'%completed%'||p_title;
              wwlog_api.log (p_domain      => DOMAIN,
                              p_subdomain   => SUBDOMAIN,
                              p_name        => l_user,
                              p_action      => l_action,
                              p_information => l_information,
                              p_url         => l_url,
                              p_row_count   => l_row_count,
                              p_elapsed_time=> l_elapsed_time);
       ...
       end save_prefs;

3. The `save_prefs` procedure also logs an event with `wwlog_api.log` when an exception occurs.

       exception
           when INVALID_TEXT_EXCEPTION then
                l_information := l_user||'%'||l_time
                    ||'%INVALID_TEXT_EXCEPTION%'||p_string;
                l_action      := LOG_FAILED;
                wwlog_api.log (p_domain      => DOMAIN,
                               p_subdomain   => SUBDOMAIN,
                               p_name        => l_user,
                               p_action      => l_action,
                               p_information => l_information,
                               p_url         => l_url,
                               p_row_count   => 0,
                               p_elapsed_time=> l_elapsed_time);
       ...

4. Optionally, if you want to see this portlet on a page and it is not already in the Portlet Repository, refer to the instructions in Section 8.3.2, "Implementing the Provider Package" for information on how to add it.

5. Once your portlet appears in the repository, you can add it to a page to test it. To add your portlet to a page, follow the instructions in Oracle Application Server Portal User's Guide.

## 8.11 Writing Multilingual Portlets

OracleAS Portal has a robust set of APIs for interacting with OracleAS Portal multilingual storage facility. This storage facility provides a mechanism for storing and retrieving strings in different languages. These APIs abstract the native multilingual functionality and provide developers with a powerful storage mechanism for developing providers that support different language environments.

Multilingual services are available through the `wwnls_api` package. These services include the following key features:

- The multilingual APIs enable the provider to load several translations for the strings displayed in their portlets. Once the strings have been loaded, the provider can call the APIs to retrieve the strings from the multilingual table as needed.

- Context APIs retrieve the user's language and the appropriate translation for that language. The Context APIs determine the user's language environment from the language setting in the browser. When a requested translation does not exist, the APIs return the base language translation.

For example, assume that the provider's `register` procedure loads US and French translations for the portlet title. When the portlet is rendered, the provider implementation retrieves the portlet title string from the table and displays the following results:

- A request for a French string causes the portlet title to appear in French.

- A request for a US string causes the portlet title to appear in US English.

- A request for a Chinese string causes the portlet title to appear in US English because we did not load a translation for the Chinese language.

### 8.11.1 Using Multilingual Support

In general, you can set up multilingual support as follows:

1. Load your string definitions into the database using the string equivalents for each language you intend to use. For this purpose, call the `wwnls_api.add_string` or `wwnls_api.set_string` with an appropriate domain, subdomain, error message name, and error text combination.

2. Retrieve the strings you require with `wwnls_api.get_string` for the language that you desire.

### 8.11.2 Adding Multilingual Support

To add multilingual support, you need to perform the following tasks:

- Loading Language Strings

- Retrieving Language Strings

#### 8.11.2.1 Loading Language Strings

Language strings can be loaded by a script that is part of the provider installation. This script calls `add_string` and `set_string` to create equivalent strings for different languages.

OracleAS Portal uniquely identifies language strings using a combination of domain, subdomain, and name. The domain and subdomain provide a way to categorize the strings. The domain and subdomain should be unique enough to reasonably preclude conflicts with other users of the APIs.

- A domain is a particular area of the product. An example of a domain could be provider or page group.

- A subdomain is a subsystem of the domain. For example, the subdomain could be the provider name (for example, HelloProvider) or subpage name (for example, HelloPage).

The services example, located in `..\pdkplsql\pdk\plsql\svcex` in PDK-PL/SQL (`pdkplsql.zip`), illustrates how you can implement multilingual support. You can browse through this example as follows to see how to load strings for multilingual support:

1. Open the `services_seed.sql` file in an editor.

2. Notice the `add_string` call with the parameters for domain name, subdomain name, string name, language, and the actual string text. It returns the String ID for the language string. For setting equivalent strings in other languages, `set_string` is called with the same parameters.

       set serveroutput on size 1000000
       set define off

       declare
           l_string_id  integer;
           l_person_id  integer;
           l_group_id   integer;
       begin
       ...
       -- strings for portlet record fields
       l_string_id := wwnls_api.add_string(
        'provider','services','ptldefname','us','DatabaseServicesPortlet');
       wwnls_api.set_string(
        'provider','services','ptldefname','d','DatenbankServicesPortlet-d');
       l_string_id := wwnls_api.add_string(
        'provider','services','ptldeftitle','us','Database Services Portlet');
       wwnls_api.set_string(
        'provider','services','ptldeftitle','d','Datenbank Services Portlet - d');
       l_string_id := wwnls_api.add_string(
        'provider','services','ptldefdesc','us','This is the database services
       portlet implemented in PL/SQL. It displays 6 show modes.');
       wwnls_api.set_string(
        'provider','services','ptldefdesc','d','Dies ist das Datenbank Service
       Portlet, erstellt in PL/SQL. Es stellt 6 Anzeigemodi dar. - d');
       l_string_id := wwnls_api.add_string(
        'provider','services','ptldevtmmsg','us','Web Services Portlet Timed
       Out.');
       wwnls_api.set_string(
        'provider','services','ptldevtmmsg','d','Zeitüeberschreitung aufgetreten
       in Web Services Portlet. -d');

#### 8.11.2.2 Retrieving Language Strings

The services example, located in `..\pdkplsql\pdk\plsql\svcex` in PDK-PL/SQL (`pdkplsql.zip`), illustrates how you can implement multilingual support. You can browse through this example as follows to see how to retrieve strings for multilingual support:

1. Open the `services_portlet.pkb` file in an editor.

2. The domain and subdomain definitions for your language strings are provided with aliases in the constants part of your portlet definition.

       DOMAIN           constant varchar2(30) := 'provider';
       SUBDOMAIN        constant varchar2(32) := 'services';
       PORTLET_PATH     constant varchar2(256):= 'oracle.portal.pdk.servicesportlet';
       PREFNAME_STRING  constant varchar2(30) := 'services_string';
       PREFNAME_TITLE   constant varchar2(30) := 'services_title';

3. Find the `get_portlet_info` procedure. Notice the calls to `wwnls_api.get_string` to populate the portlet title, name, and description.

       function get_portlet_info
       (
            p_provider_id in integer
           ,p_language in varchar2
       )
       return wwpro_api_provider.portlet_record
       is
            l_portlet  wwpro_api_provider.portlet_record;
       begin
           l_portlet.id := services_provider.SERVICES_PORTLET_ID;
           l_portlet.provider_id := p_provider_id;
           l_portlet.language := p_language;
           l_portlet.title :=
               wwnls_api.get_string(
                    p_domain       =>  DOMAIN
                   ,p_sub_domain   =>  SUBDOMAIN
                   ,p_name         =>  'ptldeftitle'
                   ,p_language     =>  p_language
                   );
           l_portlet.description :=
               wwnls_api.get_string(
                    p_domain       =>  DOMAIN
                   ,p_sub_domain   =>  SUBDOMAIN
                   ,p_name         =>  'ptldefdesc'
                   ,p_language     =>  p_language
                   );
           l_portlet.name :=
               wwnls_api.get_string(
                    p_domain       =>  DOMAIN
                   ,p_sub_domain   =>  SUBDOMAIN
                   ,p_name         =>  'ptldefname'
                   ,p_language     =>  p_language
                   );
       ...

4. Browse the rest of the file to examine other usage examples of `wwnls_api.get_string`, which is used in several other places in `services_portlet.pkb`.

5. Optionally, if you want to see this portlet on a page and it is not already in the Portlet Repository, refer to the instructions in Section 8.3.2, "Implementing the Provider Package" for information on how to add it.

6. Once your portlet appears in the repository, you can add it to a page to test it. To add your portlet to a page, follow the instructions in Oracle Application Server Portal User's Guide.

## 8.12 Enhancing Portlets for Mobile Devices

This section explains how to go about enhancing a portlet with PDK-PL/SQL for a mobile device. Before proceeding with this section, you should familiarize yourself with the guidelines for building mobile-enabled portlets, Section 8.1.3, "Guidelines for Mobile Portlets", and the methods of building portlets with PDK-PL/SQL, Section 8.2, "Building PL/SQL Portlets with the PL/SQL Generator" and Section 8.3, "Building PL/SQL Portlets Manually".

To properly build a portlet for a mobile device, do the following:

1. Set the portlet record attributes to support mobile output. Requests arriving from mobile devices through the mobile gateway need to be answered with OracleAS Wireless XML using the `text/vnd.oracle.mobilexml` content type in the header. A mobile-enabled portlet must specify that it can handle these types of requests and produce the mobile content accordingly.

   Table 8-6 lists the portlet record attributes pertinent to mobile portlets and explains how you should specify them.

   Table 8-6 Portlet Record Attributes

   Attribute | Description
   --- | ---
       accept_content_type
   | Specify a comma-delimited list of the content types that the portlet produces. If the portlet can produce both HTML and OracleAS Wireless XML, the string would be:
       text/html, text/vnd.oracle.mobilexml
   If the portlet can produce only OracleAS Wireless XML, the string would be:
       text/vnd.oracle.mobilexml
       mobile_only
   | Indicate whether the portlet is available only to mobile pages. TRUE declares the portlet as mobile only, and it will not appear in the Add Portlets page for a standard page. FALSE declares the portlet as mobile and standard, and it will appear in the Add Portlets page for both standard and mobile pages. If the portlet only produces OracleAS Wireless XML and you set this flag to FALSE, the OracleAS Wireless XML is automatically transformed into HTML for desktop devices (such as a normal PC Web browser). This functionality enables you to develop portlets that output only OracleAS Wireless XML but can be viewed on standard pages for desktop access as well as for mobile access.
       short_title
   | Enter a shorter version of the portlet title. This title is used on mobile devices because it is more likely to fit the smaller screen. If you do not set a short title, then the portlet title is used instead.
       has_show_link_mode
   | Indicate whether you have implemented Link mode for the portlet. We recommend that all mobile portlets enable Link mode. The rationale for using Link mode is more fully explained when you reach step 3 below.

2. If the portlet can produce both HTML and OracleAS Wireless XML, then, during execution, it must determine whether the request is for a mobile or a desktop device. If it is a desktop request, then the portlet must product HTML output. If it is a mobile request, then it must produce OracleAS Wireless XML output. You can determine the request type with the `wwctx_api.get_http_accept()`. It fetches the HTTP accept header, which indicates the request type. In the portlet response, you must set the MIME header to the appropriate value in the HTTP header before the portlet content is produced. If not, then the portlet response is rejected when the resulting page is built by OracleAS Portal. If the call to get `wwctx_api.get_http_accept()` returns a string starting with `text/vnd.oracle.mobilexml`, then you can assume it is a mobile request. Otherwise, it is a desktop request. In the case of a mobile request, you should set the MIME header to `text/vnd.oracle.mobilexml`. In the case of a desktop request, you can explicitly set the MIME header to `text/html`, but it is not required that you do so because it's the default setting.

   If you want to produce HTML for desktop requests and OracleAS Wireless XML for mobile requests, the `show` procedure for your portlet should look similar to the following:

       procedure show
           (
                p_portlet_record in out wwpro_api_provider.portlet_runtime_record
           )
           is
           begin
             --
             -- Does the portal want us to render the portlet contents?
             --
             if (p_portlet_record.exec_mode = wwpro_api_provider.MODE_SHOW) then
                 --
                 -- Is this a mobile request?
                 --
                 if owa_util.get_cgi_env('HTTP_ACCEPT') like
                   wwpro_login.CONTENT_TYPE_MOBILEXML || '%' then
                     --
                     -- This is a mobile request for the portlet contents
                     -- call the mobile show contents procedure
                     --
                     render_mobile_show_contents(p_portlet_record);
                 else
                     --
                     -- This is a desktop request for the portlet contents,
                     -- call the desktop show contents procedure
                     --
                     render_desktop_show_contents(p_portlet_record);
                 end if;
             elsif -- check for other show modes, and handle them
             ...
       end show;

   If you want to produce only OracleAS Wireless XML for all requests, the `show` procedure for your portlet should look similar to the following:

       procedure show
           (
                p_portlet_record in out wwpro_api_provider.portlet_runtime_record
           )
           is
           begin
             --
             -- Does the portal want us to render the portlet contents?
             --
             if (p_portlet_record.exec_mode = wwpro_api_provider.MODE_SHOW) then
                 --
                 -- This is either a desktop or mobile request for the portlet
                 -- contents call the show contents procedure to render the
                 -- OracleAS Wireless XML output
                 --
                 render_show_contents(p_portlet_record);
             elsif -- check for other modes
             ...
       end show;

   In order to generate OracleAS Wireless XML, the `render_show_contents` or `render_show_mobile_contents` procedure would need to contain something similar to the following:

       ...
           (
                p_portlet_record in out wwpro_api_provider.portlet_runtime_record
           )
           is
           begin
             --
             -- Set the MIME type to be Oracle9iAS Wireless XML
             --
             owa_util.mime_header('text/vnd.oracle.mobilexml');
             --
             -- Output the Oracle9iAS Wireless XML markup
             --
             htp.p('<SimpleText><SimpleTextItem>');
             htp.p('Hello World!');
             htp.p('</SimpleTextItem></SimpleText>');
       ...

   Note: As this is a mobile request the MIME header is set to `text/vnd.oracle.mobilexml`. In the OracleAS Wireless XML markup, you only need to render the actual content. OracleAS Portal fits it into the complete OracleAS Wireless XML document. You do not need `<SimpleResult>` or `<SimpleContainer>` tags. These tags are rendered by OracleAS Portal along with a back link, which, by default, is assigned to one of the buttons on the mobile device.

3. If you want your portlet to allow personalization of titles for mobile rendering, you need to implement Link mode. Link mode is only called for mobile requests and it renders a link to the portlet content. This link appears in the menu of page contents when the user first navigates to the page. If a Link mode is not present for a portlet, then OracleAS Portal renders a default link mode using the short title from the portlet record. You can also use Link mode to render more than just a title. For example, in a stock portlet, you could render the stock price of a user's favorite stock. Thus, they could see the current stock price without drilling down further.

   The link for Link mode rendition is provided by OracleAS Portal and passed to the portlet via the `page_url` parameter on the portlet record. The portlet can extend or completely replace this behavior. If this link is rewritten as a URL that takes the user away from OracleAS Portal (it does not point to the OracleAS Portal middle tier server), then the portlet should set the `show_behaviour_style` field to `wwpro_api_provider.EXTERNAL_PORTLET` on the portlet record.

   The following example illustrates how to code your `show` procedure to include Link mode:

       procedure show
           (
                p_portlet_record in out wwpro_api_provider.portlet_runtime_record
           )
           is
           begin
             --
             -- Does the portal want us to render the portlet contents?
             --
             if (p_portlet_record.exec_mode = wwpro_api_provider.MODE_SHOW) then
                 --
                 -- Is this a mobile request?
                 --
                 if owa_util.get_cgi_env('HTTP_ACCEPT') like
                   wwpro_login.CONTENT_TYPE_MOBILEXML || '%' then
                     --
                     -- This is a mobile request for the portlet contents,
                     -- call the mobile show contents procedure
                     --
                     render_mobile_show_contents(p_portlet_record);
                 else
                     --
                     -- This is a desktop request for the portlet contents,
                     -- call the desktop show contents procedure
                     --
                     render_desktop_show_contents(p_portlet_record);
                 end if;
             --
             -- Does the portal want us to render the LINK mode?
             --
             elsif (p_portlet_record.exec_mode = wwpro_api_provider.MODE_LINK) then
                 --
                 -- This is a mobile request for the portlet link mode,
                 -- call the mobile link procedure
                 --
                 render_mobile_link(p_portlet_record);
             elsif -- check for other show modes, and handle them
             ...
       end show;

   The following example illustrates what you must implement in the `render_mobile_link` procedure for Link mode. In particular, notice the setting of the MIME type, the usage of `get_custom_short_title`, and the OracleAS Wireless XML output.

       procedure render_mobile_link
           (
                p_portlet_record in out wwpro_api_provider.portlet_runtime_record
           )
           is
             l_short_title varchar2(80);
           begin
             --
             -- Set the MIME type to be Oracle9iAS Wireless XML
             --
             owa_util.mime_header('text/vnd.oracle.mobilexml');
             --
             -- Get the personalized short title for this portlet instance
             -- using the language and reference path held in the portlet
             -- record
             --
             l_short_title := get_custom_short_title(p_portlet_record);
             --
             -- Output the OracleAS Wireless XML markup
             --
             if l_short_title is not null then
                   htp.p('<SimpleHref target="'
                       || htf.escape_sc(p_portlet_record.page_url)
                       || '">'
                       || l_short_title
                       || '</SimpleHref>');
               else
                   htp.p('<SimpleHref target="'
                       || htf.escape_sc(p_portlet_record.page_url)
                       || '">'
                       || get_custom_title(p_portlet_record)
                       || '</SimpleHref>');
               end if;
       end render_mobile_link;

### 8.12.1 Accessing the DeviceClass Header

To further facilitate the creation of mobile-enabled portlets, you can access information in the `DeviceClass` header, which is sent to all portlets, via `owa_util.get_cgi_env('x-oracle-device.class')`. You can use the values of this header to determine the complexity of the OracleAS Wireless XML rendition of the portlet. For example, the PDA rendition could contain considerably richer content than the microbrowser rendition. The voice rendition could contain extra tags for voice commands and links to sound files, which play instead of the text-to-speech system.

Table 8-7 describes the values available in the header.

Table 8-7 DeviceClass Header Values

Value | Description
--- | ---
    voice
| Indicates a voice-only device, such as a normal telephone calling a voice access number.
    microbrowser
| Indicates a small size display device, which supports a markup browser, such as a WAP phone.
    pdabrowser
| Indicates a medium size display device, such as a Palm or PocketPC.
    pcbrowser
| Indicates a large size display device used with desktop browsers

## 8.13 Registering Providers Programmatically

In most cases, you use the OracleAS Portal user interface to register providers as described in Section 8.2.3.2, "Registering the Database Provider". In some instances, though, you may wish to register a provider programmatically rather than through the user interface. This section describes how to use `wwpro_api_provider_registry.register_provider` to register your providers:

- Registration Prerequisites

- Provider Record Input

- Registration Example

### 8.13.1 Registration Prerequisites

In order to register a provider programmatically with `wwpro_api_provider_registry.register_provider`, the following requirements must be met:

- You must first install the provider as described in Section 8.2.3.1, "Installing the Packages in the Database".

- You must have the necessary privileges to execute `wwpro_api_provider_registry.register_provider`. As it is a secure API, a security check determines whether the user calling it has the necessary privileges. At a minimum, you must have `wwsec_api.ANYPROVIDER_PUBLISH`. If you have the privileges to execute the API, you also have sufficient privileges to set the OracleAS Portal session via `wwctx_api.set_context`.

Note: If the database user of the SQL*Plus session is the OracleAS Portal schema owner, then, by default, the user is the OracleAS Portal schema owner (unless `wwctx_api.set_context` is called with a different user). The schema owner already has the necessary privileges to execute `wwpro_api_provider_registry.register_provider`.

### 8.13.2 Provider Record Input

The `wwpro_api_provider_registry.register_provider` API requires the `provider_record` as input. When you pass the `provider_record` to `register_provider`, all of the fields are not required. Table 8-8 indicates which fields are required for Web and database providers. If a field is not applicable for a particular type of provider, it is shown as NA.

Table 8-8 Required `provider_record` Fields

Field | Required for database providers | Required for Web providers | Comments
--- | --- | --- | ---
    name
| Yes | Yes | None
    implementation_style
| Yes | Yes | This field is always the following for PL/SQL portlets:
    wwpro_api_provider_registry.
       DATABASE_IMPL;
    implementation_owner
| NA | Yes | This field is the schema where the provider/portlet is located.
    implementation_name
| NA | Yes | When registering a database provider the implementation package of the provider must be valid for the registration to be successful.
    login_frequency
| Yes | Yes | This field specifies how often to grab the session information.
    http_app_type
| Yes | NA | For external application Web providers:
    wwpro_api_provider_registry.HTTP_APP_TYPE_EXTERNAL
For regular Web providers:
    wwpro_api_provider_registry.HTTP_APP_TYPE_PORTAL
    http_url
| Yes | NA | None
    require_url
| Yes | NA | None
    encryption_key
| If provider_key is not null,yes. If provider_key is null, no. | If provider_key is not null,yes. If provider_key is null, no. | None

### 8.13.3 Registration Example

The following sample SQL script illustrates the usage of `wwpro_api_provider_registry.register_provider`.

    set serveroutput on size 1000000
    set define on
    declare
        l_prov_rec     wwpro_api_provider_registry.provider_record;
        l_prov_id      integer;
    begin
        l_prov_rec.name                 := 'CacheProvider';
        l_prov_rec.display_name         := 'Cache Provider';
        l_prov_rec.timeout              := 10;
        l_prov_rec.timeout_msg          := 'The provider timed out';
        l_prov_rec.implementation_style := wwpro_api_provider_registry.DATABASE_IMPL;
        l_prov_rec.implementation_owner := '&&2';
        l_prov_rec.implementation_name  := 'cache_provider';
        l_prov_rec.language             := wwctx_api.get_nls_language;
        l_prov_rec.enable_distribution  := true;
        l_prov_rec.login_frequency      := wwpro_api_provider_registry.LOGIN_
                                             FREQUENCY_NEVER;
        l_prov_rec.created_on           := sysdate;
        l_prov_rec.created_by           := '&&1';
        l_prov_rec.last_updated_on      := sysdate;
        l_prov_rec.last_updated_by      := '&&1';
        l_prov_id := wwpro_api_provider_registry.register_provider(l_prov_rec);
        commit;
        dbms_output.put_line('Cache Provider successfully registered');
    exception
        when others then
            dbms_output.put_line('ERROR: Could not register Cache Provider');
            dbms_output.put_line('SQLERRM: ' || SQLERRM);
            rollback;
    end;
    /

Note: After you have registered your provider, you may also refresh the Portlet Repository programmatically using `wwpro_api_provider_registry.refresh_portlet_repository` or through the OracleAS Portal user interface.