Skip navigation.

exploreopera

| Help

Sign up | Help

JOracle

Oracle's Java technologies: JDeveloper, OC4J, ADF

Improving the SRDemo Shuttle Adapter

, ,

In the ADF Developer's Guide and in the SRDemo sample application, there is a backing bean class to help populate a SelectManyShuttle component from two tables with a foreign key association.

This is already fairly flexible as you can specify the underlying value, the label and even a description attribute for each item that is retrieved. There is a level of flexibility that is missing though, and that is the ability to specify more than one attribute to serve as the label (or description).

Say you wanted to be able to assign several employees to a project using the shuttle adapter as backing for a SelectOneShuttle component. You would obviously choose the EmployeeId as the value attribute, but what attribute would you choose for the label? Either FirstName or LastName is likely to be repeated, but a combination of both (with maybe another attribute in the description to differentiate employees with the exact same name) would make it much easier to select the correct employee.

It is actually fairly simple to modify the existing shuttle backing described in the Developer's Guide. The end result of this being that you can specify the allItemsDisplayAttrName attribute in the following form:
LastName, FirstName


where any word that resolves to an attribute name in the row will be replaced by the value of that attribute, and any other charaters will stay as they are. So this example might give you a list of employees looking like this:
Abel, Ellen
Ande, Sundar
Atkinson, Mozhe
Austin, David
Baer, Hermann
Baida, Shelli
Banda, Amit
Bates, Elizabeth
Bell, Sarah
etc.


The work for this is done in the ShuttlePageBackingBeanBase class and in the getAllItems method. Before we do anything, it looks like this:
public List getAllItems() {
  if (allItems == null) {
    allItems = ADFUtils.selectItemsForIterator(allItemsIteratorName,
                                               allItemsValueAttrName,
                                               allItemsDisplayAttrName,
                                               allItemsDescriptionAttrName);
  }
  return allItems;
}


I've replaced this code completely with my own, but you could easily move it into the ADFUtils so as to make this generic for anywhere that uses the selectItemsForIterator method.

Here's the modified method:
public List getAllItems() {
  if (allItems == null) {
    allItems = new ArrayList();
    
    ArrayList attrNameList = new ArrayList();
    DCIteratorBinding iter = ADFUtils.findIterator(allItemsIteratorName);
    AttributeDef[] attrDefs = iter.getAttributeDefs();
    for (int i = 0; i < attrDefs.length; i++)
    {
      AttributeDef attrDef = attrDefs[i];
      String attrName = attrDef.getName();
      if (allItemsDisplayAttrName.matches(".*\\b" + attrName + "\\b.*") ||
          allItemsDescriptionAttrName.matches(".*\\b" + attrName + "\\b.*"))
      {
        attrNameList.add(attrName);
      }
    }
    
    for (Row r: iter.getAllRowsInRange())
    {
      for (int i = 0; i < attrNameList.size(); i++)
      {
        String attrName = (String)attrNameList.get(i);
        allItemsDisplayAttrName = allItemsDisplayAttrName.replace(attrName,
          r.getAttribute(attrName).toString());
        allItemsDescriptionAttrName = allItemsDescriptionAttrName.replace(attrName,
          r.getAttribute(attrName).toString());
      }
      
      allItems.add(new SelectItem(r.getAttribute(allItemsValueAttrName),
        allItemsDisplayAttrName,
        allItemsDescriptionAttrName));
    }
  }
  return allItems;
}


This code finds the specified iterator, then extracts the attribute definitions. From there it uses a regular expression match to find if the attribute name is used in either the display or description strings. If an attribute is found, it is stored for later use.

The code then iterates through each of the rows in the iterator and then uses the stored attribute names to do a global replace on the strings with the actual values from the current row.

I hope this is simple enough not to need an example project. If you have any questions, just pop a reply in here and I can get back to you.

How To Create a Super-Intersection View

,

When you use ADF Business Components, you can create them from database tables. This is an extremely useful feature and helps map foreign key relationships. It also means that for a many-to-many relationship with an intersection table, you can easily extract all children of a specific parent.

One use case that appears quite regularly with large database schemas is not covered by this method however. That is when you have a many-to-many relationship with more than one table inbetween them. A diagram of this situation is shown below. In this case you may want to retrieve a list of all the "grandchildren" of a "grandparent". That is, all the children of all the children of an item. In the example below, you may want to find all the books in all the collections for one library.



As it stands, in ADF you would need to use the LIBRARY_COLLECTIONS intersection to get a list of collection IDs and then apply that as a view criteria on the COLLECTION_BOOKS table (e.g. WHERE COLLECTION_ID IN (12, 23, 65, 32)). You would then have a list of the book IDs.

This is not the best solution, nor a very desireable one. A much better way would be to design a business component that represents an imaginary "super" intersection table, encompassing all three tables between LIBRARIES and BOOKS.

First you need to design an entity object to hold the needed attributes. Create a new entity object.



Call it LibraryBooks, and at first map it to the LIBRARY_COLLECTIONS intersection table. This is simply so that there is some database table that is referenced. Plus it helps in mapping the first attributes needed.



On the next step the wizard automatically adds all the columns from the selected table (in this case LIBRARY_ID and COLLECTION_ID). You want to keep all the foreign keys and discard any other columns. In this case we're keeping both columns as they are foreign keys and will help in creating a primary key later on.

At this point you will also need to add some attributes. Use the New... button rather than the New from Table... one. Add columns for the foreign keys of the COLLECTION_BOOKS table, unless they are already represented. In this case the COLLECTION_BOOKS table has 2 foreign keys: COLLECTION_ID and BOOK_ID, however COLLECTION_ID is already in the list, so we only need to add BOOK_ID.



Make sure that the Primary Key check box is selected. On the next step, make sure all the other foreign keys also have this check box selected. Basically, the IDs from each of the three main tables will be sufficient to define a primary key on this new table. You can also have any fields you need from the two intersection tables (e.g. a status column).



Then click Finish. Right-click on the entity object and select New Default View Object....



Go with the default name of LibraryBooksView. Open up the view object editor for the newly created LibraryBooksView.



Obviously the default SQL query is not going to work, since there is no column named BOOK_ID on the LIBRARY_COLLECTIONS table. To get this right, you will need to select "Expert Mode", which is a posh way of saying "do it yourself"!



The important points to note here are that the FROM clause has the two intersection tables: LIBRARY_COLLECTIONS and COLLECTION_BOOKS, and there is a WHERE clause that equates the COLLECTION_ID column on both tables. This means that a join is performed between the two tables.

I've renamed the alias for LIBRARY_COLLECTIONS from LibraryBooks to LibraryCollections so it is easier to read. We don't need the COLLECTIONS table in this query because we don't have any columns from it. If we did then it should be included. In this example I've taken the COLLECTION_ID column from the LIBRARY_COLLECTIONS table, but it could have easily come from the COLLECTION_BOOKS table instead.

Now that you have the view object representing this "super" intersection, you can use it to create associations and links with the LIBRARIES and BOOKS tables as you would with any other entity and view objects.

Have fun!

An Advanced Custom Search Form For ADF

, ,

I recently read a question on the JDeveloper forum about an advanced search. This intrigued me, and so I set about implementing this.

Required: I used the HR schema for my sample, so you need to have a connection to HR from JDeveloper to run it. The advanced search itself, however, is not tied to any particular schema and has been made to be as generic as possible.

Firsly, let me show what an example search form will look like using this method:



The first drop down box has been populated with field labels, based on a managed property that you define as a list of field names. The second drop down box has two options: Is and Is Not, and the third drop down box has four different operators: Like, Equal To, Greater Than and Less Than. To perform a search on one field you would select the field label from the first list, select whether you want the field to match or not match the parameters from the second list, select the relevant operator from the third list and enter a value in the input box. Once the search button is clicked, the associated table will update to show the results.

The other two buttons are there to add and remove a search criteria. If you click the add criteria button, another row will be added with a drop down box before it containing two items: And and Or. This allows you to select how the search method will treat the two criteria (whether the records must match both, or just one).

Here's an example of the search form with two criteria and the results shown in the table below:



Most of the work of this search form is done by a backing bean named SearchCriteriaModelAdapter. There are just a few things to set up in the faces-config.xml and on the page itself, to make it work. Before I get into the code that lies behind all this, I'll show you what set up is actually needed.

In the faces-config.xml file, you need to add a session-scoped managed bean that has the class SearchCriteriaModelAdpater. You then need to add two managed properties: searchIteratorName that contains the name of the iterator binding (in the case of my sample EmployeesSearchIterator) and searchAttrNames that contains a list of attribute names (e.g. FirstName and LastName). That's it for the bean. On the page you need to do a bit more.

<af:panelForm partialTriggers="Add Remove">
  <af:forEach items="#{searchCriteria.model}" var="criteria"
              varStatus="vs">
    <af:panelHorizontal halign="center">
      <af:objectSpacer width="50" height="10"
                       rendered="#{vs.count == 1}"/>
      <af:selectOneChoice value="#{criteria.andConjunction}"
                          rendered="#{vs.count > 1}">
        <af:selectItem label="And" value="true"/>
        <af:selectItem label="Or" value="false"/>
      </af:selectOneChoice>
      <af:selectOneChoice value="#{criteria.attrName}">
        <f:selectItems value="#{searchCriteria.attrList}"/>
      </af:selectOneChoice>
      <af:selectOneChoice value="#{criteria.notConjunction}">
        <af:selectItem label="Is" value="false"/>
        <af:selectItem label="Is Not" value="true"/>
      </af:selectOneChoice>
      <af:selectOneChoice value="#{criteria.operator}">
        <f:selectItems value="#{searchCriteria.operatorList}"/>
      </af:selectOneChoice>
      <af:inputText value="#{criteria.attrValue}"/>
    </af:panelHorizontal>
  </af:forEach>
  <f:facet name="footer">
    <h:panelGroup>
      <af:commandButton text="Add criteria"
                        actionListener="#{searchCriteria.addCriteria}"
                        id="Add" partialSubmit="true"/>
      <af:commandButton text="Remove criteria"
                        actionListener="#{searchCriteria.removeCriteria}"
                        id="Remove" partialSubmit="true"/>
      <af:commandButton text="Search"
                        actionListener="#{searchCriteria.updateView}"
                        id="Search" partialSubmit="true"/>
    </h:panelGroup>
  </f:facet>
</af:panelForm>


Above is a code sample showing what is needed to set up the search form. The managed bean is called searchCriteria in this example. There is a forEach component that is mapped to the model property of the bean. This property is a list of criteria, each of which contain values defining the search attribute name, the operator in use, the attribute value, whether it should be an and conjuction or an or conjunction and whether it should match the condition or not. Each of these properties are mapped to the values of four selectOneChoice components and an inputText component. The items in the attribute and operator selectOneChoice components are mapped to another two attributes from the bean (attrList and operatorList respectively). The first of these is constructed from the list of attribute names defined in the faces-config.xml, while the other is hard-coded in the backing bean class (this could be extended to include other operators). The three commandButton components each have a corresponding action listener method in the backing bean.

Now I'll talk about the code in the backing bean class. The SearchCriteriaModelAdapter class has two attributes that are set in the faces-config (searchIteratorName and searchAttrNames) and three others accessible from EL (model, attrList and operatorList). The operatorList is the most simple to explain - it is a list containing a few SelectItems corresponding to the different operators available. This maps directly to the selectItems component on the page. The attrList property is constructed from the list of attribute names defined in the searchAttrNames property. The code below shows how the label for each attribute is found and then added to a SelectItem instance.

public List getAttrList()
{
  if (attrList == null)
  {
    attrList = new ArrayList();
    ViewObject view = getViewObject();
    for (int i = 0; i < searchAttrNames.size(); i++)
    {
      String searchAttrName = (String) searchAttrNames.get(i);
      AttributeDef attrDef = view.findAttributeDef(searchAttrName);
      if (attrDef != null)
      {
        String label = 
          attrDef.getUIHelper().getLabel(ADFUtils.getDCBindingContainer().getLocaleContext());
        SelectItem item = new SelectItem(searchAttrName, label);
        attrList.add(item);
      }
    }
  }
  return attrList;
}


The getViewObject method uses the searchIteratorName property to find the DCBindingIterator and from that, the ViewObject. ADFUtils is a class that is distributed with the SRDemo sample and it is used here un-altered. Once this method is called once, the attrList is set up for the whole session.

The model is the main property of this class and the code below shows how it is constructed.

public List getModel()
{
  if (model == null)
  {
    model = new ArrayList();
    model.add(getBlankItem());
  }
  return model;
}

private SearchCriteriaItem getBlankItem()
{
  return new SearchCriteriaItem((String)searchAttrNames.get(0), "like", "", true, false);
}


At the start it is simply populated with a "blank" criteria. The SearchCriteriaItem class is a simple bean with five properties for storing the attribute name, the operator, the attribute value, whether to use an and conjuction or and or conjunction, and whether to use a not conjunction. A blank criteria in this case defines default values for all attributes other than the attribute value, which really is blank. This means that the top item in each of the drop down boxes will be selected, which is probably what the user wants. You could change this so that you can define which attribute name or which operator to use as the defaults. You could even define a default value to go in the input field.

The methods to add and remove a criteria row are quite simple too.

public void addCriteria(ActionEvent event)
{
  model.add(getBlankItem());
}

public void removeCriteria(ActionEvent event)
{
  model.remove(model.size() - 1);
  if (model.size() == 0) model.add(getBlankItem());
}


The removeCriteria method ensures that if the user removes the last criteria, there will always be a blank criteria put in its place.

The updateView method is called when the search button is click and it is the main method in the class.

public void updateView(ActionEvent event)
{
  ViewObject view = getViewObject();
  ViewCriteria criteria = view.createViewCriteria();
  for (int i = 0; i < model.size(); i++)
  {
    SearchCriteriaItem item = (SearchCriteriaItem)model.get(i);
    if (!item.getAttrValue().equals(""))
    {
      String attrName = item.getAttrName();
      String operator = item.getOperator();
      String attrValue = item.getAttrValue();
      ViewCriteriaRow row = criteria.createViewCriteriaRow();
      row.setAttribute(attrName, operatorMap.get(operator) + " '" + attrValue + "'");
      if (item.isAndConjunction())
      {
        if (item.isNotConjunction()) row.setConjunction(ViewCriteriaRow.VCROW_CONJ_NOT | ViewCriteriaRow.VCROW_CONJ_AND);
        else row.setConjunction(ViewCriteriaRow.VCROW_CONJ_AND);
      }
      else
      {
        if (item.isNotConjunction()) row.setConjunction(ViewCriteriaRow.VCROW_CONJ_NOT | ViewCriteriaRow.VCROW_CONJ_OR);
      }
      criteria.addElement(row);
    }
  }
  view.applyViewCriteria(criteria);
  view.clearCache();
}


Using all the values from each of the SearchCriteriaItems in the model, this method constructs a ViewCriteria object and applies it to the view of the specified iterator. The clearCache method is called so that the view and its iterator are refreshed.

Since each SearchCriteriaItem object created has default values given to it, you can make the search form as simple or as complex as you want. For example, you could simply not include drop downs for And/Or or Is/Is Not, since the default values of these are probably what most users will want (it's the default behaviour of a normal search form).



That's all there is to it. I have created a sample application which has two pages - one with the full advanced search form and the other with the simpler form described above. You can download this below.

CustomSearch.zip

Using a request wrapper seems to submit two requests

In a previous blog I described how to use a request wrapper to fool a J2EE application into believing that the user has logged in to the local login context even if they were actually authenticated using a remote service or some custom authentication method.

After having used this method for quite a while in the project I am working on, I happened upon a small snag. I was trying to put in some custom code for the prepare model stage of the ADF page lifecycle and I noticed that when a partial page rendering submit is executed, the custom code was called twice. The first time, all the page bindings were accessible and the second time, they weren't. I put in some extra debug output and managed to trace the source of the problem down to the request wrapper.

It seems that on a partial page render, the OC4J instance deals with the request using the custom request wrapper and then constructs an instance of EvermindHttpServletRequest (the OC4J internal implementation of HttpServletRequest) and submits that after the page has been rendered.

I've made a minimal example project (download here
), which uses HttpSerlvetRequestWrapper instead of a custom one which shows that it is simply a problem with the request being wrapped. The same thing occurs if you use a custom implementation of HttpServletRequest, rather than a wrapper.

So what I'm wondering is,
a. is it a bug?
b. if it isn't a bug, is there some way to stop it doing this?

PhantomLoad.zip

Showing a list of items based on the contents of another list in ADF BC

This is a query rather than a solution, although if someone works out how to do it, I'll update this post with the solution.

I have constructed a very simple example schema to illustrate my query. Here is a picture of the schema.


It has three entity tables: Employee, Department and Location, and two intersection tables: Employee_Location and Department_Location.

What I want to do is have a creation process for an employee where you first fill in the details (forname and surname), then assign them to department, then depending on that department assign them to one or more possible locations.

So each department is based in one or more locations and an employee can also be based in one or more locations, but only those where the department is based (imagine that the employee is a door-to-door salesman and the department has several areas of a town that it covers).

The first two steps are easy - the details step can be modelled using a form on the employee view object and the department assignment can be done with a list of values drop down on the department view object.

The third step is a little more complex because you can't just show a list of all locations, since some of these will not apply to the department. So my question is how do I model this using ADF Business Components?

Update: 21st March 2007
I have now been able to solve this problem thanks to a blog entry by Frank Nimphius: Working with the afselectManyListsbox (Part I). This describes how to use the ViewCriteria object to perform a search on a view using a list of values, which is exactly what I needed - thanks Frank.

Curbing the shown level of an ADF Faces menu tree

The ADF Developer's Guide (link) contains a lot of useful sections on implementing different features. One of them is how to model a dynamic menu structure (19.2 Using Dynamic Menus for Navigation).

This chapter shows how to take advantage of the af: page nodeStamp facet to achieve a multi-level menu system based on managed beans. This has three levels of menu - the top level is represented by items in a menuTabs component, the second level by items in a menuBar component and the third level by items in panelSideBar. After that, deeper levels are represented as nodes on a menuTree component inside the panelSideBar. And the great thing about this is that for the current page, all the right items in the tree are highlighted, showing the menu path.

There are two problems I see with this. The first is that at three levels, the elements in the panelSideBar are bulleted, whereas at four or more levels, the bullets disappear as the list is replaced with a menuTree. This can make the items harder to differentiate (is that one item that has gone onto two lines, or is it two separate items?).

The second problem is that you may have pages that you don't want to show up on the menu system, but you do want to see the menu path to the page. For example, I have a series of pages that allow you to create/edit an item in a list. I don't want these pages to show up in the menu system because I want the user to only have one access point to them (the create/edit buttons on the list page). I do, however, want to show the user what area of the application they are in.

For me both of these problems happened at the same level in the menu system (my create/edit pages are on the fourth level). I needed a way of curbing the shown level of the menu tree, but at the same time not sacrificing the highlighted menu path functionality.

To do this, I have added a maxLevel attribute to the MenuTreeModelAdapter and subclassed both ChildPropertyTreeModel and ViewIdPropertyMenuModel. First, the ChildPropertyTreeModel subclass.

The code for all of these classes is based on the code in the Trinidad repository.

MaxLevelChildPropertyTreeModel
(imports)
public class MaxLevelChildPropertyTreeModel extends ChildPropertyTreeModel
{
  private int maxLevel;
  
  public MaxLevelChildPropertyTreeModel(Object instance, String childProperty, int maxLevel)
  {
    super(instance, childProperty);
    this.maxLevel = maxLevel;
  }
  
  public boolean isContainer()
  {
    boolean container = super.isContainer() && (getDepth() < maxLevel);
    return container;
  }
  
  public boolean isReallyContainer()
  {
    boolean container = super.isContainer();
    return container;
  }
  
  public boolean isContainerReallyEmpty()
  {
    if (!isReallyContainer()) return true;
    
    enterContainer();
    try
    {
      int kids = getRowCount();
      if (kids < 0)
      {
        setRowIndex(0);
        return !isRowAvailable();
      }
      return (kids == 0);
    }
    finally
    {
      exitContainer();
    }
  }
}


As you can see, there is a maxLevel attribute passed in the constructor. This comes from the value in the MenuTreeModelAdapter. I have overridden the isContainer method to return false if the current depth is greater or equal to the maxLevel. This ensures that all nodes of the menu tree at that level are considered empty. This results in the menu being rendered to only maxLevel levels, which for me is 2 (where 0 is the top level).

The other two methods are for the benefit of our MaxLevelViewIdPropertyMenuModel, so that it can traverse the whole tree to find all the pages. This is so when the application requests the row key of the current page, there will be an entry in the list. It doesn't matter how long the row key is, the correct menu path will be highlighted (i.e. using a maxLevel of 2, the row key [1, 0, 3, 0, 1] will highlight [1], [1, 0] and [1, 0, 3] in the menu system.

MaxLevelViewIdPropertyMenuModel
(imports)
public class MaxLevelViewIdPropertyMenuModel extends ViewIdPropertyMenuModel
{
  private Map<Object, Object> _focusPathMap;
  
  public MaxLevelViewIdPropertyMenuModel(Object instance, String viewIdProperty) throws IntrospectionException
  {
    super(instance, viewIdProperty);
  }
  
  public void setWrappedData(Object data)
  {
    Object oldPath = getRowKey();
    
    //set the focus path map
    _focusPathMap = new HashMap<Object, Object>();
    _focusPathMap.clear();
    setRowKey(null);
    FacesContext context = FacesContext.getCurrentInstance();
    _addToMap(context, (MaxLevelChildPropertyTreeModel)data, _focusPathMap, getViewIdProperty());
    setRowKey(oldPath);
  }

  private static void _addToMap(
    FacesContext context,
    MaxLevelChildPropertyTreeModel tree,
    Map<Object, Object> focusPathMap,
    String viewIdProperty
    )
  {
    for ( int i = 0; i < tree.getRowCount(); i++)
    {
      tree.setRowIndex(i);
      if (viewIdProperty != null)
      {
        Object focusPath = tree.getRowKey();
        
        Object data = tree.getRowData();
        PropertyResolver resolver =
          context.getApplication().getPropertyResolver();
        Object viewIdObject = resolver.getValue(data, viewIdProperty);
        focusPathMap.put(viewIdObject, focusPath);
      }

      if (tree.isReallyContainer() && !tree.isContainerReallyEmpty())
      {
        tree.enterContainer();
        _addToMap(context, tree, focusPathMap, viewIdProperty);
        tree.exitContainer();
      }

    }
  }
  
  public Object getFocusRowKey()
  {
    String currentViewId = getCurrentViewId();
    Object focusRowKey = _focusPathMap.get(currentViewId);
    return focusRowKey;
  }
}


This is slightly more complex. There is nothing different with the constructor - the values are simply passed on to the superclass constructor. In the superclass constructor, the setWrappedData method is called, so we override that with our own version. This calls the _addToMap method in the class rather than the _addToMap method in the superclass. This method takes MaxLevelChildPropertyTreeModel rather than a TreeModel. This is so that we can call the isReallyContainer and isContainerReallyEmpty methods to get the real return values of each. This means that the real menu tree is mapped to the _focusPathMap attribute. We then override the getFocusRowKey to return the value from the map.

This all means that the correct row key will be return for all pages in the menu tree, even if they are not rendered.

The last thing to do is add the maxLevel attribute to MenuTreeModelAdapter and change the model classes in MenuTreeModelAdapter and MenuModelAdapter to our new classes.

MenuModelAdapter
public class MenuModelAdapter implements Serializable {
    private           String    _propertyName = null;
    private           Object    _instance = null;
    private transient MenuModel _model = null;
    private           List      _aliasList = null;

    public MenuModel getModel() throws IntrospectionException
    {
      if (_model == null)
      {
        MaxLevelViewIdPropertyMenuModel model = 
                               new MaxLevelViewIdPropertyMenuModel(getInstance(),
                                                           getViewIdProperty());
...


The important part is where the model attribute is populated with an instance of MaxLevelViewIdPropertyMenuModel.

MenuTreeModelAdapter
public class MenuTreeModelAdapter {
    private String _propertyName = null;
    private Object _instance = null;
    private transient TreeModel _model = null;
    private int _maxLevel = Integer.MAX_VALUE;

    public TreeModel getModel() throws IntrospectionException
    {
      if (_model == null)
      {
        _model = new MaxLevelChildPropertyTreeModel(getInstance(), getChildProperty(), getMaxLevel());
      }
      return _model;
    }
  
    public void setMaxLevel(int maxLevel)
    {
      this._maxLevel = maxLevel;
    }
  
    public int getMaxLevel()
    {
      return _maxLevel;
    }
...


This shows the maxLevel attribute and how _model is populated with an instance of MaxLevelChildPropertyTreeModel.

And that's pretty much it. This is set up so that there's one global maximum level, but it could be improved upon to have custom levels for each part of the menu tree, say 2 levels here and 5 levels there.

I have included a JDeveloper application which has all of this code and a few pages that demonstrated the solution. Unzip it into your JDeveloper mywork folder.
JOracle.zip

Installing Oracle MetaLink Patches Without Perl

MetaLink is a good place to solve your major problems, that is of course if your work has a MetaLink account.

If the solution to your problem is an Oracle patch, the instructions will tell you that you need perl to be able to install it. This comes with OracleAS, but not with JDeveloper or standalone OC4J.

After being told that a patch would solve a problem I had in JDeveloper embedded OC4J and the standalone OC4J, I queried MetaLink on how to install them without perl. They came back with a load of command prompt lines that were specific to the patch to install them. I decided to make a generic batch script to install any patch to any OC4J installation.

orapatch.bat
@echo off
:: Installer for Oracle Patches
:: Parameters:
:: <patch path> The absolute or relative path to the patch folder
:: <oc4j path> The absolute or relative path to an oc4j instance
:: <backup dir> The directory name to store the backups
:: e.g. orapatch C:\1234567 "C:\Program Files\JDeveloper" JDeveloper
:: or D:\7654321 D:\oc4j "Standalone OC4J"

:initial
if "%~3"=="" goto useerror

set PATCH_DIR=%~f1
set OC4J_DIR=%~f2
set BACKUP_DIR=%~3

if not exist "%PATCH_DIR%" goto patherror2
if not exist "%PATCH_DIR%\files\j2ee\home\" goto patherror2

if not exist "%OC4J_DIR%" goto patherror
if not exist "%OC4J_DIR%\j2ee\home\lib" goto patherror

if not exist "%PATCH_DIR%\backup" md "%PATCH_DIR%\backup"
if not exist "%PATCH_DIR%\backup\%BACKUP_DIR%" md "%PATCH_DIR%\backup\%BACKUP_DIR%"

if not exist "%PATCH_DIR%\temp" md "%PATCH_DIR%\temp"
set TEMP_DIR=%PATCH_DIR%\temp

if not exist "%PATCH_DIR%\files\j2ee\home\lib\oc4j-internal.jar" goto client

echo Patching oc4j-internal.jar
copy "%OC4J_DIR%\j2ee\home\lib\oc4j-internal.jar" "%TEMP_DIR%\oc4j-internal.jar" > NUL
jar -ufv0 "%TEMP_DIR%\oc4j-internal.jar" -C "%PATCH_DIR%\files\j2ee\home\lib\oc4j-internal.jar" .
copy "%OC4J_DIR%\j2ee\home\lib\oc4j-internal.jar" "%PATCH_DIR%\backup\%BACKUP_DIR%\oc4j-internal.jar.original" > NUL
copy "%TEMP_DIR%\oc4j-internal.jar" "%OC4J_DIR%\j2ee\home\lib" > NUL

:client
if not exist "%PATCH_DIR%\files\j2ee\home\oc4jclient.jar" goto finish

echo.
echo Patching oc4jclient.jar
copy "%OC4J_DIR%\j2ee\home\oc4jclient.jar" "%TEMP_DIR%\oc4jclient.jar" > NUL
jar -ufv0 "%TEMP_DIR%\oc4jclient.jar" -C "%PATCH_DIR%\files\j2ee\home\oc4jclient.jar" .
copy "%OC4J_DIR%\j2ee\home\oc4jclient.jar" "%PATCH_DIR%\backup\%BACKUP_DIR%\oc4jclient.jar.original" > NUL
copy "%TEMP_DIR%\oc4jclient.jar" "%OC4J_DIR%\j2ee\home" > NUL
goto finish

:finish
echo.
echo OC4J installation at %OC4J_DIR% patched.
echo Backed up files stored in %PATCH_DIR%\backup\%BACKUP_DIR%.
rd /S /Q "%TEMP_DIR%"
set PATCH_DIR=
set OC4J_DIR=
set BACKUP_DIR=
set TEMP_DIR=
goto done

:useerror
echo Usage: orapatch ^<patch path^> ^<oc4j path^> ^<backup dir^>
echo.
echo e.g. orapatch C:\Patches\7654321 C:\JDeveloper JDeveloper
goto done

:patherror
echo Specified path (%OC4J_DIR%) is not an OC4J installation.
goto done

:patherror2
echo Specified path (%PATCH_DIR%) is not an Oracle patch.
goto done

:done


If you copy and paste this into a new text file called orapatch.bat and then save it. You can run it from the command prompt with this command:

orapatch <patch path> <oc4j path> <backup dir>

Where <patch path> is the absolute or relative path to the extracted patch folder, <oc4j path> is the absolute or relative path to the OC4J installation and <backup dir> is the name of the directory you want the backed up files to go to.

Example:
orapatch "C:\Oracle Patches\1234567" "C:\Program Files\JDeveloper" "JDeveloper Backup"


When you run the batch file you will see that it adds some files to two jars in the OC4J installation.

All you need to do then is test that it works as it should. If for some reason it doesn't, the output at the end of the script will tell you where the original files are.

Edited 6th December 2006: Fixed a couple of bugs in the script

Programmatic Authentication for Oracle ADF Business Components

The ADF Business Components make creating and using the model layer extremely fast and easy to manage. They have several very useful features including history columns. History columns are extra auditing fields in an entity object that track changes. The best feature of this is that ADF BC is able to use the session user to populate the created by and modified by columns. However, this only works if you use declarative JAZN authentication (configured in the web.xml deployment descriptor). If you want to programmatically authenticate your users, or you use a single sign-on that passes user information to your application after authentication, the user's details are not accesible in the HTTP request and therefore ADF BC is not able to find the username.

The problem is that when an Application Module is initiated there is no authenticated user in the HTTP request. After the initiation, it doesn't matter whether the user is in the session or not, however it would also be useful to be able to use the getRemoteUser and isUserInRole methods, which will fail unless using the declarative authentication method.

The solution is quite simple. When you have authenticated the user, or retrieved the user information from an SSO implementation, put the username and roles into the session. Then create a new servlet filter for all pages (URL pattern /*), and make sure that it is called before the adfBindings and adfFaces filters, but after any filter that performs or requests authentication.

Sample web.xml
... 
<filter-mapping> 
  <filter-name>SecurityFilter</filter-name> 
  <url-pattern>/*</url-pattern> 
</filter-mapping> 
<filter-mapping> 
  <filter-name>AuthFilter</filter-name> 
  <url-pattern>/*</url-pattern> 
</filter-mapping> 
<filter-mapping> 
  <filter-name>adfBindings</filter-name> 
  <url-pattern>*.jsp</url-pattern> 
</filter-mapping> 
<filter-mapping> 
  <filter-name>adfBindings</filter-name> 
  <url-pattern>*.jspx</url-pattern> 
</filter-mapping> 
<filter-mapping> 
  <filter-name>adfFaces</filter-name> 
  <url-pattern>*.jsp</url-pattern> 
</filter-mapping> 
<filter-mapping> 
  <filter-name>adfFaces</filter-name> 
  <url-pattern>*.jspx</url-pattern> 
</filter-mapping> 
... 

Here I've called mine AuthFilter and the SecurityFilter requests authentication from an SSO provider, receives the username and roles and stores them in the session with the attribute names security.auth.user and security.auth.roles respectively. The AuthFilter looks like this:
import java.io.IOException; 
import java.util.Set; 
import javax.servlet.Filter; 
import javax.servlet.FilterChain; 
import javax.servlet.FilterConfig; 
import javax.servlet.ServletException; 
import javax.servlet.ServletRequest; 
import javax.servlet.ServletResponse; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpSession; 

public class AuthFilter implements Filter 
{ 
  private static final String SESSION_ATTR_USER =
    "security.auth.user"; 
  private static final String SESSION_ATTR_ROLES =
    "security.auth.roles"; 

  public void doFilter(ServletRequest request, 
    ServletResponse response, FilterChain fc) 
    throws IOException, ServletException 
  { 
    if (ServletRequest instanceof HttpServletRequest) 
    { 
      HttpServletRequest httpRequest = 
        (HttpServletRequest)request; 
      HttpSession session = httpRequest.getSession(); 

      if (session.getAttribute(SESSION_ATTR_USER) != null 
        && session.getAttribute(SESSION_ATTR_ROLES) != null) 
      { 
        String username = 
          (String)session.getAttribute(SESSION_ATTR_USER); 
        Set roleSet = 
          (Set)session.getAttribute(SESSION_ATTR_ROLES); 

        AuthRequestWrapper wrapper = 
          new AuthRequestWrapper(httpRequest, username, roleSet); 
        fc.doFilter(wrapper, response); 
      } 
    } 

    fc.doFilter(request, response); 
  } 

  public void init(FilterConfig filterConfig) 
  { 
  } 

  public void destroy() 
  { 
  } 
}


The main work of the filter is done through the AuthRequestWrapper, which extends javax.servlet.http.HttpServletRequestWrapper and implements the getRemoteUser, isUserInRole and getUserPrincipal methods.
import java.security.Principal; 
import java.util.Set; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletRequestWrapper; 

public class AuthRequestWrapper extends HttpServletRequestWrapper 
{ 
  private String username; 
  private Set roleSet; 
  private Principal principal; 

  public AuthRequestWrapper(HttpServletRequest request, 
    String username, Set roleSet) 
  { 
    super(request); 
    this.username = username; 
    this.roleSet = roleSet; 
    this.principal = new AuthUserPrincipal(username); 
  } 

  public String getRemoteUser() 
  { 
    return username; 
  } 

  public Principal getUserPrincipal() 
  { 
    return principal; 
  } 

  public boolean isUserInRole(String roleName) 
  { 
    return roleSet.contains(roleName); 
  } 
}


AuthUserPrincipal is a small class that implements java.security.Principal to pass the username to ADF BC.
public class AuthUserPrincipal implements java.security.Principal 
{ 
  private String username; 

  public AuthUserPrincipal(String username) 
  { 
    this.username = username; 
  } 

  public String getName() 
  { 
    return username; 
  } 
}


And that’s it. ADF BC finds the username in the request wrapper and initiates an Application Module instance with it, the history columns get populated correctly, and roles are accessible in the request for authorisation.

If, like me, you need to pass roles from the SSO to your application, I will write another post about how to modify CAS (Central Authentication Service) to do this.

Edit 5th December 2006:
Due to a request by Rashid (see comments), I'm including the SecurityFilter in this post.

My SecurityFilter class is loosely based on the CASFilter class that comes with the CAS Java client. If you take out which authentication method it uses, the part that allows AuthFilter to work is the same.

Here I've highlighted the parts of the code that are specific to my AuthFilter implementation.

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class SecurityFilter implements Filter
{ 
/* START - AuthFilter specific */
  private static final String SESSION_ATTR_USER =
    "security.auth.user"; 
  private static final String SESSION_ATTR_ROLES =
    "security.auth.roles"; 
/* END - Auth Filter specific */
  private static final String SESSION_ATTR_RECEIPT =
    "security.auth.receipt";

  private String loginUrl = null;
  private String validateUrl = null;

  public void init(FilterConfig filterConfig)
    throws ServletException
  {
    loginUrl = filterConfig.getInitParameter("security.auth.loginUrl");
    validateUrl = filterConfig.getInitParameter("security.auth.validateUrl");
          
    if (validateUrl == null) throw new ServletException("validateUrl parameter must be set.");
    if (loginUrl == null) throw new ServletException("loginUrl parameter must be set.");
  }

  public void destroy()
  {
  }
  
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain fc)
    throws IOException, ServletException
  {
    if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse))
    {
      throw new ServletException("SecurityFilter only accepts HTTP requests");
    }
    
    HttpServletRequest httpRequest = (HttpServletRequest)request;    
    HttpServletResponse httpResponse = (HttpServletResponse)response;
    HttpSession session = httpRequest.getSession();
    
/* START - CAS specific */
    AuthReceipt receipt = (AuthReceipt)session.getAttribute(Constants.SESSION_ATTR_RECEIPT);
    if (receipt != null)
    {
      fc.doFilter(request, response);
      return;
    }
    
    String ticket = request.getParameter("ticket");
    if (ticket == null || ticket.equals(""))
    {
      redirectToSSO(httpRequest, httpResponse);
      return;
    }
    
    try
    {
      receipt = getAuthReceipt(httpRequest);
    }
    catch (AuthenticationException e)
    {
      throw new ServletException(e);
    }
/* END - CAS specific
    
    if (session != null)
    {
/* START - AuthFilter specific */
      session.setAttribute(SESSION_ATTR_USER, receipt.getUsername());
      session.setAttribute(SESSION_ATTR_ROLES, receipt.getAcl());
/* END - AuthFilter specific */
      session.setAttribute(SESSION_ATTR_RECEIPT, receipt);
    }
    
    fc.doFilter(request, response);
  }
  
/* REST - CAS specific */
  private AuthReceipt getAuthReceipt(HttpServletRequest request)
    throws ServletException, AuthenticationException
  {
    TicketValidator tv = null;
    tv = new TicketValidator();
    tv.setValidateUrl(validateUrl);
    tv.setServiceTicket(request.getParameter("ticket"));
    tv.setService(getService(request));
    return AuthReceipt.getReceipt(tv);
  }
  
  private void redirectToSSO(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
  {
    String ssoLoginUrl = loginUrl + "?service=" + getService(request);
    response.sendRedirect(ssoLoginUrl);
  }
  
  private String getService(HttpServletRequest request)
  {
    String serverName = request.getLocalName() + ":" + request.getLocalPort();
    String service = "http://" + serverName + request.getRequestURI();
    
    if (request.getQueryString() != null)
    {
      String queryString = request.getQueryString();
      int ticketIndex = queryString.indexOf("&ticket=");
      
      if (ticketIndex == -1)
      {
        service += "?" + queryString;
      }
      else
      {
        service += "?" + queryString.substring(0, ticketIndex);
      }
    }
    return service;
  }
}


The main bit is where you add the session variables, the rest is how you get those values in the first place, which depends on your SSO or custom authentication implementation.

Oracle ADF: Adding/deleting entries in a many-to-many relationship

I am using JDeveloper 10.1.3 and the ADF BC / ADF Model / ADF Faces / JSF technology stack.

For this question, I am using the SRDemo application as an example, specifically the many-to-many relationship between USERS and PRODUCTS (the Staff Expertise relationship), outlined in red in this schema diagram:

Users may have any number of product expertise areas and many users may have the same product expertise area. For the purposes of this example I have added two reference fields from the PRODUCT table into the ExpertiseAreas VO (Name and Description), outlined in red in this data control palette section:

I have created a page which shows the details of a user (1), along with a table showing the expertise areas the user has (3), and a table of all the products available (2), numbered on this design page: and corresponding to the numbered data controls on this data control palette section: , where 1 is rendered as an ADF Read-only Form, 2 is an ADF Read-only Table with a tableSelectMany element in the selection facet, and 3 is also an ADF Read-only Table.

What I am trying to do is have the tableSelectMany element reflect which of the products are linked to the current user in the expertise areas relationship (by having the checkbox for currently linked products checked), and when you check or uncheck a product's checkbox, it should add or remove the row in the EXPERTISE_AREAS intersection table, respectively (asume for this example that there are default values for Expertise Level and Notes).

So far I have been following the process used in section 19.8 of the ADF Developer's Guide for Forms/4GL Developers, which describes how to set up a selectManyShuttle which implements the adding and deleting functionality. So I have a Client Interface method in the Application Module, updateSkillsForCurrentStaff (described in section 10.6.7.2), and I have a selection listener for the assignment table in the backing bean for the page, which calls the updateSkillsForCurrentStaff method:

public void selectionChanged(SelectionEvent event)
{
  BindingContainer bc = getBindings();
  DCIteratorBinding productsIB = (DCIteratorBinding)bc.get("ProductListIterator");
  
  Set keys = getTable1().getSelectionState().getKeySet();
  Iterator iter = keys.iterator();
  List productIds = new Vector();
  
  while (iter.hasNext())
  {
    String product = ((Key)iter.next()).toStringFormat(true);
    productsIB.setCurrentRowWithKey(product);
    ViewRowImpl productRow = (ViewRowImpl)productsIB.getCurrentRow();
    Number productId = (Number)productRow.getAttribute("Id");
    productIds.add(productId);
  }
  
  OperationBinding ob = bc.getOperationBinding("updateSkillsForCurrentStaff");
  Map pm = ob.getParamsMap();
  pm.put("productIds", productIds);
  ob.execute();
}


All of this works, but I can't work out how to link the selection state of the assignment table to the expertise areas that are linked to the user. Also I think the method listing above must be a bit of a hack, but I don't know enough about this to know if there's an easier way of doing it.

Any help is appreciated.

My first blog and competition entry

I registered this account on the MyOpera community nearly exactly 2 years ago on the 13th of September 2003. I haven't done anything on it since then. The reason I'm suddenly creating this blog is because I've just found out that I was one of the 100 winners of the Opera 10-year anniversary creative competition. In the e-mail I received informing me of this, they asked me for a MyOpera username if I had one, which led me to look here.

I wanted to use this blog to explain my inspiration for this competition entry. Firstly I have to say that Opera is by far the best web browser in my opinion. Its simple yet attractive user interface, plethora of features and standards compliance are just some of the things have characterised Opera versions over the years.

When I first read about the competition I knew that it was something I wanted to take part in. I definitely wanted to write a poem of some sort. I am very interested in etymology (the study of the origins of words) and so my first thought was to look up 'opera' in my etymological dictionary. I already knew that it was the plural of opus so I already had in my head "a plural noun". So the first line I made was the second line of the poem: "Plural noun of Latin root, 'a creative work', its meaning doth suit". It was after this that I thought of making the first letter of each line correspond with each letter of 'opera'.

The first line, "One nine nine five in the modern era, a browser was borne with name of Opera.", was the next line I thought of. I tried desperately hard to find something sensible that would be a better rhyme than 'era' and 'opera', but the line works well I think, even with the bad rhyme. It was during thinking of this line that I decided to try and make it sound 'ye olde worlde'.

The next two lines, "Even then one could see, its splendid speede and subtletie.", and "Regarded first-rate by all who use, prime over others, only there to amuse.", came quite quickly afterwards. The second of the two could be a little confusing. The "only there to amuse" line is refering to the "others", meaning other browsers are only there to amuse. Notice the use of older looking forms of 'speed' and 'subtlety' to reinforce the old style. Inbetween these two I started to make the image, using the Monotype Corsiva font because it is the most like old script.

The final line, "Ask me not how this is true, simply try and become an Operian too.", is that line that has amused and niggled me the most. It amuses me because every time I read it I like the term 'Operian' which I invented more and more. It niggles me because I submitted it to the competition before I realised that I had left out a word. The last part should read, "simply try *it* and become and Operian too."

Any way - I must get back to doing some work. I hope you enjoy reading my entry as much as I enjoyed making it.
July 2008
SMTWTFS
June 2008August 2008
12345
6789101112
13141516171819
20212223242526
2728293031