Monday, March 20, 2017

Triggers, Jobs, Events and Queues - Part 1

A question in the OTN SQLand PL/SQL Forum got me to thinking about database triggers and the correct way to implement some requirements that lead people to misuse them.  This relates back to my earlier post on Updatable Views - Code Schema with Updatable Views.  I said:
"I must emphasize that INSTEAD OF triggers operate within the scope of the current transaction.  Any change they make to the underlying data is part of the same transaction, and all changes are either entirely committed, or entirely rolled back.  This can work to your advantage – take the example of the funds transfer that I mentioned before.  This is two INSERTs and either both are committed, or both are rolled back.  But there are things that a trigger could do that are not part of the transaction, such as calls to UTL_FILE to write a file outside of the database.  These happen even if the transaction is rolled back.  Not only that, but there are cases when the code in a trigger might actually be run more than once.  If you want to call one of the built-in packages with a name starting with “UTL”, you should probably queue an event to do it, rather than doing it directly."

Send an E-Mail on an Event

So here is a case where you might be tempted to use a trigger:  In the HR sample schema that comes with all Oracle databases, there is a table, EMPLOYEES.  Each Employee has a foreign key, DEPARTMENT_ID that points to DEPARTMENTS.  Each Department has a foreign key, MANAGER_ID, which points to the EMPLOYEES row for the Department Manager.  What we want to do is send an e-mail to the department manager whenever a new Employee is added to that Department, or an existing Employee’s DEPARTMENT_ID is changed to point to a new Department.
You say, "Fine, I’ve got a SEND_MAIL procedure that calls the UTL_SMTP built-in package to send e-mails.  I’ll just write a trigger to call it when inserting a new EMPLOYEES row with a non-null DEPARTMENT_ID, or updating it with the new DEPARTMENT_ID not equal to the old one."

CREATE OR REPLACE TRIGGER employees_manager_change_email
  AFTER INSERT OR UPDATE OF department_id
  ON employees
  FOR EACH ROW
DECLARE
  manager_first_name employees.first_name%TYPE;
  manager_last_name  employees.last_name%TYPE;
  manager_email      employees.email%TYPE;
BEGIN
  IF :NEW.department_id IS NOT NULL AND
     (INSERTING OR
      (UPDATING AND :NEW.department_id <> NVL(:OLD.department_id,0))) THEN
    SELECT first_name, last_name, email
      INTO manager_first_name, manager_last_name, manager_email
      FROM employees
     WHERE employee_id = (SELECT manager_id
                            FROM departments
                           WHERE department_id = :NEW.department_id);
    SEND_MAIL (p_from    => 'HR',
               P_to      => manager_email,
               P_subject => 'New Employee in your Department',
               P_text    => 'Please welcome '||:NEW.first_name ||
                            ' '||:NEW.last_name || ' to Department '||
                            TO_CHAR(:NEW.department_id)||' as a '||
                            :NEW.job_id ||'.');
  END IF;
END;

Ah, but it won’t work – you’ll get a mutating table exception because you can't select from the EMPLOYEES table at the same time that you are updating it.  Some people would then take the offending code and put it in a procedure and make it AUTONOMOUS transaction.  That way, since the SELECT from EMPLOYEES is in a separate transaction from the insert or update, it works.
I'm certainly in favor of making this a separate procedure, but making it autonomous is a bad idea.  Suppose the change to an Employee's department isn't committed?  The user changed his/her mind, and decided not to save the change.  The manager would get the e-mail anyway, because the e-mail transaction is no longer dependent on the insert or update transaction.  No, we need to make sure that the e-mail only gets sent if the change is committed.  Can we make the change raise some sort of notification that an e-mail should be sent, but as part of the current transaction, so it ONLY happens on COMMIT?

The Procedure

So, first, let's make this a procedure to separate the code for sending the e-mail from the trigger code.  We are still going to use a trigger to call it because we want it to send the e-mail automatically on the event, but this is not going to be an AUTONOMOUS TRANSACTION, so we can't just call it.  We'll need to pass it all the information it needs from the EMPLOYEES row that is being changed by the transaction.  Although I'm going to show this as a stand-alone procedure, my usual practice is to put all my procedures and functions in packages.
CREATE OR REPLACE PROCEDURE employee_change_manager_email (
  p_department_id IN employees.department_id%TYPE,
  p_first_name    IN employees.first_name%TYPE,
  p_last_name     IN employees.last_name%TYPE,
  p_job_id        IN employees.job_id%TYPE
  ) IS
  manager_first_name employees.first_name%TYPE;
  manager_last_name  employees.last_name%TYPE;
  manager_email      employees.email%TYPE;
BEGIN
  SELECT first_name, last_name, email
    INTO manager_first_name, manager_last_name, manager_email
    FROM employees
   WHERE employee_id = (SELECT manager_id
                          FROM departments
                         WHERE department_id = p_department_id);
  SEND_MAIL (p_from    => 'HR',
             p_to      => manager_email,
             p_subject => 'New Employee in your Department',
             p_text    => 'Please welcome '||p_first_name ||
                          ' '||p_last_name || ' to Department '||
                          TO_CHAR(p_department_id)||' as a '||
                          p_job_id ||'.');
END employee_change_manager_email;

But how to call it?  I can't just call it from the trigger, because it would still raise a mutating table error.  If I make it AUTONOMOUS, it gets called whether the transaction is committed or not.  So I need to call it in some way that depends on the transaction, but is still in some way autonomous.  Fortunately, there are two possibilities in Oracle.  We can submit a job with DBMS_JOB, or we can queue it as an event with Oracle Advanced Queuing (AQ).  DBMS_JOB is simpler, so I'll save Oracle AQ for another post.

Method 1: DBMS_JOB

We mostly think of the DBMS_JOB package as a way to schedule jobs and have them execute at particular times.  And in that role, it has mostly been supplanted by the DBMS_SCHEDULER package.  However, it is also possible to use DBMS_JOB to schedule a job to run immediately.  And a job runs in its own transaction context, independent of the transaction that scheduled it.  Best of all, the DBMS_JOB.SUBMIT procedure requires a COMMIT, which means that if you run it from a trigger, the job does not start running until the triggering transaction is committed.  DBMS_SCHEDULER does not need a commit, so it won't do for this task.  If the triggering transaction is rolled back, the job will not run.  This is exactly what we want for this requirement.  So here is our new version of the trigger:
CREATE OR REPLACE TRIGGER employees_manager_change_email
  AFTER INSERT OR UPDATE OF department_id
  ON employees
  FOR EACH ROW
DECLARE
  job_number NUMBER;
BEGIN
  IF :NEW.department_id IS NOT NULL AND
     (INSERTING OR
      (UPDATING AND :NEW.department_id <> NVL(:OLD.department_id,0))) THEN
    DBMS_JOB.SUBMIT (job  => job_number,
                     what => 'employee_change_manager_email (
  p_department_id => '||:new.department_id||',
  p_first_name    => '''||:new.first_name||''',
  p_last_name     => '''||:new.last_name||''',
  p_job_id        => '''||:new.job_id||''');'
                     );
  END IF;
END;

This is a little hard to read because the call to the procedure to send the mail is submitted as a string, and we need to concatenate the parameters into the string.  The trigger runs DBMS_JOB.SUBMIT to create and submit a job, and the missing "next_date" and "interval" parameters default to running the job immediately and only one time.  But the job doesn't actually get submitted until the COMMIT for the INSERT or UPDATE of EMPLOYEES.  When the job runs, the procedure runs in a separate transaction, so there is no mutating table.
In another post, I'll give the Oracle AQ method.

Friday, March 10, 2017

Reading Entity-Relationship Diagrams to Users

Earlier this week, I attended a meeting of the National Capital Oracle Users Group (NATCAP-OUG) at which I saw a presentation about validating your database design.  It got me to thinking about database design, why we should do it, and how to use it to improve applications.  And it all starts with an Entity-Relationship Diagram (ERD).

ERD

So, here's an ERD for the Oracle HR sample schema:http://www.oracle.com/webfolder/technetwork/tutorials/obe/db/12c/r1/odb_quickstart/images/t10101.gif
I do an ERD very early in the requirements definition phase of an application, and I refine it throughout the process.  Why?  Because this can help you discover things about your subject area and use cases that you don't get any other way.  After all, the application is designed to help your business collect information about the things they do, and to use that information to inform and govern what they do next.  Without data, your application is just a game.  Not that I have a problem with games.  And even games need to collect information about score, progress, items the player's avatar is carrying, etc.

Unfortunately, a very important part of an ERD is missing from this one - the names of the relationships.  Relationship names help you to read and understand your diagram, and particularly to read the diagram to your users.  What?  You don't read your ERDs to your users?  You SHOULD, if just because your users shouldn't be expected to read and understand the diagram for themselves.  So the rest of this post is to explain why and how to read an ERD to your users.  A lot of what I'm going to say is not original with me.  I read an article a long time ago called "Why we say Each" which has guided my database designs ever since.  Unfortunately, I have somehow lost my copy of the article, and have forgotten who wrote it.  So to the author of this article - thank you.

Reading the ERD

I'll start at the very top with the Departments entity:
  • Each Department may be staffed by one or more Employees.
  • Each Employee may work for one and only one Department.
"staffed by" and "work for" are two of the missing relationship names.   Notice that I have a name for both sides of the relationship.  This is important, because it will help us discover when we have a relationship wrong.  The word, "may" is also important because it indicates that the relationship is optional.  The use of "may" in the above two statements means that it is possible to have a Department that has no Employees, and it is possible to have an Employee who is not part of the staff of any Department.

Here you may begin to see why it is useful to read the ERD to your future application users.  I would use this as an opportunity to ask them, "Is this correct - is it true that you can have a Department with no Employees?  Is it possible to have an Employee who doesn't work for a Department?"  Finally, there are the words, "one or more" and "one and only one".  Once again, I can ask the users, "Can an Employee work for more than one Department at the same time?"  Let's continue:
  • Each Department must be managed by one and only one Employee.
  • Each Employee may be the manager of one or more Departments.
Once again, I have added "managed by" and "manager of" as relationship names and I name each side of the relationship.  I've arbitrarily decided that it is mandatory that there be a Department Manager, but I need to check this with my users.  By the way, by making this a mandatory relationship, I seem to have decided that a Department must have at least one Employee, but notice that the manager of a Department does NOT need to "work for" that Department.  In fact, since an Employee may manage one or more Departments, we can't force the manager to "work for" each Department he/she manages, UNLESS we change the relationship so that Employees may work for one or more Departments.  Finally, we'll look at the self-referencing relationship in the Employee entity:
  • Each Employee may manage one or more other Employees.
  • Each Employee may be managed by one and only one other Employee.
These relationships are fairly straight forward, except we may want to ask the users if every Employee MUST have a manager.  That would be possible, EXCEPT that there is probably at least one Employee, the President, who has no manager.  Exceptions to the rules are possible, but we need to know about them and clearly define them.  Also, see that I have added a rule in these relationship statements, the word "other".  This is to make clear that an Employee may not manage her/himself.  Or maybe that is the solution to the problem about the company President - he manages himself.  By the way, did you notice that manager_id in the Employee entity and manager_id in the Department entity are not really the same thing, even though they probably will contain employee_ids as a foreign key?  We may want to give these attributes different names.

Further Study

See what you can do with reading the rest of the diagram.  Hope this has convinced you that database design is an important part of requirements definition, and that you need to read your ERDs to your users.

Friday, March 3, 2017

A Work-Around for Security in ADF Essentials 12.2.1

In my previous post, I talked about ADF Essentials and configuring it for authentication and authorization.  And I reported a problem with the new 12.2.1 versions that raises an exception trying to initialize a UserProfile object.  I surmise that Oracle's newer versions call a facility that is only in Oracle WebLogic server, and NOT in the typical application server running ADF Essentials, notably Glassfish.  But, until Oracle fixes this (PLEASE Oracle) I have tested a workaround.  This was suggested by a comment on this post by Andrejus Baranovski (and if you are using ADF, you NEED to read Andrejus' blog - it is GREAT!)  http://andrejusb.blogspot.com/2012/10/adf-essentials-security-implementation.html.

The comment is by Ivan Melluso, who said:
I'm using JDeveloper 12.2.1, Glassfish 4.1.1, ADF Essentials 12.2.1;
I obtained
NoClassDefFoundError: Could not initialize class oracle.adf.share.security.identitymanagement.UserProfile
Then I discover the UserProfile class initialize a MDS session, that can't exists in GlassFish;

My solution:
- Decompile the class
- Rewrite it preserving package name and all public methods
- Create a new jar
- Place it in /lib directory

I restart the server 5 times to check the class conflict (same class exists in adf-share-ca.jar and my jar) and all 5 times I don't obtain the error
So the work-around is to write our own version of UserProfile and get the application to call ours instead of Oracle's original.  Here is my version:

package oracle.adf.share.security.identitymanagement;

import java.io.Serializable;

import java.security.Principal;

import java.util.HashMap;

import javax.faces.context.FacesContext;

public class UserProfile implements Serializable {
    @SuppressWarnings("compatibility:-6264967054558894560")
    private static final long serialVersionUID = 1L;

    public UserProfile() {
        super();
    }

    java.util.HashMap properties = new HashMap();


    public Object getProperty(String propName) {
        return properties.get(propName);
    }

    public void setProperty(String name, Object value) {
        properties.put(name, value);
    }

    public void setBusinessCity(String businessCity) {
        setProperty("businessCity", businessCity);
    }

    public String getBusinessCity() {
        return (String)getProperty("businessCity");
    }

    public void setBusinessCountry(String businessCountry) {
        setProperty("businessCountry", businessCountry);
    }

    public String getBusinessCountry() {
        return (String)getProperty("businessCountry");
    }

    public void setBusinessEmail(String businessEmail) {
        setProperty("businessEmail", businessEmail);
    }

    public String getBusinessEmail() {
        return (String)getProperty("businessEmail");
    }

    public void setBusinessFax(String businessFax) {
        setProperty("businessFax", businessFax);
    }

    public String getBusinessFax() {
        return (String)getProperty("businessFax");
    }

    public void setBusinessMobile(String businessMobile) {
        setProperty("businessMobile", businessMobile);
    }

    public String getBusinessMobile() {
        return (String)getProperty("businessMobile");
    }

    public void setBusinessPager(String businessPager) {
        setProperty("businessPager", businessPager);
    }

    public String getBusinessPager() {
        return (String)getProperty("businessPager");
    }

    public void setBusinessPhone(String businessPhone) {
        setProperty("businessPhone", businessPhone);
    }

    public String getBusinessPhone() {
        return (String)getProperty("businessPhone");
    }

    public void setBusinessPOBox(String businessPOBox) {
        setProperty("businessPOBox", businessPOBox);
    }

    public String getBusinessPOBox() {
        return (String)getProperty("businessPOBox");
    }

    public void setBusinessPostalAddr(String businessPostalAddr) {
        setProperty("businessPostalAddr", businessPostalAddr);
    }

    public String getBusinessPostalAddr() {
        return (String)getProperty("businessPostalAddr");
    }

    public void setBusinessPostalCode(String businessPostalCode) {
        setProperty("businessPostalCode", businessPostalCode);
    }

    public String getBusinessPostalCode() {
        return (String)getProperty("businessPostalCode");
    }

    public void setBusinessState(String businessState) {
        setProperty("businessState", businessState);
    }

    public String getBusinessState() {
        return (String)getProperty("businessState");
    }

    public void setBusinessStreet(String businessStreet) {
        setProperty("businessStreet", businessStreet);
    }

    public String getBusinessStreet() {
        return (String)getProperty("businessStreet");
    }

    public void setDateofBirth(String dateofBirth) {
        setProperty("dateofBirth", dateofBirth);
    }

    public String getDateofBirth() {
        return (String)getProperty("dateofBirth");
    }

    public void setDateofHire(String dateofHire) {
        setProperty("dateofHire", dateofHire);
    }

    public String getDateofHire() {
        return (String)getProperty("dateofHire");
    }

    public void setDefaultGroup(String defaultGroup) {
        setProperty("defaultGroup", defaultGroup);
    }

    public String getDefaultGroup() {
        return (String)getProperty("defaultGroup");
    }

    public void setDepartment(String department) {
        setProperty("department", department);
    }

    public String getDepartment() {
        return (String)getProperty("department");
    }

    public void setDepartmentNumber(String departmentNumber) {
        setProperty("departmentNumber", departmentNumber);
    }

    public String getDepartmentNumber() {
        return (String)getProperty("departmentNumber");
    }

    public void setDescription(String description) {
        setProperty("description", description);
    }

    public String getDescription() {
        return (String)getProperty("description");
    }

    public void setDisplayName(String displayName) {
        setProperty("displayName", displayName);
    }

    public String getDisplayName() {
        return (String)getProperty("displayName");
    }

    public void setEmployeeNumber(String employeeNumber) {
        setProperty("employeeNumber", employeeNumber);
    }

    public String getEmployeeNumber() {
        return (String)getProperty("employeeNumber");
    }

    public void setEmployeeType(String employeeType) {
        setProperty("employeeType", employeeType);
    }

    public String getEmployeeType() {
        return (String)getProperty("employeeType");
    }

    public void setFirstName(String firstName) {
        setProperty("firstName", firstName);
    }

    public String getFirstName() {
        return (String)getProperty("firstName");
    }

    public void setGivenName(String givenName) {
        setProperty("givenName", givenName);
    }

    public String getGivenName() {
        return (String)getProperty("givenName");
    }

    public void setGUID(String GUID) {
        setProperty("GUID", GUID);
    }

    public String getGUID() {
        return (String)getProperty("GUID");
    }

    public void setHomeAddress(String homeAddress) {
        setProperty("homeAddress", homeAddress);
    }

    public String getHomeAddress() {
        return (String)getProperty("homeAddress");
    }

    public void setHomePhone(String homePhone) {
        setProperty("homePhone", homePhone);
    }

    public String getHomePhone() {
        return (String)getProperty("homePhone");
    }

    public void setInitials(String initials) {
        setProperty("initials", initials);
    }

    public String getInitials() {
        return (String)getProperty("initials");
    }

    public void setJpegPhoto(byte[] jpegPhoto) {
        setProperty("jpegPhoto", jpegPhoto);
    }

    public byte[] getJpegPhoto() {
        return (byte[])getProperty("jpegPhoto");
    }

    public void setLastName(String lastName) {
        setProperty("lastName", lastName);
    }

    public String getLastName() {
        return (String)getProperty("lastName");
    }

    public void setMaidenName(String maidenName) {
        setProperty("maidenName", maidenName);
    }

    public String getMaidenName() {
        return (String)getProperty("maidenName");
    }

    public void setManager(String manager) {
        setProperty("manager", manager);
    }

    public String getManager() {
        return (String)getProperty("manager");
    }

    public void setMiddleName(String middleName) {
        setProperty("middleName", middleName);
    }

    public String getMiddleName() {
        return (String)getProperty("middleName");
    }

    public void setName(String name) {
        setProperty("name", name);
    }

    public String getName() {
        return (String)getProperty("name");
    }

    public void setNameSuffix(String nameSuffix) {
        setProperty("nameSuffix", nameSuffix);
    }

    public String getNameSuffix() {
        return (String)getProperty("nameSuffix");
    }

    public void setOrganization(String organization) {
        setProperty("organization", organization);
    }

    public String getOrganization() {
        return (String)getProperty("organization");
    }

    public void setOrganizationalUnit(String organizationalUnit) {
        setProperty("organizationalUnit", organizationalUnit);
    }

    public String getOrganizationalUnit() {
        return (String)getProperty("organizationalUnit");
    }

    public void setPreferredLanguage(String preferredLanguage) {
        setProperty("preferredLanguage", preferredLanguage);
    }

    public String getPreferredLanguage() {
        return (String)getProperty("preferredLanguage");
    }

    public Principal getPrincipal() {
        Principal principal =
            FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal();
        return principal;
    }

    public void setProperties(HashMap properties) {
        setProperty("properties", properties);
    }

    public HashMap getProperties() {
        return (HashMap)getProperty("properties");
    }

    public void setTimeZone(String timeZone) {
        setProperty("timeZone", timeZone);
    }

    public String getTimeZone() {
        return (String)getProperty("timeZone");
    }

    public void setTitle(String title) {
        setProperty("title", title);
    }

    public String getTitle() {
        return (String)getProperty("title");
    }

    public void setUIAccessMode(String UIAccessMode) {
        setProperty("UIAccessMode", UIAccessMode);
    }

    public String getUIAccessMode() {
        return (String)getProperty("UIAccessMode");
    }

    public void setUniqueName(String uniqueName) {
        setProperty("uniqueName", uniqueName);
    }

    public String getUniqueName() {
        return (String)getProperty("uniqueName");
    }

    public void setUserID(String userID) {
        setProperty("userID", userID);
    }

    public String getUserID() {
        return (String)getProperty("userID");
    }

    public void setUserName(String userName) {
        setProperty("userName", userName);
    }

    public String getUserName() {
        return (String)getProperty("userName");
    }

    public void setWirelessAcctNumber(String wirelessAcctNumber) {
        setProperty("wirelessAcctNumber", wirelessAcctNumber);
    }

    public String getWirelessAcctNumber() {
        return (String)getProperty("wirelessAcctNumber");
    }
   
    public void saveProfile () {
        // do nothing
    }

}

Now, how to package it, and where to put it.  We need to make sure that ours is used instead of Oracle's.  We could do what Ivan did, and package it in our own jar and add the jar to the lib folder.  Well, I'm not confident that it will always use mine instead of Oracle's - depends on what the class loader does, and I'm not really that experienced with how java and Glassfish really work.  So I got out my trusty 7-Zip and replaced UserProfile in Oracle's jar, adf-share-ca.jar with my version of the class.  This works, no more Exception.

The real question - is this legal? If I started using this in production, could Oracle come after me.  Honestly, I doubt that they would bother, but could they?  On the other hand, if Oracle would fix this, I wouldn't do it.  After all, to deploy my application, I have to deploy MY version of adf-share-ca.jar, and I have to make a new version of the jar anytime Oracle's changes.

Wednesday, March 1, 2017

Authentication and Authorization in ADF Essentials 12.2.1.x on Glassfish 4

I think I’ve said before how much I appreciate that Oracle provides a subset of ADF functionality for free, in the library called ADF Essentials.  ADF Essentials will let you write ADF applications that can be deployed to pretty much any JEE standard application server, and Glassfish is specifically supported.  I find that this is a good selling point for ADF because my clients may find Oracle WebLogic Server (WLS) a bit hefty for their needs, and may balk at the licensing and support costs for WLS.

One of the problems with the early versions of ADF Essentials is that they are intended for Glassfish 3.1.2 and may not work properly with the latest version, Glassfish 4.1.1.  So I was delighted when ADF 12.2.1.0 was released, and included ADF Essentials from the beginning.  And it was certified for Glassfish 4.  I set to work, and soon I had my standard test application on the HR sample schema working in Glassfish 4.1.1 with ADF Essentials 12.2.1.1.  It ran just as well on Glassfish as it did on the Integrated WLS, maybe even as little faster.  Glassfish 4.1.1 has a few bugs and quirks that I wish would be fixed, and I’m sorry to say that Oracle is phasing out its support for the Open Source Glassfish project – so members of the community are going to have to step forth to continue the work.  But generally, it works.

Now for the next stage of my test application which was adding authentication and authorization.  One of the missing pieces in the ADF Essentials subset that is part of full ADF is ADF Security.  This is missing, because it relies on some capabilities that are part of WLS, and not part of the JEE standard.  This doesn’t mean that you can’t write secure applications with ADF Essentials, but you must use the capabilities that are part of the JEE standard or a third party library like Spring Security.  I’d done this before with ADF 12.1.3 and Glassfish 3.2.1, so I had some experience with setting it up.  The steps are:
  1. Add a security realm to Glassfish.  ADF defaults to using the name, “myrealm”, but you can change this and use pretty much any name you like.  For simple testing, you might want to use the “file” ream, which will allow you to add test users and their security groups in the Glassfish admin console.  Later, you can replace this with a realm based on an LDAP directory or some database tables.
  2. Optionally, add one or more security roles in web.xml.  These roles are used for authorization to govern what users may do in your application.  If you don’t use roles, you’ll have to authorize by username, so I always do this.
  3. Add one or more security constraints in web.xml.  In the simplest configuration, a constraint will protect particular pages in the application, and allow only certain users or users with certain roles to access these pages.  It can be more complex than that, but let’s stick with the simple case.  The application will require authentication only when the user tries to access a protected page, and has not yet been authenticated.
  4. Configure authentication in web.xml with the login-config element.  This tells the application server how to get the username and password.  There are several ways to do this, and the most common way is with a login form, so you may have to add a page with the required login fields.
  5. Add a glassfish-web.xml file in WEB-INF if you don’t already have one, and use it to map the security roles in web.xml to the security groups that will be returned from your Glassfish security realm.  You may need this file anyway to map the data sources used by your application to the data sources that you added to Glassfish.
So, I did these with my test application, re-deployed it, and tried it.  It showed the login page as expected, showed the error page if I typed in an incorrect username and/or password, but when I typed in the correct username and password, I saw the following error:
javax.servlet.ServletException: Filter execution threw an exception
root cause
java.lang.NoClassDefFoundError: Could not initialize class
 oracle.adf.share.security.identitymanagement.UserProfile 

After making sure that I had made no discernible error, I posted a question in the JDeveloper and ADF Forum on the Oracle Communities - Filter exception ADF Essentials 12.2.1.1 on Glassfish 4.  There was a considerable amount of discussion, and other people reported the same problem.  Several of us did some additional testing.  One thing I immediately learned is that the application works fine in the Integrated WebLogic Server.  This points to a problem that is related to running with ADF Essentials on Glassfish.

As far as I can determine, there has been a change to the ADF Controller that is part of ADF 12.2.1 including the one that is part of ADF Essentials.  When a user authenticates, the user’s name is in the userPrincipal property of the external context of the application.  The ADF Controller is probably picking up that this is not null and not equal “anonymous” and deciding that it needs to initialize a UserProfile for the user.  The library with this class is adf-share-ca.jar which IS part of ADF Essentials, but the constructor for this class must be calling something that is unique to WLS, and not available in Glassfish.  So it fails.  It does not matter how the external context property is set.  I tried it with Spring Security, which is an alternative to standard JEE authentication, but still does set the external context.  It had the same problem.

As it stands now, unless the ADF developers listen to our pleas to fix this, it is not possible to use authentication and authorization in an ADF Essentials 12.2.1.x application.  At least, it is not possible with an out-of-the-box deployment of the ADF Essentials libraries.  In my next post, I will detail a work-around.