Showing posts with label Apex. Show all posts
Showing posts with label Apex. Show all posts

Thursday, December 19, 2024

Accessing values stored in a global context from APEX

Have you ever tried, within an APEX application, to retrieve a value from a user-defined context accessed globally using function SYS_CONTEXT ?
Chances are that you will only retrieve NULLs.

After some testing, following a series of inexplicable results, a bell rang in my head pointing to something dealing with a few required values that a procedure launched from an APEX application needed in order to process the data.

It turns out that APEX is setting the CLIENT_IDENTIFIER value in USERENV, that is the value that you can retrieve using function SYS_CONTEXT('USERENV', 'CLIENT_IDENTIFIER').
If you have a context defined as ACCESSED GLOBALLY, there are some rules explained in the documentation about what you could get given certain conditions (link for Oracle 19c).

At the end of the discussion, the documentation says that if you are getting NULLs, then you might have a problem with the CLIENT_IDENTIFIER and you need to clear it using:
DBMS_SESSION.CLEAR_IDENTIFIER;

It turned out that after clearing the CLIENT_IDENTIFIER, the missing values started finally to appear because, indeed, APEX is setting the CLIENT_IDENTIFIER with a value composed like this:
<APP_USER>:<APP_SESSION>.

Now, in my case it was sound and safe to clear the CLIENT_IDENTIFIER because my process was running in background and the associated Oracle session is closed when the job terminates, but if you need to retrieve these values as part of an interactive process run inside an APEX page, I'd say that you would be better off creating a wrapper function for SYS_CONTEXT where you :

  1. save the current CLIENT_IDENTIFIER in a variable
  2. clear it using DBMS_SESSION.CLEAR_IDENTIFIER
  3. assign the desired value retrieved from the global context to another variable
  4. restore the original CLIENT_IDENTIFIER using DBMS_SESSION.SET_IDENTIFIER
  5. return the desired value.
I didn't test this suggestion yet as I am busy with other urgent stuff, so caveat emptor!

Thursday, November 14, 2024

APEX tip of the day: translate tooltip when TITLE = "some text" is added to the link attributes of a IR report column

The "link attributes" of an interactive report allows a developer to specify additional attributes for a column displayed as a link in a interactive report.

A tooltip that will be displayed when a user hovers over the link text with the pointer can be specified using the attribute TITLE, for instance: TITLE="open page xyz".

This column attribute however is not picked up by the translation process of APEX, so it's not something that can be be found in the translation repository.

An easy way to work around the problem if you need to translate the text is as follows:

  1. add a new column to the IR report containing:
    APEX_LANG.MESSAGE('MSGOPENXYZ') as MSG1
  2. Make the column hidden.
  3. Update the LINK ATTRIBUTES column attribute adding TITLE="#MSG1#"
  4. Add the message MSGOPENXYZ to the message repository for the main language and for the additional languages.
  5. Repeat the steps 1-4 if you have more links needing this.
  6. Seed you application
  7. Publish the application in the additional languages
  8. Enjoy the translated tooltip.

 



Tuesday, March 05, 2024

When #OWNER# is not the OWNER you expected

Oracle provides developers with a ton of dictionary views, many of which containing a column called OWNER, basically most of the DBA* and ALL* views contain such column, with a few exceptions, for instance compare view ALL_INDEXES with ALL_IND_COLUMNS and try to figure out why the latter comes with a column called INDEX_OWNER while the former comes with a simple OWNER.

If you are developing an APEX report based on some of these views and for some reason the report comes with a link to another page where you pass the value of the OWNER column as a parameter using the #OWNER# substitution string, as APEX itself suggests if you click on the helper icon in the link builder, you won't get the OWNER you expect, because #OWNER# is an undocumented APEX substitution string that gets replaced with the name of the primary schema attached to the workspace.

I created a simple application on APEX.ORACLE.COM to show this behavior (open door credentials).

I believe that someone should update the section dedicated to the Substitution strings in the APEX Builder manual and add #OWNER# to the list and I am almost 100% sure that #OWNER# is a legitimate substitution string that gets replaced everywhere, not just accidentally in report links.

So, at the end of the day, if you need to pass the owner of something inside a link, you must rename the column, and I'd recommend avoiding the word OWNER altogether, use SCHEMA instead.


 

Thursday, February 29, 2024

APEX Master Detail side-by-side form ICON_CLASS and ICON_COLOR_CLASS explained

In case you are wondering what to put in the ICON_CLASS and ICON_COLOR_CLASS columns of the query underlying the left side column of a Master Detail side-by-side form, here is an example.

select "FILE_ID",
    null LINK_CLASS,
    apex_page.get_url(p_items => 'P210_FILE_ID', p_values => "FILE_ID") LINK,
    case 
      when x.ACTIVE = 'Y' then 'fa fa-badge-check' 
      else 'fa fa-low-vision' 
    end AS ICON_CLASS,
    null LINK_ATTR,
    case 
      when x.ACTIVE = 'Y' then 'u-color-4' 
      else 'u-color-15' 
    end AS ICON_COLOR_CLASS,
    case when coalesce(:P210_FILE_ID,'0') = x."FILE_ID"
      then 'is-active' 
      else ' '
    end LIST_CLASS,
    (substr("FILE_NAME", 1, 50)||( case when length("FILE_NAME") > 50 then '...' else '' end )) LIST_TITLE,
    (substr("REPORT_NAME", 1, 50)||( case when length("REPORT_NAME") > 50 then '...' else '' end )) LIST_TEXT,
    null LIST_BADGE
from "V_BATCH_REPORT_FILES" x
where (:P210_SEARCH is null
        or upper(x."FILE_NAME") like '%'||upper(:P210_SEARCH)||'%'
        or upper(x."REPORT_NAME") like '%'||upper(:P210_SEARCH)||'%'
    )
  and REPORT_ID = :P200_REPORT_ID
order by "REPORT_ID","FILE_SEQUENCE"

ICON_CLASS refers to the icons of the font awesome set therefore you must provide both the generic fa class and the specific icon class to get the desired icon to show up.

ICON_COLOR_CLASS refers to the set of colors defined by the classes u-color-dd whose actual value depends on selected theme.

The result for the report template Media Type (Show Icons and Show Descriptions in the template options) will be as follows:

 



Tuesday, February 20, 2024

APEX feature request, support needed!

I recently submitted the following feature request through the APEX Ideas and feature request application.

https://apex.oracle.com/ideas/FR-3624

In short, I am asking for the possibility of sub-setting the list of named (private) interactive reports basing on the value of one or more items, thus reducing the size of the list of named reports shown to the end-user as I have an application where the end-user may end up with dozens of them and each report is bound to the specific arrangement of the data contained in a generic table.

If you find it useful, I encourage you to vote for it.

Thank you!

Flavio

Tuesday, October 31, 2023

Handling XLIFF documents generated by Oracle APEX

XLIFF files have been around since the first version of Oracle APEX as I remember.

Even if you are not interested in XLIFF files, you may find interesting the code below as I am showcasing the usage of many SQL/XML functions.

Recently I wanted to spare some time while translating some of these files, so I decided to brush up my Oracle XMLDB skills that have been put on hold in the last years owing to the increasing popularity of JSON.

The goal is to take a XLIFF file exported from an APEX application and turn it into relational data for easier handling, then take the data, presumably modified for some reason, and convert them back into a XLIFF file that can be applied to the original application.

This code was created on Oracle 19c.

-- XLIFF structure
/*
<?xml version="1.0" encoding="UTF-8"?>
<!-- 
  ****************** 
  ** Source     :  100
  ** Source Lang:  en
  ** Target     :  108
  ** Target Lang:  it
  ** Filename:     f100_108_en_it.xlf
  ** Generated By: TOOLS_WKS_ADMIN
  ** Date:         26-OCT-2023 15:55:30
  ****************** 
 -->
<xliff version="1.0">
<file original="f100_108_en_it.xlf" source-language="en" target-language="it" datatype="html">
<header></header>
<body>
<trans-unit id="S-5-1-100">
<source>Welcome</source>
<target>Benvenuto</target>
</trans-unit>
...
<trans-unit id="S-458-4595199304859029-100">
<source>Username</source>
<target>Nome utente</target>
</trans-unit>
</body>
</file>
</xliff>
*/


--------------------------------------------------------
--  DDL for Table XLIFF_EXPORT_FILES
--
--  This table holds the XLIFF documents stored as XMLType
--  Created by Byte64 2023/10/27
--------------------------------------------------------

  CREATE TABLE "XLIFF_EXPORT_FILES" 
( "ID" NUMBER(*,0) GENERATED ALWAYS AS IDENTITY ORDER NOT NULL,
"FILENAME" VARCHAR2(128) NOT NULL,
"DATE_INS" DATE DEFAULT SYSDATE,
"XLIFF" "XMLTYPE" NOT NULL,
CONSTRAINT "XLIFF_EXPORT_FILES_PK" PRIMARY KEY ("ID") USING INDEX ENABLE
);
-------------------------------------------------------- -- DDL for View V_XLIFF_RELATIONAL -- -- This view transforms XML into relational data -- Created by Byte64 2023/10/27 -------------------------------------------------------- CREATE OR REPLACE FORCE EDITIONABLE VIEW "V_XLIFF_RELATIONAL" ("ID", "ITEM_ID", "SOURCE_LANGUAGE", "TARGET_LANGUAGE", "TRANS_UNIT_ID", "SOURCE_TEXT", "TARGET_TEXT", "DATE_INS") AS
select x.id, rownum as item_id, tu.source_language, tu.target_language, t."TRANS_UNIT_ID",t."SOURCE_TEXT",t."TARGET_TEXT", x.date_ins
from xliff_export_files x,
xmltable('/xliff/file' passing x.xliff
columns
source_language varchar2(30) path '@source-language',
target_language varchar2(30) path '@target-language',
trans_unit xmltype path 'body/trans-unit'
) tu,
xmltable('/trans-unit' passing tu.trans_unit
columns
trans_unit_id varchar2(128) path '@id',
source_text varchar2(4000) path 'source',
target_text varchar2(4000) path 'target'
) t
; -------------------------------------------------------- -- DDL for Table XLIFF_RELATIONAL -- -- This table will contain the data extracted with -- view V_XLIFF_RELATIONAL -- Created by Byte64 2023/10/27 -------------------------------------------------------- CREATE TABLE "XLIFF_RELATIONAL"
( "ID" NUMBER(*,0) NOT NULL,
"ITEM_ID" NUMBER(*,0) NOT NULL,
"SOURCE_LANGUAGE" VARCHAR2(30) NOT NULL,
"TARGET_LANGUAGE" VARCHAR2(30) NOT NULL,
"TRANS_UNIT_ID" VARCHAR2(128) NOT NULL,
"SOURCE_TEXT" VARCHAR2(4000) NOT NULL,
"TARGET_TEXT" VARCHAR2(4000) NOT NULL,
"DATE_INS" DATE DEFAULT SYSDATE
); -------------------------------------------------------- -- DDL for View V_XLIFF_RELATIONAL_DATA -- -- This view ensures that the elements are pulled out in -- the same original order, which could make it easier -- to spot problems by comparing the original XLIFF -- with the new one -------------------------------------------------------- CREATE OR REPLACE FORCE EDITIONABLE VIEW "V_XLIFF_RELATIONAL_DATA" ("ID", "ITEM_ID", "SOURCE_LANGUAGE", "TARGET_LANGUAGE", "TRANS_UNIT_ID", "SOURCE_TEXT", "TARGET_TEXT", "DATE_INS") AS
select "ID","ITEM_ID","SOURCE_LANGUAGE","TARGET_LANGUAGE","TRANS_UNIT_ID","SOURCE_TEXT","TARGET_TEXT","DATE_INS"
from xliff_relational
order by id, item_id
; -------------------------------------------------------- -- DDL for View V_XLIFF_OUTPUT_FILES -- -- This view extracts the relational data as XLIFF files -- one BLOB for each ID -- Created by Byte64 2023/10/27 -------------------------------------------------------- CREATE OR REPLACE FORCE EDITIONABLE VIEW "V_XLIFF_OUTPUT_FILES" ("ID", "XLIFF") AS
select
id,
xmlserialize(document
xmlconcat(
xmlcomment('generated externally'),
xmlelement("xliff", xmlattributes('1.0' as "version"),
xmlelement("file", xmlattributes(f.filename as "original",
(select x1.source_language
from xliff_relational x1
where x1.id = f.id
fetch first 1 row only) as "source-language",
(select x1.target_language
from xliff_relational x1
where x1.id = f.id
fetch first 1 row only) as "target-language"
),
xmlelement("header", null),
xmlelement("body", (select xmlagg(
xmlelement("trans-unit", xmlattributes(x.trans_unit_id as "id"),
xmlforest(x.source_text as "source", x.target_text as "target")
)
) from v_xliff_relational_data x
where x.id = f.id
)
)
)
)
) as blob
encoding 'UTF-8'
version '1.0' -- this will generate the XML header automatically
indent size = 2 -- optional indentation for pretty-print
) as xliff
from xliff_export_files f
;

Workflow:

  1. seed the application translation;
  2. export the XLIFF file generated by APEX;
  3. load the XLIFF file into XLIFF_EXPORTED_FILES;
  4. populate XLIFF_RELATIONAL selecting from V_XLIFF_RELATIONAL with the ID created during the previous step;
  5. do some work on the data;
  6. download the resulting file querying V_XLIFF_OUTPUT_FILES by ID;
  7. load the translation back into the Apex Builder;
  8. apply the translation to the application;
  9. publish the application.

Steps 3-6 could be done with the help of a simple APEX application enabling people who are not allowed to work directly in the Application Builder to perform the translation work.

Besides the normal translation work, having the data in a custom table allows to:

  • leverage existing translations by matching the source text, especially in the case of simple strings like button names, item labels taking the translations from another application;
  • perform quality checks searching for similar text and make them consistent throughout the whole application, i.e. verify that buttons, item labels, region titles have a consistent format, and so on.
  • clone a language with minor linguistic variations starting from a complete translation, i.e. English (US) vs English (CA) or Spanish (ES) vs Spanish (MX) and so on.

The XLIFF file generated by the view V_XLIFF_OUTPUT_FILES are pretty printed with an indentation value of 2 spaces, this will cause the file to be somewhat larger than the original even if no change has been made to the target elements. The clause INDENT SIZE = 2 can be removed to keep the file size smaller.

Caveat emptor!

Wednesday, March 29, 2017

Apex 5.1.1 released

The latest version of Oracle Application Express has been made available yesterday for download from Oracle as reported by Joel Kallman.

As usual a number of issues and bugs have been fixed, read more in Apex 5.1.1 patch set notes.

Enjoy.

Tuesday, November 10, 2015

Error: parsererror - SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data at line 2 column 1 of the JSON data

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.

Error: parsererror - SyntaxError: JSON.parse: 
unexpected non-whitespace character after JSON data at line 2 column 1 of the JSON data

You may get this self-explanatory error at run-time if you specified a non-existing page item in the list of items to be returned after invoking a PL/SQL procedure from within a dynamic action in Oracle Application Express.




This may easily happen if you mistyped the page item name for instance, but in my case I had completely forgotten to create the item P4_ID.
Luckily I had an epiphany before going totally nuts.

Monday, November 09, 2015

About displaying images using APEX_UTIL.GET_BLOB_FILE_SRC in non trivial situations

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.

In a perfect situation, when we need to display an inline image inside an Apex report, we might simply pick the BLOB column and apply the special formatting required in these cases, I mean that weird format mask containing a list of column attributes separated by colons where each member represents a column name in the table being queried:

SELECT ...
       dbms_lob.getlength(thumbnail) as image,
       ...
  FROM image_table...
 
The format mask specified in the column attributes of the report is something like:



Note also the call to dbms_lob.getlength that for some reason is still NOT properly documented and it is absolutely necessary to make work this type of reports otherwise you will incur into the rather obscure error message when you try to run the report:
 
report error:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error 

But as I said, sometimes we are not in a perfect situation.
We might have some rows with an image and some without images.
How do we display an alternate image in case the BLOB value is null?
 
SELECT ...
       nvl2(thumbnail,
          '<img src="'||APEX_UTIL.GET_BLOB_FILE_SRC ('P4_X',id)||'" />',
          '<img height="72" src="#IMAGE_PREFIX#1px_trans.gif" width="72" />'
       ) as image,
       ... 
 FROM image_table...

the SQL above displays the BLOB image stored in image_table only if the value is not null (it might still be an empty BLOB, but that's another story and you'd better to avoid this further annoyance), otherwise it displays a transparent image that you could replace with anything that suits better your needs, say an icon with a question mark or whatever.

Now, the problem with a report column defined in a SQL statement like this is that you can no longer use Apex's built-in report image formatting, but you need to add some additional pieces here and there.

The first requirement comes from APEX_UTIL.GET_BLOB_FILE_SRC itself: the first parameter must be the name of a page item (P4_X in the code above), type FILE BROWSE, containing the format mask specifying a list of columns, similar to the picture above, but without the Blob table.

Now, where does Apex take the name of the table if it doesn't ask me to specify one here?
It will be soon clear as you try to run the page, because it will throw the following run-time error.

Error: No corresponding DML process found for page 4
Contact your application administrator
Technical Info (only visible for developers)

    is_internal_error: true
    apex_error_code: WWV_FLOW_NATIVE_ITEM.NO_DML_PROCESS_FOUND
    component.type: APEX_APPLICATION_PAGE_ITEMS
    component.id: 2568302329371214
    component.name: P4_X

This means that Apex is expecting to retrieve the name of the table from a built-in Row Fetch process where you specify the table name and it's primary key(s).

But wait a minute, I am not using a built-in Row Fetch in this page because I am running a report.
Never mind, you can create a *fake* Row Fetch process by specifying never as a condition in the process attributes.

After adding the fake Row Fetch process, the report will magically start working.

However there is one last refinement to be done:
the FILE BROWSE page item we added is probably undesired, visually speaking.
If that is the case, you need to add something like style="display:none;" in the HTML Form Element Attributes to hide it from the user.

I wish we could have a more streamlined version of APEX_UTIL.GET_BLOB_FILE_SRC in a future release of Apex such that we could supply all the necessary parameters without having to resort to this kind of tricks.

Wednesday, October 07, 2015

About downloading files from an Apex page without using download links

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.

Yesterday I needed to download a file as the final step in processing a user request involving a tabular form. For some reason this requirement is somewhat unusual if you look at the most typical scenarios where you either download files stored in a table by means of report links or a static link where the developer can leverage Apex's built-in features for this purpose.
The main difference with my need lies in that there is no file at all to download until the user has selected one or more checkboxes in a tabular form and I need to process the tabular form after the submit in order to create the file.
In a case like this you cannot simply put the file download logic inside an after submit process because the browser will reload the page after the submit, so the file download request won't be caught by the browser. An alternative would be to create the file and then reload the page showing the download link somewhere, but I preferred to avoid the unnecessary page reload.

Luckily enough I stumbled upon Joel Kallman's posting about the more generic "file downloading" requirement and more specifically a comment he made gave me this idea.

As Joel says one can invoke the file download by means of an on-demand process that can be triggered from an Apex standard URL, so what if I try to set up the parameters in the branch section as needed?

The simple answer is: it can be done and it works well, the browser won't reload the page but it will begin the file download instead.

configuring a branch to trigger a file download request

The prerequisites for the branch are as follows:
  • a DOWNLOAD button performing a normal page submit.
  • a process that handles the tabular form data and gathers the rows involved, creating a file and storing it temporarily or permanently in a table of your own choosing, running conditionally on the pressing of the DOWNLOAD button.
  • An application item called FILEID containing the key to retrieve the file from the table above or alternatively a page item if the file download application process doesn't need to be invoked elsewhere.
  • An application process named DOWNLOAD that is invoked as shown above which reads the blob from the table using the provided key value.
A final note about this method: as I am storing the file(s) in a table I wanted to delete them automatically right away once the download was finished.
Initially I thought it would be smart to delete the relevant row from the table after the call to WPG_DOCLOAD.DOWNLOAD_FILE but to my surprise rows are not deleted from the table even if I explicitly commit the operation just after the delete statement.

It looks like as if any code after WPG_DOCLOAD.DOWNLOAD_FILE doesn't get executed at all.

Failing to find a solution for this issue, I fell back to the simple workaround of deleting the rows older than 1 day when invoking the code for creating a new file, but one may opt for a batch procedure that does the housekeeping as well.

Thursday, July 23, 2015

About using APEX_COLLECTIONS in a tabular form with INSTEAD OF triggers

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.
 
In case you dreamed of updating an Apex tabular form built on top of a view based on APEX_COLLECTIONS by means of INSTEAD OF triggers, you must be ready to grant the following rights to the schema user attached to the workspace, otherwise you'll get an ORA-01031: Insufficient privileges run-time error.
Each privilege is only required for the corresponding action, if you don't need to delete from the view you can safely omit GRANT DELETE for instance.
 
GRANT UPDATE,INSERT,DELETE ON APEX_xxxxxx.APEX_COLLECTIONS TO user_schema;

This DDL can only be executed by the DBA or by the APEX_xxxxxx user.
Probably the DBA won't like to do it for the usual security issues.

Note also that this kind of permissions are not carried over during subsequent Apex upgrades, which means that tabular forms will stop working if you forget to re-execute the grants after a major upgrade.


See message translations for ORA-01031 and search additional resources.

Tuesday, June 09, 2015

In case you wish to download Apex 5.0 documentation as a zip file

For some reason the official page for Oracle Application Express release 5.0 doesn't include a download link for its documentation library, while archived releases are all available for download from the following page:

http://www.oracle.com/technetwork/developer-tools/apex/documentation/apex-094287.html

Fortunately for us the consistency in Oracle's internal documentation library organization provides us with the following valid link for downloading the whole document set for Apex 5.0.

http://download.oracle.com/docs/cds/E59726_01.zip


Friday, January 23, 2015

Apex multilingual applications and build options

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.

Just a quick reminder for those who are devoloping Apex multilingual applications:

Build options' state is propagated to translated applications at the time of seeding, so you need to be careful when changing the state of a build option in the primary language because you may end up with translated applications having a different build option state.

For example, say you have a "production" build option attached to an application process whose goal is to run some PL/SQL code only if the application is deployed in the production environment.
Of course at some point during development you need to verify it it works so you turn on the build option (state = "include"). You seed and publish your mapped applications in order to verify if it works also when the user changes language. Then you turn off the build option (state="exclude") but you forget to seed and publish again the translated applications.
The result is you end up with a mixed situation with the primary application having "production" off and translated applications with "production" on (or viceversa) and you are probably starting to see some odd behaviour when you change language.
Even worse if you seed and publish a subset of translated applications, the situation may become really confusing.

The rule of thumb when you change a build option's state in a multilingual application is to always seed and publish immediately all translated applications for consistency.

At any rate, you can quickly assess build option states for all the translated applications by running the following query as the schema owner (or sysdba):


select application_id, build_option_name, build_option_status 
  from apex_application_build_options
 where application_id in (select b.translated_application_id 
                            from apex_application_trans_map b 
                           where b.primary_application_id = :APPLICATION_ID
                             and b.workspace = :WORKSPACE)
    or application_id = :APPLICATION_ID
 order by build_option_name, application_id;
 
 
100 production Include
134 production Include
144 production Include 

I am not quite sure if this should be regarded more as a feature or as a problem, I could not really figure out a situation where you might actually desire to have a primary application whose build options' state needs to be different from the state of its translated counterparts.

Monday, October 20, 2014

ORA-20104: create_collection_from_query ParseErr:ORA-00918: column ambiguously defined

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.
 
If you are wondering why you are getting this error message after following the example in the documentation describing procedure CREATE_COLLECTION_FROM_QUERYB2 in the APEX_COLLECTION API, the quick answer is that the sample code is flawed.

ORA-20104: create_collection_from_queryb2 Error:ORA-20104: create_collection_from_query 
ParseErr:ORA-00918: column ambiguously defined

The problem is in the missing aliases for the null columns:

Begin
    l_query := 'select empno, sal, comm, deptno, null, hiredate
              , null, null, null, null, ename, job, mgr from emp';
    APEX_COLLECTION.CREATE_COLLECTION_FROM_QUERYB2 (
        p_collection_name => 'EMPLOYEES', 
        p_query => l_query,
        p_generate_md5 => 'NO');
End;

After adding the aliases, the API call works without a hitch.
 
Begin
    l_query := 'select empno, sal, comm, deptno, null as n5, hiredate
              , null as d2, null as d3, null as d4, null as d5
              , ename, job, mgr from emp';
    APEX_COLLECTION.CREATE_COLLECTION_FROM_QUERYB2 (
        p_collection_name => 'EMPLOYEES', 
        p_query => l_query,
        p_generate_md5 => 'NO');
End;

 See message translations for ORA-00918 and search additional resources.

Tuesday, October 14, 2014

Apex substitution strings in reports, not always replaced with their values, guess why?

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.

It's amazing to you find out how easy is to take for granted certain features of Apex for the simple fact that you have been using them for a long time without paying too much attention to certain specific details.

This morning I ran a report that I rarely use in an application and to my surprise there were some missing icons. How can it be, did I forgot to copy over the images during the last upgrade?
Did I miss some flag or inadvertently change some report attribute?
Did something change in Apex 4.2 that I was not aware of?

I quickly assessed not only that the missing icons were not missing at all, but for some reason the #IMAGE_PREFIX# substitution string was not replaced by its run-time value only in this specific report.
But why if the report has been cloned from a perfectly working one with only minor changes in the column values?

As it always happens in these cases, the first thing that comes off the top of your mind is to blame the software, no matter how improbable that could be, so I started comparing the two reports with the help of the APEX dictionary views trying to understand why one was working perfectly and the other one was failing miserably, may be some little flag makes the difference, but it turned out very quickly that Apex 4.2 wasn't the culprit here :-)

Before revealing the name of the killer, let's see how Apex deals with the substitution strings.

First of all you could define a simple classic report as follows, changing the IMG column definition from "Display text (does not save state)" to "Standard Report Column" and setting the report attribute "Strip HTML" to "No".

select htf.img('#'||'IMAGE_PREFIX#/copy.gif') img from dual
union all 
select htf.img('#IMAGE_PREFIX#/copy.gif') img from dual



The report will then display like this (I am using Firefox, on different browser it could be seen differently):

So, why does the first icon appear to be broken, while the second one displays just fine?
The reason is in that Apex report engine as part of its job is replacing #IMAGE_PREFIX# in the source SQL at some stage before executing the query. This explains why the first row is broken, it's because the string #IMAGE_PREFIX# results from the concatenation of the strings that occurs only at run time, while the second row gets its value before run-time.
Of course the same applies to queries containing functions that return values containing substitution strings at run-time or even functions returning a SQL query containing functions that return values containing substitution strings at run-time (sorry for the recursive definition, but Apex gives you also this powerful possibility).

So, what to do in these cases where the #IMAGE_PREFIX# or other substitution values are generated only at the last second?
The solution is very simple and is what I initially overlooked, but it emerged very clearly when I ran the comparison query between the attributes of the two reports, because the bad one was missing one important attribute.


By specifying #IMG# in the "HTML expression" attribute, we can indeed force Apex to perform the substitution also at run-time but be sure to have set "Strip HTML" to "No" in the report definition otherwise you won't see anything.
This powerful attribute allows for the customization of the HTML code of each individual report column and gives also the possibility of referring to the current values of other columns which can be combined with HTML tags for building snippets of HTML of arbitrary complexity.

Interestingly enough the on-line help for "HTML Expression" doesn't mention the fact that Apex will perform the evaluation of built-in substitution strings as part of the processing of this piece of data, which is a life saver if you are generating HTML on-the-fly.


Another example of how useful Apex dictionary views can be when you need to find out what's wrong between two apparently identical report definitions...

Thursday, October 09, 2014

How many page views can Apex sustain when running on Oracle XE?

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.


While some people think that hosting a web site on top of Oracle XE can be hardly considered much more than a joke, I can claim instead, basing on my personal experience of the last 5 years, that for small sites it can be a perfectly reasonable choice, provided you have the technical skills required to maintain it.

Here are some figures collected last night when a new type of bot hit one of my websites with a somewhat intense flow of requests compared to the average.

Status log starting from 2014/10/08 20:00:00 to 2014/10/09 04:00:00 +00:00
TimeframeApp IDUser AgentHit count
10/08 20:00XXXXXMozilla/4.0 (compatible; ICS)3902
10/08 20:00XXXXXother agents with hits < 10046
10/08 20:00XXXXX*** Partial Hits by App ***3948
10/08 20:00
*** Partial Hits ALL Apps *** 3948
10/08 21:00XXXXXMozilla/4.0 (compatible; ICS)6381
10/08 21:00XXXXXMozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)103
10/08 21:00XXXXXother agents with hits < 10017
10/08 21:00XXXXX*** Partial Hits by App ***6501
10/08 21:00
*** Partial Hits ALL Apps *** 6501
10/08 22:00XXXXXMozilla/4.0 (compatible; ICS)8149
10/08 22:00XXXXXother agents with hits < 10039
10/08 22:00XXXXX*** Partial Hits by App ***8188
10/08 22:00
*** Partial Hits ALL Apps *** 8188
10/08 23:00XXXXXMozilla/4.0 (compatible; ICS)8360
10/08 23:00XXXXXother agents with hits < 10052
10/08 23:00XXXXX*** Partial Hits by App ***8412
10/08 23:00
*** Partial Hits ALL Apps *** 8412
10/09 00:00XXXXXMozilla/4.0 (compatible; ICS)8420
10/09 00:00XXXXXother agents with hits < 10030
10/09 00:00XXXXX*** Partial Hits by App ***8450
10/09 00:00
*** Partial Hits ALL Apps *** 8450
10/09 01:00XXXXXMozilla/4.0 (compatible; ICS)8424
10/09 01:00XXXXXother agents with hits < 10073
10/09 01:00XXXXX*** Partial Hits by App ***8497
10/09 01:00
*** Partial Hits ALL Apps *** 8497
10/09 02:00XXXXXMozilla/4.0 (compatible; ICS)8413
10/09 02:00XXXXXother agents with hits < 10084
10/09 02:00XXXXX*** Partial Hits by App ***8497
10/09 02:00
*** Partial Hits ALL Apps *** 8497
10/09 03:00XXXXXMozilla/4.0 (compatible; ICS)1071
10/09 03:00XXXXXother agents with hits < 10029
10/09 03:00XXXXX*** Partial Hits by App ***1100
10/09 03:00
*** Partial Hits ALL Apps *** 1100


*** Total Hits ***53593
App IDApex UserPage IDHit count
XXXXXANONYMOUS153180
XXXXXANONYMOUS1049
XXXXXANONYMOUS187
XXXXXANONYMOUS257
XXXXXANONYMOUS3035
XXXXXANONYMOUS3221
XXXXXANONYMOUS4063
XXXXXANONYMOUS10049
XXXXXANONYMOUS30014
XXXXXANONYMOUS400159
XXXXXANONYMOUS5005
XXXXXANONYMOUS5204
XXXXX*** Total Hits for user ANONYMOUS ***
53593
XXXXX*** Total Hits for app XXXXX ***
53593
No errors found in 53593 log entries 


The blu dots belong to the Apache web server that acts as a proxy, the orange dots are the XE11g database server with Apex 4.2.

The Apache web server runs on a T2 micro instance on Amazon EC2, while the database server is a T2 small instance.

As you see the XE machine is handling pretty well the load which amounts more or less to an average of 2.36 requests per second in the busiest period.

The CPU load of course depends on the type of page visited, in this case most of the pages were not running expensive queries.

The spike at 22:00 hours UTC recorded a daily batch job activity that starts at midnight CET.

Monday, September 29, 2014

Time for an Oracle Apex feature request - single sign-on authentication

I've just submitted a feature request because I believe that the current Application Group concept is a fairly weak one if it doesn't bring about some advantage in terms of features for the developer.

Basically what I am asking for is that one can easily implement a single sign-on authentication method across multiple applications within the same application group, pretty much like the existing authentication method that APEX itself is using when you navigate between the Application Builder, SQL Workshop and other opaque applications that constitute the framework.

 If you agree, here is the link for voting this feature request.

Thursday, September 04, 2014

The curious problem of Apex login page session expiration

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.


Did you ever notice that if you open an Apex page requiring authentication but you don't log in during a long time, it may happen that when you finally try, you are bounced back?

That happens because even the Apex login page obeys the session expiration rules defined either at the application level or, in lack thereof, at the instance level.

For example, if the default session duration is 3600 seconds and you don't log in within 3600 seconds, Apex won't accept your credentials and it will reload the login page instead.
But that is not the end of the story: since the session lifetime starts as soon as you load the login page, if the total lifetime is 3600 seconds and you log in after 3500 seconds, there will be only 100 seconds left before Apex kicks you out of the application.

You can easily see this behavior in action if you set a rather short session life time, say 60 seconds, in a test application (don't do this in a production application please).
For your convenience I created a simple demo application on apex.oracle.com

Now, you can work around the problem by setting the session duration to the maximum of 12 hours (43200 secs) in case you don't mind about it or there are no special security requirements. Setting the session duration to a value higher than 12 hours is worthless because there is scheduled job that deletes sessions older than 12 hours anyway.

Or perhaps it could make sense to use this trick: put the (in)famous meta "refresh" HTML tag in the login page header with a reasonable refresh time, so we can ensure that the user is presented always with a fresh login page.

Now, the question is: how much is a reasonable value?
I think it depends on the session lifetime you define.  For short session durations, it must be a small number, for long session durations it can be much larger, for the reason explained above, users surely don't like to be thrown out of the application after a very short time.

For instance, if the total session duration is defined to be 2 hours and you reload the login page every 10 minutes, the user in the worst case will still have 1 hour and 50 minutes available that is roughly 90% of the maximum time. On the contrary, if you refresh every hour, in the worst case there will be just 50% of the time left.

<head>
... 
<meta http-equiv="refresh" content="600;&LOGOUT_URL.">
... 

Again, you can see a second demo app showing how it works. I set the refresh to 20 seconds, in this case.

In case you don't like to hard-code a fixed value, it's possible to retrieve the maximum_session_life_seconds from Apex's dictionary view APEX_APPLICATIONS (Apex 4.0 or better only) and store it in an application item that you can put inside the page header in its substitution string form:

<head>
... 
<meta http-equiv="refresh" content="&LOGIN_REFRESH.;&LOGOUT_URL.">
... 


Application item LOGIN_REFRESH can be calculated in a before header computation that runs only in your login page and you can adjust the value depending on some algorithm, for instance 10% of the session lifetime.

It is worth noting that besides the numerical value I also added LOGOUT_URL substitution variable, because after some tests I realized that this is the only way to avoid reloading the page with the same session number.

Please note that this approach requires that you always specify the maximum session life at the application level, because as far as I know, there is no dictionary view retrieving the instance-wide value.

yes you can!

Two great ways to help us out with a minimal effort. Click on the Google Plus +1 button above or...
We appreciate your support!

latest articles