Monday, November 22, 2010

UCM 11g: Sourcing of metadata fields and/or How to create metadata during component installation





Just wanted to note an interesting and useful new feature of 11g: metadata can now be sourced.

It appears that this functionality is specifically designed to be leveraged by components, but perhaps this will be expanded to metadata created via the Config Manager applet in the future (or perhaps someone will create an enhancement will allow admin access to these values).

This ability is useful for any system where multiple custom, or component,  attributes are in use. The ability to track an attribute back to the component that’s leveraging it allows for better understanding of its intended purpose. This can prevent illogical hijacking of attributes which could potentially confuse programmers and users alike. On the other hand, if it is decided to share an attribute across components, this also can be detailed.

Anything that helps answer the question, “why is this here?”, is useful in my opinion.


As previously mentioned, it appears that there is no admin-level UI to modify this field. To leverage this, I’ll show how the ‘dComponentName’ property can be appended to the standard set of attribute metadata in order to use this feature. 

I’ve created a simple component to demonstrate this functionality. I’ll start by briefly reviewing the functional aspects, then I’ll go into the different features of the component.


The SenaSampleManageMetadata component is an example of a starting template one could use to create components. Some people prefer to work out of the ComponentWizard for editing, modifying, and generally managing their components. I prefer to use Eclipse and my svn to keep a history of each component I create. 

That being the case, I have a few different starting templates I use to jump-start my component building. This component has two useful maintenance features: managing of metadata & tracing information. Side note: I’m working on building an ant build file that will automate the cloning and renaming of the template files into the new template. I also would like it to auto increment the version information (<today’s date> <previous build # + 1).

The first feature will allow for the simultaneous deployment of metadata with their associated components instead of creating a component bundle or manual creation via an IQ/OQ document or implementation notes. Anything that simplifies and streamlines deployment and implementation is typically going to be a step in the right direction.

The second feature is an important step in keeping log files tidy and useful. Developers can easily enable or disable debugging output from their custom components by leveraging custom trace sections. Using custom trace sections will not only allow for easy isolation (e.g., by grepping a tail) of output, but it will ensure that I/O is limited when the trace is disabled. Think about the last project you were on where all code utilized the system trace… it’s not pretty.


Now that the reasons for the maintenance features have been defined, we can discuss how we approach these via component additions. 

The first feature is handled by leveraging a component installation filter. The ComponentWizard creates a starting class that includes a conditional structure for handling multiple filter events within the same class. I’m using the extraAfterServicesLoadInit filter as well as the SenaSampleManageMetadataComponentUninstallFilter filter.

As defined in the template, the extraAfterServicesLoadInit filter is called after the last standard activity of a server side application initialization. These events are triggered as the content server starts during the post-installation restart. This is a good place to manipulate cached data or override standard configuration data. The SenaSampleManageMetadataComponentUninstallFilter filter is called for custom un-installation steps. NOTE: Change the uninstall filter name to have your component name prefix. For example: <ComponentName>CompnentUninstallFilter. This event is triggered after confirmation of component uninstallation via the UCM admin server. I’ve modified the descriptions a bit for added clarity, but they’re basically lifted from the filter template comments.

Code added for the extraAfterServicesLoadInit filter condition:

 else if (param.equals("extraAfterServicesLoadInit"))
    {
      SystemUtils.trace("senasamplemeta", "\n\n\nSenaSampleManageMetadata: Found install filter\n\n\n");
      processMeta(loader, ws, binder, cxt, true);
    }

Code added to the SenaSampleManageMetadataComponentUninstallFilter filter condition:

else if (param.equals("SenaSampleManageMetadataComponentUninstallFilter"))
    {
      SystemUtils.trace("senasamplemeta", "\n\n\nSenaSampleManageMetadata: Found uninstall filter\n\n\n");
      processMeta(loader, ws, binder, cxt, false);
    }

The function created to handle the create/delete of metadata:

protected int processMeta(IdcExtendedLoader idcextendedloader,
      Workspace ws, DataBinder binder,
      ExecutionContext cxt, boolean isInstall)
  {
    SystemUtils.trace("senasamplemeta", "Starting to process custom meta..");

    try
    {
      1: DataResultSet dataresultset = SharedObjects
          .getTable("SenaSampleManageMetadata_Metadata");
      SystemUtils.trace("senasamplemeta", "after get table");
      if (dataresultset != null)
      {
        SystemUtils.trace("senasamplemeta", "table not null");
        dataresultset.first();
        for (; dataresultset.isRowPresent(); dataresultset.next())
        {
          2: Properties properties = dataresultset.getCurrentRowProps();
          String metaDefName = properties.getProperty("dName");
          SystemUtils.trace("senasamplemeta", "retrieved metadata name: " + metaDefName);
          SystemUtils.trace("senasamplemeta", "hasDocMetaDef: " + MetaFieldUtils.hasDocMetaDef(metaDefName));
          3: if (metaDefName == null || metaDefName.length() == 0
              || ( isInstall && MetaFieldUtils.hasDocMetaDef(metaDefName))
              || ( !isInstall && !MetaFieldUtils.hasDocMetaDef(metaDefName)))
            continue;
          try
          {
            SystemUtils.trace("senasamplemeta", "is install: " + isInstall);
            4: if (isInstall)
              MetaFieldUtils.updateMetaDataFromProps(ws, null, properties,metaDefName, true);
            else
              MetaFieldUtils.deleteMetaData(ws, cxt, metaDefName);
            SystemUtils.trace("senasamplemeta", "after Meta call");
          } catch (ServiceException serviceexception)
          {
            SystemUtils.error(serviceexception, (new StringBuilder()).append(
                "Error:").append(metaDefName).append(
                " senasamplemeta -- metadata field was not installed.").toString());
          }
        }

      }
    } catch (Exception exception)
    {
      SystemUtils.error(exception,
          "senasamplemeta: Unable to execute install filter.");
    }
    return 0;
  }

There are four main points of interest:

  1. Instead of hard-coding metadata values, they can be externalized into an UCM static table (see below)
  2. While looping over the rows in the table, this retrieves the property keysets for each row
  3. Verification check: ensure that the current metaField exists; if this is an install request, verify that the metaField doesn’t already exist; if this is an uninstall, verify that the metaField exists 
  4. Choose function based upon install/uninstall

The MetaFieldUtils helper class supplies the heavy lifting by encapsulating the COLLETION_UPDATE_META_TABLE and DEL_METADEF services.
Below is the detail of my static table:
<@table SenaSampleManageMetadata_Metadata@>
<table border=1><caption><strong>

<tr>
      <td>dsdComponentName</td>
      <td>dComponentName</td>
      <td>dsdVersion</td>
      <td>dName</td>
      <td>dCaption</td>
      <td>dType</td>
      <td>dIsRequired</td>
      <td>dIsEnabled</td>
      <td>dIsSearchable</td>
      <td>dIsOptionList</td>
      <td>dOptionListKey</td>
      <td>dOptionListType</td>
      <td>dDefaultValue</td>
      <td>dOrder</td>
      <td>dIsPlaceholderField</td>
      <td>dsdCheckFlag</td>
      <td>dsdDisableOnUninstall</td>
</tr>
<tr>
      <td>SenaSampleManageMetadata</td>
      <td>SenaSampleManageMetadata</td>
      <td>1.0.0</td>
      <td>xSampleMetaField1</td>
      <td>Sample Meta Field 1</td>
      <td>BigText</td>
      <td>0</td>
      <td>1</td>
      <td>1</td>
      <td>0</td>
      <td></td>
      <td></td>
      <td></td>
      <td>25000</td>
      <td>0</td>
      <td></td>
      <td></td>
</tr>
<tr>
      <td>SenaSampleManageMetadata</td>
      <td>SenaSampleManageMetadata</td>
      <td>1.0.0</td>
      <td>xSampleMetaField2</td>
      <td>Sample Meta Field 2</td>
      <td>BigText</td>
      <td>0</td>
      <td>1</td>
      <td>1</td>
      <td>0</td>
      <td></td>
      <td></td>
      <td></td>
      <td>25100</td>
      <td>0</td>
      <td></td>
      <td></td>
</tr>

</table>
<@end@>
After installing this component, you can verify that the metadata has been added:

Uninstalling the component will remove the attributes. Again, this is a POC, whether this is a feature you would want to implement is up to your specific requirements.

Going forward:

This code can be enhanced to include much more functionality. Different datatypes can be introduced and managed. The creation and population of views, drop-down lists (ddl), user attributes, etc can all be handled within this filter. Modification of these variables can also occur. Other possibilities include sharing an existing component attribute. You could check if the attribute exists, then modify the dComponentName attribute by appending your component name to the end of the existing string.

Values could also be maintained via component install strings and/or component configurations which would allow users to review and potentially modify during install. Component configurations can also be configured to be modifiable while the component is installed.



For the second feature, I’ve added a custom trace to the standard ddl so that it’s easily remembered and accessible by developers and admins alike. Remember, just because the initial developer that added the custom trace remembers the exact string doesn’t preclude that the next developer will remember when it comes time to debug … or, the even the initial developer just a few months down the road for that matter…



The table where the custom trace section is defined:
<@table SenaSampleManageMetadataTraceSections@>
<table border=1><caption><strong>SenaSampleManageMetadata Tracing Sections</strong></caption>
<tr>
  <td>itsSection</td>
  <td>itsDescription</td>
  <td>itsDefaultEnabled</td>
</tr>
<tr>
  <td>senasamplemeta</td>
  <td>Sena Sample Manage Metadata Tracing</td>
  <td></td>
</tr>
</table>
<@end@>
This table is merged into IdcTracingSections.


You can find the component here.


I hope you’ve found some part of this useful and perhaps even reusable.

Thanks,
-ryan

edits: 
12/17/2012: updated download link


Disclaimer: The code, opinions, and other content on this page are my own and are not necessarily representative of my employer. There is no expressed support for any code contained herein. Most code supplied within these pages has been created to proof out a specific concept or to show a specific case. No code should be considered production-ready without your own testing and validation.

Tuesday, November 9, 2010

Adding External Users as Document Reviewers – Advanced Workflow Features part II

Most enterprises today use directory services for organizing users, resources, services and other objects of the heterogeneous enterprise. LDAPs are the authentication and authorization source for the various enterprise applications.

Oracle UCM can leverage these enterprise LDAPs by configuring security providers. The users in LDAP providers are identified as EXTERNAL users by the UCM. UCM queries its internal security tables to see if a user exists and if a user does not exist then queries LDAP provider for security information.

Troy Allen, in his blog on Getting User Information - Advanced Workflow Features, provides a good insight into dynamically adding users to workflow using events. Things get little different when the users are not LOCAL users within the UCM security tables but are defined externally in an LDAP. The Oracle UCM USERSECURITYATTRIBUTES table (reference http://senasystems.blogspot.com/2010_09_01_archive.html) holds user role information only for local users. External user roles are not stored in this table.

Below is an amendment to the Getting User Information - Advanced Workflow Features blog where the users are external.

Situation: An administrator wants to create a workflow where the participating users are external users and are dynamically derived based upon particular Security Role that users belong to.

Issue: UCM does not store role information of external users.
Solution: Depending on the user requirements there are two approaches to this. Solution 1 should be adopted when the latest user and role information is required. Solution 2 should be adopted when the latest user and role information is not critical but performance is.

Solution 1
Create a custom component and retrieve user information from LDAP using a Java Service Handler. Invoke this service from the workflow entry event script. This approach provides latest up to date information however has performance impact as every document workflow item will retrieve user information from the LDAP.

Solution 2
In UCM provider configuration, you can map LDAP user attributes to UCM user attributes. UCM creates or updates an entry in the “USERS” table every time an external user logs into UCM. The mapped attributes are also stored in the “USERS” table during this process. This feature comes handy when creating advanced workflow that needs to add reviewers at run time based on user role information.

A field that carries user role information can be mapped to a UCM user attribute field and during the workflow event this mapped user attribute can be queried to determine if the user should participate in the workflow or not.

Let’s go into details of Solution 2.

  1. Add a Custom User Attribute:
    Launch User Admin applet and click on Information Fields tab.


    Click on Add to add a custom User Attribute. Enter the name of the field “UserRole”. Set appropriate flags on this screen as you would like. Ideally this should be a “view only Field” since it is managed under LDAP administration.



  2. Map LDAP Attribute to User Attribute:

    I. Create a LDAP Provider
    Launch providers page under Administration > Providers and create an LDAP provider. Use information below to create provider and map LDAP attributes to user attributes.



    This is the basic LDAP provider set up screen. Key information you need is
    LDAP server – Where is the LDAP server?
    Suffix – What is the root dn of user and group information?
    LDAP Port – 389 in most of the cases but change it based on LDAP configuration.

    II. Map User Role Information
    Add appropriate group mapping to either “cn=” or check the “Use Full Group Names” checkbox to provide full dn of the groups node.


    Note: “cn=” should be based on your directory structure and the value you have entered for “LDAP Suffix”.

    Tip: Roles defined in LDAP must exist in UCM with the same name.

    III. Map User Attributes
    Under the attribute map section, you can map ldap attribute to user attribute. Add LDAP attribute as “userrole”, select newly created user attribute under User Attribute option list and click on “Add” button.


    Note: You need to restart the server for the provider changes to take effect.

  3. Create a Custom Component
    Create a custom component that can read this user attribute and make the user role information available to the workflow entry event.

    Again, Troy Allen’s blog Getting User Information - Advanced Workflow Features provides very detailed information on how to create this component so we will not go in much detail here but only highlight the changes required.

    Add a new query to the Workflow_Example component with ID “qgetusersandmappedroles”. Add query text as “select dname, uUserRole from users”.

    (Note: The custom user attribute gets added to the Users table so we can easily fetch the same from users table. All custom attributes starts with a “u”).

    Add a new Service “UserRoleMappings” that uses the new query and creates a resultset named “UserLoop”.

    Enable the component and restart the server.

  4. Change Workflow Entry Event Script
    Now that we have mapped LDAP attributes and created custom component, we are ready to modify the workflow entry event script to use the new component. Script below will run through all users in “USERS” table and add the users that belong to “REVIEWER_ROLE” role as a reviewer of the document.
    <$xuserlist=""$>
    <$xcount=0$>
    <$executeService("UserRoleMappings")$>
    <$loop UserLoop$>
      <$if uUserRole like "REVIEWER_ROLE"$>
        <$if xcount < 1$>
          <$xuserlist=dName$>
        <$else$>
          <$xtempuserlist=xuserlist$>
          <$xuserlist="" & xtempuserlist & " , " & dName$>
        <$endif$>
        <$xcount = xcount + 1$>
      <$endif$>
    <$endloop$>
    <$wfSet("xuserlist",xuserlist)$>


Test your changes

  1. Check in a document matching your workflow criteria to test the workflow.
  2. Go to Workflow Assignments to ensure the document is in the queue.
  3. From the Actions left-hand icon, click on Workflow Info. You should see all the users internal and external that were part of “REVIEWER_ROLE” role.

Points to Note:

  • If the requirement is to have all the users irrespective of their existence in “USERS” table, participate in the workflow then solution 1 is appropriate.
  • Solution 2 expects an attribute similar to userrole in user dn that carries user role information. LDAP administrator needs to set this up if it does not exist already.