About This Article
Though the intention of this article is to help novice WDK
developers quickly get up to speed and overcome some of the
common development hurdles, it is recommended that you are
familiar with at least some of the basics, including
controls, actions, components, behavior classes, etc.
Preferably, you have already completed some customizations.
It is also highly recommended that you at least keep the WDK
Development Reference Manual on hand. Finally, though most
of the discussion applies across all of the versions, this
document has been written with WDK 5.3 specifications in
mind.
Introduction to Scoping
One of the core architectural layers of the WDK framework is
the component model. It allows WDK-application architects to
develop applications using a set of existing pre-built
components and actions, each one encapsulating specific
repository functionality. The term “scope” or “scoping”
refers to the ability of the framework to select
appropriately which component definition (the XML
configuration file) will be used for execution of a
requested component or action depending on the current
runtime conditions (the current state) of the application.
The motivating factor behind the introduction of scoping in
WDK is reusability. Of course, we should always develop
software with reusability in mind, but this concept is even
more critical to the applications developed using WDK.
Through its application layers of wdk, webcomponent, webtop
and numerous other WDK-based applications, Documentum
provides a very sophisticated set of out-of-the-box
functionality. Nevertheless, in the real world of customized
business repositories and content-driven applications, all
of this functionality would be useless without the ability
for developers to plug in their own customizations rapidly.
Very often the entire out-of-the-box Documentum application
may work just fine for 90 percent of our business; however,
the inevitable requirement “to process these types of
documents in these work flows with these versions by these
users” comes along sooner or later. Among other
WDK-development details, let’s also investigate how
component scoping can help us design a reusable application
in the most effective way
Framework Support
Though requirement specs typically are foggy and unclear,
let’s assume that we understand them somewhat. We know what
we want to happen in our WDK application and we know there
are several variables in this particular equation; we are
just not exactly sure what, where, when, and how “to scope.”
In WDK, there are several framework concepts and services
that work together. It is imperative that we become familiar
with the internal implementation of these concepts and
services before proceeding. After these are defined, it will
become clear why “menu options are still enabled,” “my
custom qualifier degrades performance,” etc. Let’s take a
quick inside look at some of the responsible players. Then,
we will be ready to come back and put the
"<scope a=b, d not c>"
part of your component configuration files under a microscope.
From a high-level viewpoint, the entire processes that start
with a request for a particular component and terminate with
the framework dispatching that component with a specific
definition file are always the same. Components and actions
are specified using the XML configuration files, and all of
these definitions are loaded into memory during application
start-up. These XML configurations use very specific terms
(or qualifications) to describe situations when the
component in question is to be applied. As the application
is running and user is interacting, the executing components
are driving and describing the current state of the
application. When the request for a component is made, the
WDK framework will interrogate and translate this runtime
state and match it to the desired definition. In WDK
terminology, the runtime state of the application is
referred to as the context. Various qualifications used to
describe that context — as well as above-mentioned
“situations” in the component XML configuration files — are
referred to as scope qualifiers. Part of the framework
responsible for appropriately interpreting the context using
scope qualifiers and dispatching a component is known as
configuration service.
Tip
The context sensitivity of the WDK UI, such as menu
items being disabled for a particular scoped action,
is merely a by-product of the above process. It is
the nature of the menu control —or any control
extending the action control — to disable itself
when a requested component definition file cannot be
found. For example, the configuration service could
not match the current context to the appropriate
definition (or the context has been successfully
matched to a definition file described as “not
defined”).
Context and the Application Runtime
As we have mentioned above, we need to monitor and keep
track of the application runtime state. This runtime state
is described using a list of name/value pairs, and in WDK,
it is typically implemented using the context. The term
“typically” is important to note here. The runtime state can
also be described by any arbitrary name/value pair; for
example, the innate qualifications describing security
permissions of the currently logged-in user.
The context is initialized and driven by all of the other
components that the user is interacting with. For example,
when a user displays contents of a specific folder and
selects a specific document, the browser-tree component is
keeping track of the location (selected folder or cabinet),
while the document-listing component is keeping track of the
details of the currently selected object (document type =
article, lifecycle state = work in progress, etc). You can
initialize the context either in Java code, as in your
component behavior class, or using controls on the JSP page,
as in your components view:
Example: Setting the context using controls in the JSP page
<dmf:datagrid ...
<dmf:datagridRow...
<dmfx:actionmultiselectcheckbox name='check'>
<dmf:argument name='type' datafield='r_object_type' />
</dmfx:actionmultiselectcheckbox>
...
</dmf:datagridRow>
...
</dmf:datagrid>
…
Tip
When you are setting the context in the JSP page,
you have to make sure that the controls are
providing enough information to do so. In the above
example, the data grid must be provided a valid
‘r_object_type’ field by the underlying data
provider (i.e., selected for by the DQL query, a
valid column in the scrollable result set, etc).
Only then the contained argument controls will be
able to extract the value and properly initialize
the context.
Example: Setting the context in the code (i.e., component
behavior, action execution, etc.)
…
// Initialize context
Context context = getContext();
context.set(“type”, “dmdeveloper_article”);
Tip
If you find yourself developing an application
similar to the Documentum Administrator, where you
are using the navigation tree on the left to launch
different “management components,” such as “user
administration,” “taxonomies administration,” and
“jobs administration,” you will probably also
require certain menu items to be context sensitive.
For example, those related to jobs should only be
visible in the job administration node. In this
case, you can simply set the context; for example,
in the onInit method of the corresponding management
component, and scope the desired job actions to the
type “job.” Note: “Job” doesn’t even have to be a
valid docbase type
Configuration Service
The configuration service comes into play immediately upon
the start-up of the application. During initialization, it
will diligently parse and crunch through all of the XML
component configuration files it encounters. It will store
these files into an internal lookup dictionary, where each
file will be uniquely identified by a specific lookup key.
Once it is finished verifying and loading all of the
definition files, the configuration service is ready to
assume the role of a hybrid MVC controller.
When a request for a component or an action comes in, the
configuration service will first translate the context and
try to create a lookup key. It will then try to match a
previously loaded definition file to that key. So what does
a lookup key look like, and why do we even need to care
about it?
We should at least be aware of several related points.
First, it can be hard to debug a WDK-application scoping
problem, especially for more complex scoping scenarios, such
as multiple scope qualifier inclusions and exclusions (i.e.,
scope type=”dm_article,” role=”not administrator”). It can
be very useful to turn on the configuration-service tracing
and see the exact keys and the matching configuration files
the service stored upon start-up.
Tip
To turn on tracing, set the CONFIGSERVICE flag to
TRUE in “trace.properties” file. Another useful
trace flags are CONTEXT and COMPONENT.
Second, if you are writing your custom qualifier, you need
to be aware of the existence of the above lookup key and the
mentioned context translation and lookup key generation
method call by the framework, as it can significantly affect
performance (see the Custom Qualifiers section for more
information).
The action definition file called “playground_actions.xml”
is scoped as follows:
Example scoped action configuration file
<scope type='dmdeveloper_article' role='administrator'>
<action id=publish_article_action>
The configuration service will generate a similar set of
information as the following:
Example Configuration Service lookup-key
…
Setting: action[id=publish_article_action];
Scope: type=dmdeveloper_article, role=administrator, clientenv=*, application=wdkplay, version=latest, location=*, entitlement=*’;
Config File: C:exportworkdirplaygroundwdkplayapp/wdkplay/config/library/playground_actions.xml
Scope Qualifiers
As mentioned above, the configuration service will translate the context and try to create the appropriate lookup key; nevertheless, there is only one configuration service out there responsible for all of the WDK application. How does it appropriately handle various qualifications used only in our custom application? In other words, a qualification such as “lifecycle state” may make sense for a compliance-manager application and a qualification such as “document type” for a web-publishing one.
The mechanisms that help the configuration service appropriately accomplish the above are known as scope qualifiers and all they speak the language of the IQualifier interface. The scope qualifiers are listed in the application configuration file, app.xml, in the order of precedence. To add your own custom qualifiers, you would also implement the IQualifier interface and add the implementing class in the above file.
Tip
Every qualifier affects the performance of your application. If you are familiar with all of the required business functionality of your WDK application (i.e., which components are being used), you should remove the un-used qualifiers (i.e., none of the components of your application are scoped using them).
Documentum provides us with the following scope qualifiers we can use out of the box:
Docbase Type Qualifier – One of the most used qualifiers, used to qualify on a specific docbase type, such as dm_document, dm_sysobject, dmdeveloper_article, and other.
<scope type="dmdeveloper_article">
User Role Qualifier – Another often-used qualifier is a specific role as
defined in the repository, such as administrator,
developer, and other similar roles..
<scope role="administrator">
Docbase Name Qualifier – This qualifier is used to qualify against different
docbases (repositories). It can be very useful when
you need to disable certain actions or components
for a specific repository. Another typical example
is customizing the doc-list component to show some
custom attributes, yet these attributes only exist
in one docbase. Using docbase qualifier, you can
easily scope several doc/object listing components.
<scope docbase="mydocbase">
Tip
You cannot use this scope, for example, to scope
those components that are applicable across the
repositories, such as, unfortunately, the menu bar.
If you need to provide a different menu for
different repositories, you will have to scope the
individual actions and mark them as “not defined.”
WDK Component Version Qualifier – When you have invested serious man-hours into a
specific component and are simply too attached to
it, yet the time has come to upgrade to the new
version of WDK base, you can bring along your old
component and scope the desired scenarios to use it
instead of using the version qualifier.
These are some of the commonly used scope qualifiers. Each
WDK application — such as Compliance Manager, Web Publisher,
etc. — will provide scope qualifiers that are useful and
relevant to that application. The entire list of available
qualifiers (no pun intended) is beyond the scope of this
article. Please consult the WDK documentation of your
application on the specifics.
In case you are not happy with the above selection and
believe that you deserve your own custom-made qualifier,
here is how to do so and what to watch out for during the
process. Indeed, implementing a custom qualifier can be very
beneficial, and completely reduce or minimize the numerous
precondition logic of your WDK application; nevertheless,
you should be aware that adding it will probably result in a
significant change of your entire architecture, and should
therefore be considered and decided upon early in the design
phase.
Custom Qualifiers and IQualifier Interface
To add a custom qualifier, you need to implement the
IQualifier interface and add the implementing class to the
app.xml
<qualifiers>
section.
Tip
It is possible that the app.xml file you are working
with does not even contain the “qualifiers” section.
Remember that even the application configuration
files are inherited, so that means that your current
application was satisfied with all of the qualifiers
from the layer below. In that case, find the app.xml
file of the application you are extending, and copy
ALL of the qualifiers and add your custom one.
The IQualifier interface is described as follows:
public interface IQualifier {
/**
* This method returns zero or more related context names used by this
* qualifier, such as “type,” “location,” “objected,” etc. It will usually
* be the same String, such as returned by the getScopeName() (see below).
* During application start-up, configuration service will call this method
* on all of the qualifiers and create an internal list of valid context
* names. You do not have to be overly concerned with the above list — just
* be aware that the configuration service needs it during various component
* dispatching, (i.e., to determine which of the arguments contained in the
* HttpRequest object need to be propagated further)
*
*/
public String[] getContextNames();
/**
* This method will return the official name of the scope the qualifier
* resolves, i.e., the x of your <scope x=’y’> component definition. When
* configuration service is loading up all the configuration files, whenever
* it encounters a scope value, it will run a check against this method. If
* you want something like <scope lifecyclestate=”draft,” one of the
* IQualifier implementations better return “lifecyclestate” in this method.
* This is why you can safely try to remove various unused qualifiers.
*
*/
public String getScopeName();
/**
* Before we discuss the remaining IQualifier methods, it is important that
* we take a step back and review couple of points. As we remember from the
* Configuration Service section, when a request for a specific component
* comes in, the configuration service is going to interrogate the context
* and create a lookup key that it can use to try and locate the appropriate
* component definition file. In order to make the lookup key, it will
* simply ask EVERY qualifier the following public String
* getScopeValue(QualifierContext context). Unfortunately, as it is
* mentioned above, this means that in order to render appropriately any
* Action Control, such as those hidden away under <dmf:menuitem> or
* <dmf:actionlink>, the configuration service will try to make the lookup
* key for ALL which are currently rendered. This means that for 20 menu
* items, the service will call getScopeValue method for EVERY qualifier. If
* the getScopeValue method implementation of your custom qualifier is
* performing anything expensive, such as fetching the object, the amount of
* work that needs to be done just to render will be significant. Therefore,
* the fetch for the object should be your last option. Instead, you should
* first try to retrieve simply the value from the passed in Context
* argument, i.e., String lifeCycleState = context.get(“lifecyclestate”).
* For this to work, of course, either your component behavior class or the
* JSP page had to initialize the context with the appropriate value for the
* lifecyclestate. Consequently, in order to take full advantage of using
* custom qualifiers, you will have to customize various driving components
* as well, such as doclist, objectlist, etc.
*/
/**
* After configuration service calls getScopeValue() and creates a lookup
* key, it will try to find a matching definition. If no match has been
* found, it will then try to “generalize” the key and “move” up the
* hierarchy. In other words, it will try to create a different, more
* general key to use for the lookup. The following methods, if applicable,
* returns the appropriate parent or alias scope values. For example, for
* the docbase type, this method returns the parent type. For a specific
* role, it would return parent role, if any. If, for example, we had a
* custom ‘lifecycle state’ qualifier — it could return the “parent”
* lifecycle state, which, for example, could be simply the previous state
* of the life cycle (if it made hierarchical sense). If you will be using
* the hierarchical qualifiers design in your application, you should
* therefore implement the following two methods. Otherwise, for the most
* common cases, you can simply return nulls.
*/
public String getParentScopeValue(String strScopeValue);
public String[] getAliasScopeValues(String strScopeValue);
}
Scoping Actions and Components
The scope of your component or action configuration file is
defined as the outer XML element:
<scope>
that wraps one or multiple configuration elements. The
general syntax is:
<scope qualifier name = qualifier value>,i.e. <scope type=dm_document>
Tip
Typically, the convention is to scope components on
an individual basis. Therefore, except for tightly
coupled components (i.e., specific locator and its
corresponding container), you should store them into
individual files. On the other hand, actions are
typically scoped together, i.e., all of the actions
applicable only to dm_sysobject should be defined in
one physical configuration file.
In addition, remember that the component can be scoped using
both multiple qualifiers as well as multiple values for that
qualifiers.
Tip
Obviously, there can be more than one matching
qualification. For example, if we had two components
— one scoped with user role as role=”administrator’
and the other component scoped with document type
qualifier, i.e., type=’dm_article’ — the order of
the qualifiers specified in the app.xml determines
which of the two valid definitions takes precedence
Finally, as mentioned above, to disable (undefine) a
particular component definition for a particular scope, use
the not defined keyword.
Tip
When changing the component scope, remember that the
configuration service needs to reload all of the
definition files and update the lookup dictionary.
You can either restart the server or visit the
following link, which will instruct the
configuration service to reload the changes:
https://your_web_server:port:/your_application/wdk/refresh.jsp
Filtering
We use scope to determine the entire definition file to be
applied for particular qualifications. The WDK framework
also gives us a higher level of granularity with the use of
filters, by which we can further decide which parts of the
above definition to use (hide or show). A very typical
example of filtering is hiding or showing contained
components in a container, depending on a specific
qualifier, such as for example object type. For the
following example, we assume that we have different types of
jobs. All types have basic informational metadata that can
be set, such as name of the job, etc. Some of them can be
scheduled to run at regular intervals, and some are supposed
to run only once. In that case, we can simply filter the
scheduler component out of the container using the following
<component id="job_info_container">
...
<contains>
<component>iim_job_info</component>
<filter type="scheduled_job">
<component>iim_job_scheduler</component>
</filter>
</contains>
...
</component>
Another common example would be to filter which component
view (JSP page) to use for a particular component, such as a
general view vs. portlet view.
Tip
You can filter ANY part of your component definition
file. If you are using your component configuration
file to store various “properties” that are later
looked up in the behavior class, or if for example,
you are passing some static arguments, you can also
filter these if there is a qualifier that fits your
needs.
For example, assume that we want to use the same create new
job component. The only difference will be the available
list of valid job types, depending on the parameter called
job group. We can accomplish the above simply by filtering
the argument that are passed in by the launch action to the
component it is launching:
<scope>
<action id="new_job">
<execution class="com.documentum.web.formext.action.LaunchComponent">
<component>job_info</component>
<container>job_info_container</container>
<arguments>
<filter type="dm_basic_job">
<argument name="jobGroup" value="1" />
</filter>
<filter type="dm_advanced_job">
<argument name="jobGroup" value="2" />
</filter>
</arguments>
</execution>
</action>
</scope>
Conclusion
Scoping your actions and components can be somewhat
confusing, especially for novice WDK developers. Things
often appear to work magically (or not), and debugging and
troubleshooting can be very daunting. As we have seen, quite
a few things have to align properly for the entire scenario
to work, and hopefully this article will help you shed some
light at and point you into the right direction. Once,
however, you get in the scoping “zone,” you will begin to
appreciate all of the development advantages it can provide.