Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
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.
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.
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.
%> <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
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.
... 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>
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
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;
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; %>
... <!-- 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