Sei sulla pagina 1di 15

Oracle 9iAS Best Practices in PSP Development

Author: Last Updated: Version: Bryn Llewellyn and Robert Pang Auguest 27, 2001 1.0

Contents

WHAT IS PSP?............................................................................................................................................................3 WHEN TO USE PSP?....................................................................................................................................................4 Writing PL/SQL stored procedures directly ..........................................................................................................4 Writing PSP pages.................................................................................................................................................5 HOW TO STRUCTURE A PSP-BASED APPLICATION? .....................................................................................................7 Isolation of application logic.................................................................................................................................7 Framework for your PSP pages ............................................................................................................................9 Using procedure invocation to include pagelets.............................................................................................11 Exception handling ..............................................................................................................................................11 REUSE FUNCTIONALITY IN EXISTING PL/SQL WEB-BASED APPLICATIONS ...............................................................14

What is PSP?
With the evolution of World Wide Web, Web users demand rich Web content which has to be constantly updated to catch up with the explosion of information in this ever-changing world. This demand calls for a rapid development environment of Web pages which contain dynamic data. Historically, Web content developers typically wrote CGI programs in C or Perl to fetch data and produce the entire web page within the same program. In this environment, the application logic to manipulate the data and the presentation logic to format the content are often intertwined, and the development of such dynamic pages demands significant programming skills and discipline. Also, the maintenance of such CGI programs also proves to be timely and costly. Scripting in HTML pages naturally fulfills the demand for a rapid development environment of dynamic Web pages. It allows small scripts (sometimes referred to as scriptlets) to be embedded in HTML pages while the HTML pages still look like HTML pages. Those scripts contain the logic to produce the dynamic portions of HTML pages and they are executed as the pages are requested by the users using Web browsers. Exploiting the full potential of scripting functionality, Web application developers may also utilize scripting not only to quickly prototype sophisticated Web applications but also to deploy them. In contrast to the traditional web development using CGI, the advantages of scripting is that it is easy to learn and use. The separation of HTML content from application logic makes script pages easier to develop, debug and maintain. The simpler development model, along with the fact that scripting languages usually demand less programming skills, enable web page writers to develop dynamic web pages as well. PL/SQL Server Pages (PSP) enables users to develop server-side script pages in PL/SQL. This paper presents some best-practices in PSP development that you will find useful in your Web application development. It assumes that you understand the concepts of database applications and Web applications and have PL/SQL knowledge.

When to use PSP?


PL/SQL offers numerous benefits in database application development. Once you have decided that PL/SQL is the language of choice for your Web application development, the immediate question you want to ask may be - is PSP the right choice for my development? To answer that question, lets consider what choices are available to you. Oracle9i Application Server provides the PL/SQL Gateway for you to execute your Web application that is written in PL/SQL. You may write your application either as PL/SQL stored procedures directly, or as a set of PSP pages.

Writing PL/SQL stored procedures directly


If your Web application involves little HTML content, you should consider writing your application as PL/SQL stored procedures directly. You may produce HTML content in your application by making calls to the PL/SQL Web Toolkit. The PL/SQL Web Toolkit is a set of PL/SQL packages designed to facilitate the development of Web applications. The core of the toolkit are the HTP and HTF packages that contain procedures and functions which help you generate HTML content. (Additional functionality is provided by the OWA_ packages.) You may generate HTML content directly by invoking the HTP.PRINT procedure. For example, the following procedure generates a dynamic home page of a user.
CREATE OR REPLACE PROCEDURE home_page AS BEGIN htp.print('<HTML> <HEAD><TITLE>My home page</TITLE></HEAD> <BODY>This is the home page of ' || user || ' generated on ' || sysdate || '.</BODY> </HTML>'); END;

The HTP and HTF packages also provide a set of procedures and functions to generate HTML tags for you. Using the helper procedures in the HTP package, you may rewrite your procedure as
CREATE OR REPLACE PROCEDURE home_page AS BEGIN htp.htmlOpen; htp.headOpen; htp.title('My home page'); htp.headClose; htp.bodyOpen; htp.print ('This is the home page of ' || user || ' generated on ' || sysdate || htp.bodyClose; htp.htmlClose; END;

'.');

As your need to produce HTML content in your application grows, you will notice that the job to edit the HTML content produced in your application becomes more and more challenging because you cannot see the results of your HTML content
4

visually. You want to use a what-you-see-is-what-you-get (WYSIWYG) HTML authoring tool to edit the HTML content in your application but such a tool will not understand PL/SQL to allow you to edit the HTML content visually. In this case, you need PSP.

Writing PSP pages


Consider the above example again. You may write the above dynamic home page as the following PSP.
<HTML> <HEAD><TITLE>My home page</TITLE></HEAD> <BODY>This is the home page of <%= user %> generated on <%= sysdate %>.</BODY> </HTML>

Notice that the PSP page is largely a HTML page with PL/SQL codes embedded in it and the PL/SQL codes are clearly marked up with script tags. A script-tag aware HTML authoring tool will be able to safely set aside the script codes and allow you to edit the HTML content visually. In the figure below, we illustrate the use of Macromedia Dreamweaver to edit the PSP page example. The lower WYSIWYG view shows you the HTML content visually and the upper HTML view shows you what your HTML content is like. The two icons that interleave with the HTML content in the WYSIWYG view represent the two script blocks <%= user %> and <%= sysdate %>.

Using the tool, you may visually edit the HTML content and change the layout, and see the results visually. For example, you can change the background color of your page, edit text, and change fonts visually without typing any HTML codes.

After the page is saved, your new PSP page will be like this.
<HTML> <HEAD><TITLE>My home page</TITLE></HEAD> <BODY BGCOLOR=#FFFFFF> This is the home page of <%= user %>.<HR> <FONT FACE=Arial, Helvetica, sans-serif SIZE=-1> Generated on <%= sysdate %>.</FONT></BODY> </HTML>

The fact that scripts are isolated from the HTML content inside a PSP page make possible the use of a WYSIWYG HTML authoring tool to edit PSP visually. With a WYSIWYG HTML authoring tool, you may use a HTML designer to design the HTML content and layout leave the application logic, namely the PL/SQL logic, to your application developer. This division of labor enables both your HTML designer and application developer to utilize the best of their skills and focus on the job that they are best at. In summary, if your Web application contains dynamic Web pages contain sophisticated HTML content along with some application logic, you will find PSP the right tool for developing those content-rich Web pages. On the other hand, if your Web pages involves only simple HTML content, you may find it more straightforward to write PL/SQL stored procedures directly to generate them.

How to structure a PSP-based application?


Some of the best practices described in this section are the familiar principles of modular design expressed in PSP terminology. Some specific to script page development and some are highly specific to scripting in PSP.

Isolation of application logic


Separate the application logic which does not involve HTML content from your PSP page. Put the application logic in standalone PL/SQL packages, functions or procedures. This makes the coding and debugging of your application logic easier. This is especially true since the coding and debugging of the packages, functions and procedures can be done outside of the Web browser and the PL/SQL Gateway of Oracle9i Application Server. Also, the maintenance of your application is easier since you can change your application logic and your HTML content independent of each other. Consider the following example. Instead of writing the application logic for calculating the total cost of a customers order inside a PSP page like this:
<%@ <%@ <%@ <%@ <%! plsql plsql plsql plsql procedure="process_order_page" %> parameter="customer_id" type="NUMBER" %> parameter="product_id" type="NUMBER" %> parameter="units" type="NUMBER" %> NUMBER; NUMBER; NUMBER; NUMBER; NUMBER; NUMBER;

cust_rating cust_discount prod_discount unit_price discount cost

%> <HTML> <TITLE>Customer Order Processing</TITLE> <BODY> ... <% /* Give discount basing on the customer's rating */ select rating into cust_rating from customers where id = customer_id; if (cust_rating > 5) then cust_discount := 0.2; -- 20% discount elsif (cust_rating > 3) then cust_discount := 0.1; -- 10% discount else cust_discount := 0; -- no discount end if; /* See select into from if the item is on promotion */ nvl(discount, 0), price prod_discount, unit_price products
7

where id = product_id; /* Customer receives the deeper discount of the two */ if (cust_discount > prod_discount) then discount := cust_discount; else discount := prod_discount; end if; cost := unit_price * (1 - discount) * units; %> Your total cost of <%= units %> items of product ID <%= product_id %> is <%= cost %>. This reflects a <%= discount * 100 %>% discount off the unit price of <%= unit_price %>.

You can separate the application logic of calculating the discount and the total cost in a separate procedure like this
CREATE OR REPLACE PROCEDURE calculate_order_cost( customer_id IN NUMBER, product_id IN NUMBER, units IN NUMBER, unit_price OUT NUMBER, cost OUT NUMBER, discount_given OUT NUMBER) AS cust_rating NUMBER; cust_discount NUMBER; prod_discount NUMBER; prod_unit_price NUMBER; discount NUMBER; BEGIN /* Give discount basing on the customer's rating */ select rating into cust_rating from customers where id = customer_id; if (cust_rating > 5) then cust_discount := 0.2; -- 20% discount elsif (cust_rating > 3) then cust_discount := 0.1; -- 10% discount else cust_discount := 0; -- no discount end if; /* See select into from where if the item is on promotion */ nvl(discount, 0), price prod_discount, prod_unit_price products id = product_id;

/* Customer receives the deeper discount of the two */ if (cust_discount > prod_discount) then discount := cust_discount; else discount := prod_discount; end if;
8

unit_price := prod_unit_price; cost := prod_unit_price * (1 - discount) * units; discount_given := discount; END;

In your new PSP page, you invoke the procedure like this:
<%@ <%@ <%@ <%@ <%! plsql plsql plsql plsql procedure="process_order_page" %> parameter="customer_id" type="NUMBER" %> parameter="product_id" type="NUMBER" %> parameter="units" type="NUMBER" %>

unit_price NUMBER; discount NUMBER; cost NUMBER; %> <HTML> <TITLE>Customer Order Processing</TITLE> <BODY> ... <% /* Calculate the cost of the order */ calculate_order_cost(customer_id, product_id, units, unit_price, cost, discount); %> Your total cost of <%= units %> items of product ID <%= product_id %> is <%= cost %>. This reflects a <%= discount * 100 %>% discount off the unit price of <%= unit_price %>.

Remember: isolate your application logic from your HTML content whenever possible.

Framework for your PSP pages


Define a framework for your PSP pages. If you may have a need for a common template for your pages (e.g. common header, footer and look-and-feel such as color scheme), it is better to decide whether you will use one or not in the beginning of a project. If you will need one, define the templates and extract the HTML codes of the templates and make them into separate PSP "pagelets". For example, if you decide that every PSP page of your application must include links to the customer help page and a search button at the top of the page, and the customer contact information at the bottom of the page, each of your pages should share a template like this:
<HTML> <TITLE>Page Title</TITLE> <BODY> <!-- header: links to customer help page and search button --> <CENTER> <A HREF="/support/help.html">Customer Help</A> <FORM ACTION="/product/search_prod"><INPUT NAME="item"><INPUT TYPE=SUBMIT></FORM> <HR> <!-- end of header -->
9

... page body ... <!-- footer: company contact info --> <HR> <CENTER> WebStore Inc. <BR> 123 Main Street <BR> San Francisco, CA 94021 <BR> (415) 123-4567 <BR> <A HREF="mailto:sales@webstore.com">mailto:sales@webstore.com</A> <!-- end of footer --> </BODY> </HTML>

Now extract the header and footer as "pagelets": header.psp:


<%@ plsql procedure="page_header" %> <!-- header: links to customer help page and search button --> <CENTER> <A HREF="/support/help.html">Customer Help</A> <FORM ACTION="/product/search_prod"><INPUT NAME="item"><INPUT TYPE=SUBMIT></FORM> <HR> <!-- end of header -->

and footer.psp:
<%@ plsql procedure="page_footer" %> <!-- footer: company contact info --> <HR> <CENTER> WebStore Inc. <BR> 123 Main Street <BR> San Francisco, CA 94021 <BR> (415) 123-4567 <BR> <A HREF="mailto:sales@webstore.com">mailto:sales@webstore.com</A> <!-- end of footer -->

With these "pagelets" loaded to the database, you can include the "pagelets" by invoking the compiled PSP stored procedures page_header and page_footer:
<HTML> <TITLE>Page Title</TITLE> <BODY> <% page_header; %> ... page body ... <% page_footer; %> </BODY> </HTML>

10

Using procedure invocation to include pagelets


Oracle automatically maintains the dependency among PL/SQL procedures stored in the database. When a procedure is modified in the database, the database automatically marks the other procedures that depend on it as invalid. Those procedures can be recompiled on user request. When a procedure that a user invokes from a URL becomes invalid, the PL/SQL Gateway of Oracle9i Application Server automatically recompiles the procedure. This auto-recompilation comes in handy for PSP development. In PSP, there are two ways to include a PSP "pagelet" in another page. You may use the include directive, namely <% @include file=psp-pagelet.psp %> to include the text of psp-pagelet.psp during the compilation of your other page. Or, you may use procedure invocation technique as mentioned above, namely <% psp-pageletprocedure; %>. If you use the include directive to include a pagelet and you make changes to the pagelet, you will have to explicitly recompile all the pages that include the pagelet. On the other hand, if you use the procedure invocation technique to include a pagelet and you make changes to the pagelet, you just need to recompile the pagelet itself. The databases dependency-checking mechanism and the PL/SQL Gateways auto-recompilation will automatically recompile your pages that invoke the pagelet for you. Consider the example discussed in the previous section again. If you change the header or footer pagelets, you will find it extremely helpful when you do not need to recompile all your pages. There may still be situations where the procedure invocation technique does not satisfy what you initially feel is your include need. For example, if two or more of your PSP pages declare the same set of variables then you might feel you want to extract the variable declaration, namely %<! variable-declarations; %>, in a common pagelet and include it in the pages. In this case, the procedure invocation technique would not work for you. However, such an approach feasible in several languages is universally considered bad practice. (Quite often the requirement for repeating variable declarations is an indication of bad module decomposition.) A special case is constants, for example sort_mode, with values 1 for sort by date and 2 for sort by price, to be used both in populating a choice device on a calling page and in testing the users choice in a called page. Code is generally more readable and robust if symbolic constants are used for such cases. In PL/SQL programming these would probably be package global constants. The best way to handle these in PSP development is to create a PL/SQL package for defining the constants and to invoke them thus: <%= Const_Pkg.sort_by_date %>.

Exception handling
The scope of this discussion of exception handling is limited to the stateless invocation method, namely where the Oracle session that runs the PL/SQL procedure in response to the URL terminates when that procedure (and all its child invocations) is complete. In this situation it is not necessary to write explicit code to, for example, close an open cursor when an exception is raised since Oracle
11

automatically closes open cursors (and frees up other database resources) as the session terminates. Lets distinguish between two types of exception: expected and unexpected, as illustrated by the following. Consider an application to hold information about support incidents. There is certain to be an INCIDENTS table holding a user-visible tracking number and some descriptive information. The application will have a query screen allowing the information about a particular incident to be retrieved by its tracking number. We can illustrate this thus
create insert insert insert table incidents ( tracking_no number, description varchar2(20) ); into incidents values ( 1, 'incident one' ); into incidents values ( 2, 'incident two' ); into incidents values ( 2, 'incident three' ) /* deliberate error */;

create or replace procedure Show_Incident ( p_tracking_no in incidents.tracking_no%type ) is v_description incidents.description%type; begin select description into v_description from incidents where tracking_no = p_tracking_no; -- show v_description end Show_Incident;

Executing Show_Incident(1) runs without error. But executing Show_Incident(2) raises


ORA-01403: no data found ORA-06512: at "THE_USER.Show_Incident", line 4

This is an expected exception since the user must be expected to occasionally mistype a tracking number. Executing Show_Incident(3) raises
ORA-01422: exact fetch returns more than requested number of rows ORA-06512: at "THE_USER.Show_Incident", line 4

This is an unexpected exception since the application is designed from the axiom that a tracking number is unique. Of course we would expect that this would be reflected by a primary key or unique constraint, so this exception could be raised only if the intended constraint had been accidentally dropped. An expected exception should be handled locally and with explicit code, for example
begin select description into v_description from incidents where tracking_no = p_tracking_no; Dbms_Output.Put_Line ( v_description ); -- show v_description exception when NO_DATA_FOUND then
12

-- show 'You must have mistyped. Please try again.' end Show_Incident;

Note: in many cases, the principle of isolation of application logic outlined above will mean that the handling of expected exceptions will be done in hand-written PL/SQL procedures rather than in PSPs. Of course, the formatting of the userfriendly message could be conveniently delegated to a PSP. Its usually not cost-effective use of development resources to attempt to predict and program explicitly for every conceivable unexpected exception. We therefore recommend that unexpected exceptions are handled generically and only in the toplevel procedure (namely the one thats specified in the URL). Thus a child procedure (namely a procedure other than the top-level procedure) will often have no exception handler. If it has one, this will handle only expected exceptions. And every top-level procedure will have at least the generic handler for unexpected exceptions. What is the best action to take when handling an unexpected exception? You want to reassure the user that the site is up and running, that they can try other operations, and that the error was not their fault. Since they are bound to be frustrated, you might want to offer them some compensation, for example by providing a mailto tag with its subject pre-primed with a reference number where they can claim a reduction on their next purchase. But you do not want to show the mysterious internal error codes. However, the support engineers and developers will want this information, so you should log it invisibly for the end user. The most obvious mechanism is to use an Oracle table and to populate it with as much contextual information as you can obtain programmatically. (Examples include the timestamp, the exception code, the call stack, the values of the input parameters to the top-level procedure, and possibly the username, hostname and/or IP address of the browsing user.) The techniques to obtain this depend on conventional PL/SQL and Oraclesupplied utilities and thus go beyond the scope of this paper. Assuming that the top-level procedure is a PSP, then you can do the exception handling effectively via the <%@ page errorPage="My_Psp_Err.psp" %> declarative, writing the exception handler thus My_Psp_Err.psp:
<%@ plsql procedure="My_Psp_Err" %> <% Write_To_Error_Log; %> Our site experienced a processing error...

Write_To_Error_Log would be a hand-written PL/SQL procedure which would obtain and log the required information. Note however that a PSP which is called via the errorPage declarative cannot have parameters. Thus if you decided to write the values of the input parameters to the error log, you would need to write the exception handler within the <%%> tags thus
<% exception when others then Write_To_Error_Log ( input_param_1, ... ); PSP_For_User_Friendly_Message; %>
13

Note: Should you decide to log the input parameters to each procedure in the call stack down to where the exception is raised, you will need to write a generic exception handler in every procedure thus
<% exception when others then Write_To_Error_Log ( input_param_1, ... ); raise; %>

Only the top-level procedure would generate user-visible output.

Reuse functionality in existing PL/SQL Web-based applications


Whenever appropriate, reuse the functionality in existing applications by invoking the existing procedures, namely <% existing-procedure; %>. Your existing procedures that generate HTML content using the HTP.PRINT procedure or other PL/SQL Web Toolkit API will work properly in PSP and the HTML content produced will be inlined in the HTML content of your PSP page at the places where you invoke your existing procedures. However, watch out for unsafe inclusion of HTML content. For example, it may not be safe for a PSP page to invoke a PL/SQL stored procedure that generates a complete HTML page, including the </BODY> end-tag. The presence of the </BODY> end-tag in the middle of the page may cause the remaining part of the page not to be displayed at all in the browser. For example, bad_include.psp
<HTML><BODY> ... <!-- Begin invoke exsting procedure --> <% show_reservation_status; %> <!-- End invoke exsting procedure --> ... the rest of the page goes here ... </BODY></HTML>

and show_reservation_status will produce the following HTML output


<HTML><BODY> ... reservation status generated by show_reservation_status ... </BODY></HTML>

and the HTML output produced by bad_include.psp will look like


<HTML><BODY>
14

... <!-- Begin invoke exsting procedure --> <HTML><BODY> ... reservation status generated by show_reservation_status ... </BODY></HTML> <!-- End invoke exsting procedure --> ... the rest of the page goes here ... </BODY></HTML>

The rest of the page in bad_include.psp after the invocation of show_reservation_status will not appear in the browser. Normally, this will not be a concern to you since the procedures that you want to reuse are already being called by other "top-level" procedures that produce the whole pages and the HTML content produced by the reusable procedures being invoked is safe to be included inside some HTML content.

15

Potrebbero piacerti anche