Wednesday, March 24, 2010

How To Reference Package Variables Outside of PL/SQL

When developing with PL/SQL you may store public variables in the package specification. This has many uses, none of which I will get into for this post. The only catch in Oracle is that you can not easily reference these values in SQL statements outside of PL/SQL. The following example demonstrates this:

- Create Package Spec with Variable

CREATE OR REPLACE PACKAGE pkg_var
AS
c_my_var CONSTANT VARCHAR2 (5) := 'hello';
END pkg_var;


- Reference the variable in a SQL statement in SQL*Plus

SQL> SELECT pkg_var.c_my_var x
2 FROM DUAL;
SELECT pkg_var.c_my_var x
*
ERROR at line 1:
ORA-06553: PLS-221: 'C_MY_VAR' is not a procedure or is undefined

This results in an Oracle error.


- Try the same code, but in an block of PL/SQL

SQL> DECLARE
2 v_x VARCHAR2 (5);
3 BEGIN
4 SELECT pkg_var.c_my_var x
5 INTO v_x
6 FROM DUAL;
7
8 DBMS_OUTPUT.put_line (v_x);
9 END;
10 /
hello

PL/SQL procedure successfully completed.

As you can see this worked.


So how can we refernce package variables in a non-PL/SQL setting? I created the following function to do so. It will handle values that are of type VARCHAR2. I've also removed any spaces from the parameter (pkg_name.var_name) to ensure that no SQL injection will occur.

-- **
-- * Returns Package Variable value
-- * Note: for demo purposes I broke this function into various steps
-- *
-- * @param p_pkg_var_name fully qualified variable reference. Ex: pkg_x.var_y
-- * @return Varchar2 value
-- * @author Martin Giffy D'Souza: http://apex-smb.blogspot.com
-- **
CREATE OR REPLACE FUNCTION f_get_pkg_val_vc2 (p_pkg_var_name in varchar2)
RETURN VARCHAR2
AS
v_string VARCHAR2 (4000);
-- Full Variable Name (i.e. pkg.var)
v_var_full_name VARCHAR2 (61); -- Max of 61 chars since 30 + . + 30
BEGIN
v_var_full_name := p_pkg_var_name;
-- Remove any spaces to avoid SQL injections
v_var_full_name := REGEXP_REPLACE (v_var_full_name, '[[:space:]]', '');

EXECUTE IMMEDIATE 'begin :v_string := ' || v_var_full_name || '; end;'
USING OUT v_string;

RETURN v_string;
END f_get_pkg_val_vc2;


Now when you run in SQL*Plus you get the following:

SQL> SELECT f_get_pkg_val_vc2 ('pkg_var.c_my_var') x
2 FROM DUAL;

X
-----------------------------------------------------

hello

Thursday, February 11, 2010

How to Avoid Bot Spammers in APEX

If you've ever developed a public web application with a form on it you may notice that you may get bot spammers trying to enter information into your application.

There's a simple trick that a friend of mine, Sean Rabey, at Pump Interactive showed me which will help you reject submissions from bots.

Sean suggested that I use an input field and then hide it with CSS. Humans entering data into the form won't see the field and therefore won't enter anything into it. Bots on the other hand may try to fill out this field and can't detect whether or not it's visible in the browser. If your "special" field has data in it you can reject the submission since you know it's not a human entering the data.

Here's an example of how you can do this in APEX. You can view an example here: http://apex.oracle.com/pls/apex/f?p=20195:2900

- Create a "Dummy" item
Set "HTML Form Element Attributes" to class="hideMe"



- Configure "hideMe" style
Add the following in your application somewhere (or to a CSS file)




- Add validation to catch bot entries
Type: Exists
Validation Expression 1:

SELECT 1
FROM DUAL
WHERE :p2900_dummy IS NULL

Monday, February 1, 2010

Presenting at ODTUG Kaleidoscope 2010


I'll be presenting at ODTUG Kaleidoscope in Washington DC this year. If you haven't already signed up I suggest you do so before March 24th as they have an early bird special.

I've been to the past 2 Kaleidoscope conferences where I've learned a great deal about APEX and met some great people. It doesn't matter if you're a seasoned APEX developer or new to APEX, there's always something to learn and a lot of excellent presentations for all levels.

This year I'll be giving 2 presentations on APEX (http://www.odtugkaleidoscope.com/apex.html#dsouza)

Enhancing APEX Security: APEX has some excellent built-in configurable security features. This presentation will go over some extra functionality you can add to your APEX applications that will make it more secure both in the front-end and back-end. Primary focus will be on "enhanced session state protection" and "poor man's" VPD for Oracle XE.

How to be Creative: Using the APEX Dictionary to Create Solutions: The APEX dictionary is a very useful tool which can help enhance existing features in APEX. This presentation will cover how you can use the dictionary to resolve your problems. It will include some real-life issues and how the APEX dictionary was used to resolve them

Of course these presentations may be slightly altered based on the new features and functionality of APEX 4.0.

I look forward to seeing everyone in Washington.

Sunday, January 3, 2010

How to Automatically Remove Items and Values in APEX URLs

A while ago I wrote about how to pass multi-select lists through the URL: http://apex-smb.blogspot.com/2009/07/apex-how-to-pass-multiselect-list.html. This method works, however it will only support up to 4000 characters as a result of the functions it uses.

What happens when a user selects more than 4000 characters? You could update the processes to handle clobs etc. I took a different approach when faced with this problem. Instead of trying to update the process to handle clobs I asked: Why am I passing the values through the URL? Wouldn't it be easier if I didn't pass values through the URL?

When I first thought about this, I thought I could run a process on each page that would set the appropriate variables to the calling page. But then developers would have to look in the page branch and page processes to see what items were being passed to which pages. This sounded confusing and goes against the APEX framework. I really don't like having developers do extra work when they don't need to or go against standards already put in place.

I took a different approach to this problem. Instead of having a special page process to pass values from one page to another I was able to "intercept" the page branch and modify it before it was executed. I did this by taking the page branch and manually setting the item/value pairs, then removing them from the page branch. Here's an example of the final solution: http://apex.oracle.com/pls/otn/f?p=20195:2800.

Here are some screen shots from the demo application:

Page Branch configuration:


Passing in values via the URL (note that I'd like "X" to be "abc:def", however it's just abc)


Running the page process to manually remove the item/value pairs from the URL:


Before reviewing the code, it's important to note the following assumptions. I made these assumptions to make the code demo easier to read.

- Only 1 branch is defined on the page with no conditions etc.
- The branch only contains at most 1 clear cache
- The branch passes as most 1 item/value pair to the other page

- Create page process: Remove URL Params
- Branch Point: On Submit After Processing

Note: You could easily turn this into an application process to be run throughout your application

-- NOTE: For the purpose of this example I'm assuming the following:
-- Only 1 branch is defined with no conditions etc.
-- The branch only contains at most 1 clear cache
-- The branch passes as most 1 item/value pair to the other page

DECLARE
v_branch_action apex_application_page_branches.branch_action%TYPE;
p_app_id apex_application_page_branches.application_id%TYPE := :app_id;
p_page_id apex_application_page_branches.page_id%TYPE := :app_page_id;
v_clear_cache NUMBER;
v_item_names VARCHAR2 (4000);
v_item_values VARCHAR2 (4000);
BEGIN
-- Get branch action
-- The branch action is the URL before it is parsed by APEX
SELECT branch_action
INTO v_branch_action
FROM apex_application_page_branches
WHERE application_id = p_app_id AND page_id = p_page_id;

-- Get the clear cache options
v_clear_cache :=
SUBSTR (v_branch_action,
INSTR (v_branch_action, ':', 1, 5) + 1,
INSTR (v_branch_action, ':', 1, 6)
- INSTR (v_branch_action, ':', 1, 5)
- 1
);

-- Manually clear the cache if required
IF v_clear_cache IS NOT NULL
THEN
apex_util.clear_page_cache (p_page_id => v_clear_cache);
END IF;

-- Get Page Param values
v_item_names :=
SUBSTR (v_branch_action,
INSTR (v_branch_action, ':', 1, 6) + 1,
INSTR (v_branch_action, ':', 1, 7)
- INSTR (v_branch_action, ':', 1, 6)
- 1
);
v_item_values :=
SUBSTR (v_branch_action,
INSTR (v_branch_action, ':', 1, 7) + 1,
INSTR (v_branch_action, '&success_msg=', 1, 1)
- INSTR (v_branch_action, ':', 1, 7)
- 1
);

-- If item/value pairs exist, manually set the values
IF v_item_names IS NOT NULL
THEN
-- See: http://apex-smb.blogspot.com/2009/07/apexapplicationdosubstitutions.html for more info on apex_application.do_substitutions
apex_util.set_session_state
(p_name => v_item_names,
p_value => apex_application.do_substitutions (TRIM (v_item_values))
);
END IF;

-- Modify the branch action
-- Remove the clear cache option if applicable
IF v_clear_cache IS NOT NULL
THEN
v_branch_action :=
SUBSTR (v_branch_action, 1, INSTR (v_branch_action, ':', 1, 5))
|| SUBSTR (v_branch_action, INSTR (v_branch_action, ':', 1, 6));
END IF;

-- Remove the item name/value pairs if applicable
IF v_item_names IS NOT NULL
THEN
-- Remove item Names
v_branch_action :=
SUBSTR (v_branch_action, 1, INSTR (v_branch_action, ':', 1, 6))
|| SUBSTR (v_branch_action, INSTR (v_branch_action, ':', 1, 7));
-- Remove item values
v_branch_action :=
SUBSTR (v_branch_action, 1, INSTR (v_branch_action, ':', 1, 7))
|| SUBSTR (v_branch_action,
INSTR (v_branch_action, '&success_msg=', 1, 1)
);
END IF;

-- Set the new branch action
apex_application.g_branch_action (1) := v_branch_action;
END;

Tuesday, December 1, 2009

APEX Orphaned Application Files

If you allow end users to upload files to your APEX application you may have a lot of "orphaned" files in apex_application_files and not even realize it.

Orphaned files are files that exist in APEX_APPLICATION_FILES that are not associated with an application. This can happen for several reasons, the most common are:

  • Files uploaded in Shared Components that aren't associated to an application

  • End users uploading files then purposely keep them in APEX_APPLICATION_FILES

  • End users uploading files and an error occurs



You can easily identify orphaned files using the APEX_APPLICATION_FILES view:

SELECT *
FROM apex_application_files
WHERE flow_id = 0 -- flow_id is the same as application_id

All 3 situations listed above will result in the file uploaded with flow_id = 0. The last 2 points, files uploaded from end users, can result in files that you may no longer need. I don't recommend that you keep uploaded files in the APEX_APPLICATION_FILES view. Instead you should move them immediately to a custom table.

The main problem comes from the third point. When a user uploads a file and a validation fails. In this situation the file is uploaded to APEX_APPLICATION_FILES and then the validation fails. Even though the validation failed, the file still resides in APEX_APPLICATION_FILES. The following screen shot demonstrates this issue.


To resolve this issue, I run the following application process which automatically "tags" uploaded files with a flow_id of -1. By doing so you can run a nightly process to delete any files that have a flow_id of -1.

Application Process: AP_TAG_APEX_FILES
Sequence: -100
Point: On Submit: After Page Submission - Before Computations and Validations

-- AP_TAG_APEX_FILES
BEGIN
FOR x IN (SELECT v (item_name) item_name_value
FROM apex_application_page_items
WHERE application_id = :app_id
AND page_id = :app_page_id
AND display_as = 'File Browse...')
LOOP
UPDATE apex_application_files aaf
SET aaf.flow_id = -1
WHERE aaf.NAME = x.item_name_value;
END LOOP;
END;