Showing posts with label How to. Show all posts
Showing posts with label How to. Show all posts

Sunday, June 16, 2013

D2 4.1 custom logo

D2 has a standard logo which cannot be changed from configuration at the moment (though it is planned to be so from version 4.2).
You can change the D2 logo in version 4.1, but it requires customizing the D2 webapp by modifying a D2 CSS file. You can change the logo both on the login frame and menu bar. Here is the procedure to replace the D2 logo:

1. Prepare your custom D2 logo: make it in PNG format, 70x50 pixels and transparent background
2. Copy your custom logo (in our case d2_custom.png) into location resources/com/emc/x3/client/resources/images/logo
3. To replace the D2 logo in the menu bar open the file resources/themes/slate/css/xtheme-x3.css and search for "img.x3_portal_logo" (usualy line 61). In the CSS class add the style part marked in bold:

img.x3-portal-logo {
background-color: transparent !important;
z-index: 2;
width: 80px;
/* CSS customization start */
background-image: url("/D2/resources/com/emc/x3/client/resources/images/logo/d2_custom.png");
background-repeat: no-repeat;
height: 0 !important;
overflow: hidden;
padding: 60px 0 0;
/* CSS customization end */

}


4. To replace the D2 logo on the login window, in the same css file search for ".x3-login-window-banner" (usualy line 473) and add a new style, the one marked in bold:

.x3-login-window-banner {
/* height: 50px; */
background: -moz-linear-gradient(center top , #707070, #000000) repeat scroll 0 0 transparent;
background: -webkit-gradient(linear, left top, left bottom, from(#707070), to(#000000));
filter: progid:DXImageTransform.Microsoft.Gradient(StartColorStr=#707070,EndColorStr=#000000,GradientType=0);
border-bottom: 2px solid #CCCCCC;
/* the following props are temporary until we get logo img */
padding: 0 0 0 10px;
}

/* CSS customization start */
.x3-login-window-banner img {
background-image: url("/D2/resources/com/emc/x3/client/resources/images/logo/d2_custom.png");
background-repeat: no-repeat;
height: 0 !important;
overflow: hidden;
padding: 60px 0 0;
width: 80px;
}
/* CSS customization end */


After you make the changes, restart the application server, cleaning the cache, clean your browser(s) cache, then check if your custom D2 logo is correctly displayed.

Wednesday, June 12, 2013

D2 back-end tweaking

D2 is a new EMC Documentum application which comes with a brand new concept and technology. It's really dynamic, user-friendly and fast. In order to not experience a downgrade of performance as time passes, we should tweak and fine tune its environment. In this article we'll focus on the back-end, Documentum side. There are several easy tunings that can optimize your Documentum D2 system:

1. Disable unused jobs
The following jobs can be inactivated when you don't use certain features:

a) If you don't use external tasks (sent/received via email) in your Workflows:
- D2JobWFReceiveTaskMail
- D2JobWFSendTaskMail

b) If you don't use Workflows at all:
- D2JobWFReceiveTaskMail
- D2JobWFSendTaskMail
- D2JobWFCleanerWorkflows
- D2JobWFLaunchScheduledWorkflows
- D2JobWFCleanPopHistory
- D2JobWFFollowUpTaskNotif
- D2JobWFWorkflowsNotifications
- D2JobDelegation

2. Review jobs schedule
Many D2 jobs are scheduled to run often (even every 5 minutes), so if some features are not used very often, or refresh rate is not very important, consider encreasing scheduled run rate.

3. Activate and schedule Administration Jobs
Ensure the following Documentum Administration jobs are active and run on a regular basis:
- dm_DMClean (removes deleted and orphaned objects from the docbase)
- dm_DMFilescan (removes deleted or orphaned content files from the file system)
- dm_LogPurge (removes server and session logs from the docbase and file system)
- dm_ConsistencyChecker (runs lots of integrity checks on the docbase)
- dm_UpdateStates (update database table statistics and repairs fragmented tables)
- dm_QueueMgt (deletes dequeued Inbox (dmi_queue_item) items from the docbase)

4. Disable/modify auditing when possible
D2 is pretty dynamic environment, objects are saved/modified many times, many jobs run and pretty often. This leads to a considerable amount of audit entries being created, which impacts the repository performance.
If you don't need auditing for all objects in the repository for the default set of events, remove the event dm_default_set from audit management. You can add this set and other events for custom types used by your applications, or even a specific set of events for custom types.
You can remove default auditing from Documentum Administrator, or by using unregsiter API command.

5. Review indexing configuration
If you have fulltext indexing enabled, check what types are configured to be indexed and try to narrow the count as possible (don't index global types like dm_sysobject, dm_document, etc.).

Monday, June 3, 2013

How to insert a node in browsertree in Webtop

Adding and modifiying Webtop (or other WDK application) browsertree nodes is done by customizing the browsertree component. To achieve this, you must do the following:
In Webtop custom layer, in config folder (or its subfolder, according to your structure) create a new file browsertree_component.xml (or find the existing one), which will override the brosertree component configuration. Here's a sample of how the configuration should look like:
<config version="1.0">
  <scope>
    <component modifies="browsertree:webtop/config/browsertree_component.xml">
      <insertafter path="nodes.docbasenodes.node[componentid=inboxclassic]">
        <node componentid="custom_node">
          <icon>customicon_16.gif</icon>
          <label>Custom Node</label>
        </node>
      </
insertafter>
      <!-- below optional parts -->
      <replace path="pages.start">
        <start>/custom/webtop/browsertree.jsp</start>
      </replace>
      <replace path="nlsbundle">
        <nlsbundle>com.package.custom.BrowserTreeNlsProp</nlsbundle>
      <replace>
    </component>
  </scope>
</config>


In this example we modify the browsertree component configuration from webtop layer by inserting a new node after the node inboxclassic. Our node will display the "custom_node" component, which must be a WDK component. We also replaced the browsertree standard jsp layout with out custom jsp. Finally, we've defined a custom NLS resource bundle for localizable strings.
Note that we've used definition modification (introduced in WDK 6.x), but the same result can be achieved by extending the component definition from parent layer (although it usually requires copying big xml parts from parent definition).

Friday, May 31, 2013

How to install DAR into Documentum repository

Documentum artifacts (jobs, methods, modules, lifecycles, custom types, etc.) are created in a Composer project and built into a DAR file, which consequently must be installed into the repository using DARDeployer (in previous versions called DARInstaller) or Composer Headless (using scripts).
Note: the Composer project artifacts can also be installed into the repository directly from Composer, by choosing Install Documentum Project... item in the context menu (right click on project): specify repository name, user name, password, domain (optional), press login, then install.

To install a DAR file, open DarInstaller (or DARDeployer in old versions) and fill the fields:
 - DAR: the DAR file to install (required)
 - Input File: the installation parameters file (file with custom installation parameters)
 - Locale Folder: Folder with localization
 - Log File: log file location (in case you don't want the default location)

Once filled these fields, select the docbroker host, enter a port number (default: 1489), press Connect to get the repositories list. Then select a repository from the list, enter user name (superuser), password and domain (optional). When ready, click the Install button. The installation process starts and once it's completed you will see a message telling the dar was installed successfully. In case of errors, you will be notified with a proper message (no changes take place as the DAR installation is done in a transaction). In this case check the logs and try to solve the issue (sometimes it might be a trivial connection lost with the CS), then reinstall the DAR file. Sometimes, before reinstalling the DAR, Data Dictionary re-publish could be necessary (using dm_DataDictionaryPublisher job or just with API command: publish_dd,c).

If you can't use DARDeployer (for ex. non-Windows OS: Linux, AIX, etc.), you can install the DAR using Composer Headless. For this, you must create 3 files:

1. darinstall (script file)
Here's a template of the script file (Linux):
#!/bin/sh

export JAVA_HOME=/opt/documentum/shared/java/1.6.0_17
export ECLIPSE=/opt/documentum/product/6.7/install/composer/ComposerHeadless
export WORKSPACE=/opt/project/darinstall-workspace
$JAVA_HOME/bin/java -Xms512m -Xmx1024m -cp $ECLIPSE/startup.jar org.eclipse.core.launcher.Main -clean -data $WORKSPACE -application org.eclipse.ant.core.antRunner -buildfile darinstall.xml


2. darinstall.xml (Ant buildfile)
<?xml version="1.0"?>
<project name="darinstall" default="install">
  <description>ant script to install a DAR file</description>
    <property file="darinstall.properties"/>
    <property name="dar.folder" value="." />
    <target name="install">
      <antcall target="_install.dar">
        <param name="dar.file" value="${dar.folder}/CustomTypes.dar" />
      </antcall>
      <!-- other dars, in order of dependency -->
    </target>
    <target name="_install.dar">
      <echo message="============================"/>
      <echo message="Installing DAR ${dar.file}"/>
      <echo message="============================"/>
      <emc.install dar="${dar.file}" docbase="${dctm.docbase}" username="${dctm.username}" password="${dctm.password}"/>
    </target>
</project>


3. darinstall.properties (configurations,credentials)
dctm.docbase=project_dev
dctm.username=dmadmin
dctm.password=dmadmin
dar.folder=/opt/project/darinstall


Now just launch the first script (darinstall), wait until it completes, then check installation logs. If you find no errors/warnings, it's done!

Friday, May 10, 2013

Group memberships updates reflected in delay

In a multiple Content Servers Documentum environment you can encounter the following issue: after you change a group memberships (add/delete users and groups) using one CS, the changes are not reflected when you connect to the repository with other Content Servers, for a period of time (up to several hours). So the Content Server that performs the memberships change reflects the changes immediately, while the others will have a delay, usually a big one.
Why is this happening?
That's because the caching mechanism the Content Servers are using. The group memberships are cached from the DB into Content Server memory, so the CS does not query the DB every time it needs this information. When you are connected with a CS and a group is updated, this CS takes care to update its cache accordingly for that group. But what if you have more CS for the same repository? They will not update their cache, because they don't know there was a change. That's why on the other CS the group change will not be reflected until they don't refresh their cache.
How to solve that?
There is a configuration in server.ini which the CS Installation Guide does not pay attention to: upd_last_chg_time_from_db . The CS Administration Guide provides the following description for the key:
Optional key. Specifies that all Content Servers in a clustered environment have timely access to all changes in group membership. Valid values are: * TRUE: Should only be used for environments with multiple Content Servers. The value should be set to TRUE for all running Content Servers. * FALSE: The default value.
So the solution is to modify the server.ini files on all Content Severs setting this key value to TRUE:
# A boolean flag specifying whether user entries and groups list are synchronized from db.
upd_last_chg_time_from_db=T

Wednesday, May 8, 2013

How to test BOF (TBO/SBO) code changes without re-deployment

Documentum Business Object Framework (BOF) version 2.0 implies deploying of all modules implementation into the repository, as jar artifacts. These jars are downloaded and cached on demand, when the corresponding module is being called.
The need of re-deployment of BOF modules (TBOs or SBOs) each time we make a change in the code might cause important delays in the development process: recompiling composer project, installing it, restarting JMS each time consumes a lot of time.
Is there a way to test the BOF code fast, without redeploying it? Yes, it is!
You can cheat the DFC client and replace the cached jars with a newer versions. Here's how you can do it.
After having called the BOF module (to ensure it's cached), go to the BOF cache folder: its location on the client machine can be specified in dfc.properties file, in the dfc.cache.dir property (default is cache subdirectory of dfc.data.dir). Inside you'll find a folder structure like this: [DFC_VERSION]/bof/[DOCBASE_NAME]. In the docbase folder you'll find more jars having the name [DMC_JAR_OBJECT_ID].jar. You have to locate the one containing your BOF code. For this, you can run the DQL command:
select r_object_id from dmc_jar where object_name='[JAR NAME]'
Or, you could also change the property dfc.bof.cache.append_name property so that the cached jars names will contain also their object names (on next caching).

Once you locate your jar, you have 2 options:
a) replace the cached jar with the new one, built in your IDE, keeping original name
b) open the jar (like a common archive) and replace only the modified class(es)
Remember to stop your DFC client to unlock the cached jars, otherwise you can't modify them.
Now start your DFC client and test your changes.

Code fast, test faster!

Tuesday, May 7, 2013

How to create a custom job in Documentum

Documentum jobs allow to process some objects automatically, by schedule, according to the business logic. They are pretty similar to scheduled tasks on Operating Systems. When started, the job calls a Documentum method which executes the desired logic. The methods are executed on the Java Method Server (JMS), which is part of the Content Server installation.
A job consists of several items:
 1) Job Method implementation code
 1.1) for BOF 2.0: corresponding implementation jar(s) and module
 2) dm_method object - which holds information about the method, implementation class, etc.
 3) dm_job object - which holds information about job, its schedule, last and next invocation, current status, the method it runs, etc.

Here's how to create all of the required items for the job.

1. Job method implementation code
First of all you have to write the code that will implement the logic the job method will perform. There are 2 approaches of deploying this code: BOF 1.0 or 2.0.
With BOF 1.0 the method classes are deployed manually on the JMS (path: [JBOSS]\server\DctmServer_MethodServer\deploy\ServerApps.ear\DmMethods.war\WEB-INF\lib), while BOF 2.0 requires you to deploy the implementation jars into the repository and associate them with a BOF module.
For both approaches you can find job method implementation classes samples here: Custom job method DFC code sample
When you're done with coding, compile the implementation classes and build the jar.

1.1 Implementation jar(s) and module (for BOF 2.0)
Open Composer and create the follosing artifacts:
a) Jar: the artifact name should be equal to the jar name (to avoid confusion), the jar type is Implementation.
b) Module: the module name must be equal to the full class name (with packages), modul type: Standard Module, choose the implementation jar created at previous step (a), select the class name (equal to module name). In addition specify all the required modules and Java libraries used by your implementation class(es).

2. dm_method object
a) Using Composer
In Composer create a Method artifact, enter the method name, select type: java, Command: module name (equal to implementation full class name), Run Controls: Run Synchronously, check the options: Run as the server, Trace launch, Use method server, Launch directly. If your code is taking considerable time to run, increase the timeout values.

b) Using Documentum Administrator
Open DA and go to Job Management->Methods node level. From menubar select File->New->Method, enter required values and save the method.

c) Using DQL:
create dm_method object
set object_name = 'MyCustomMethod',
set launch_async = false, set launch_direct = true, set method_type = 'java',
set method_verb = 'com.documentum.project.method.SomeCustomMethod',
set run_as_server = true, set timeout_default = 600,
set timeout_min = 300, set timeout_max = 3000, set trace_launch = true,
set use_method_content = false, set use_method_server = true;


3. dm_job object
a) Using Composer
In Composer create a Job artifact, enter job name, Subject, select the Method (created at step 2), set schedule options. In Arguments section you can add custom arguments passed to your method implementation class. Keep in mind that even if you use custom arguments, you should also pass the standard arguments (docbase name, installation owner, job id, etc.) which are used to manage the job. Thus, in order to add custom arguments select Custom Arguments, add desired arguments with values, then switch back to Standard Arguments. Save the job artifact.

b) Using Documentum Administrator
Open DA and go to Job Management->Jobs node level. From menubar select File->New->Job, enter required values and save the job.

c) Using DQL:
create dm_job object
set object_name = 'MyCustomJob',
set title = 'Title', set subject = 'Job description',
set pass_standard_arguments = true, set is_inactive = false,
set start_date = DATE('01/01/2013'), 

set expiration_date = DATE('01/01/2020'), 
set a_next_invocation = DATE('02/01/2013'), set max_iterations = 0,
set method_name = 'MyCustomMethod', set run_mode = 3,
set run_interval = 1, set inactivate_after_failure = true;


Now you have all the required items for you job. Build the composer project, install it into the repository (from Composer, right click the project and choose Install Documentum Project... or take the built DAR from bin-dir folder and install it using DarDeployer/DarInstaller). If you're using BOF 1.0, deploy manually the implementation jar on JMS (usualy in [JBOSS]\server\DctmServer_MethodServer\deploy\ServerApps.ear\DmMethods.war\WEB-INF\lib).
If it's not the first deployment of the DAR, it's better to restart the JMS and clear the BOF cache (mandatory in case of BOF 1.0).

It's done! Go on with testing: run the new job and check the job report and JMS logs.

Tuesday, April 9, 2013

How to stop running job

A Documentum job consists of a dm_job object, containing job configuration, name of method to run, as well as process execution information. The job method is executed on the Content Server (if it's a java method - on the JMS). Once the job is started, there's no 'soft' approach to stop it, you can only force it.
Below are the steps to stop properly a running job, reseting also the dm_job object:
1. Get the process id, by running the DQL:
select a_last_process_id from dm_job where object_name='[JOB_NAME]'
2. Using Process Explorer (or similar tool) locate the process by id and kill it. (Or with a command like: taskkill /pid 1234)
3. Run the API command to unlock the job object:
unlock,c,[job_id]
4. Execute the following DQL queries to clear job execution-related attributes:
a) Reset application that uses/locked the job
update dm_job objects set a_special_app='' where object_name='[JOB_NAME]';
b) Reset current status
update dm_job object set a_current_status='FAILED' where object_name='[JOB_NAME]';
c) Prevent job from being run immediately
update dm_job object set run_now=FALSE where object_name='[JOB_NAME]';

Remember to change the schedule or inactivate the job if you don't want it to run automatically after specified period.
If your job is locked in running state, check here how to unlock it: How to unlock a job in running state.

Tuesday, April 2, 2013

Renaming users - how dm_UserRename job works

In a previous article How to change the user_name of a dm_user we've seen how to rename a Documentum user manually (using Documentum Administrator) and by DFC code.
Let's assume we want to update a set of users, but can't or don't want to write DFC code (it also requires some time to write and run the code). We have an additional option: have the dm_UserRename job rename the users automatically. But how does the dm_UserRename job work?
This job polls a queue: dm_job_request objects. The object has the following attributes which are relevant for the job:
object_name         : UserRename
job_name            : dm_UserRename
method_name         : dm_UserRename
request_completed   : F
arguments_keys   [0]: OldUserName
                 [1]: NewUserName
                 [2]: report_only
                 [3]: unlock_locked_obj
arguments_values [0]: userA

                 [1]: userB
                 [2]: F
                 [3]: F


The jobs read the arguments_keys & arguments_values values and calls the dm_UserRename method with these arguments, so the specified user is renamed with selected options (report_only mode, unlock objects or leave locked). When the operation completes, the request_completed attributed is set to TRUE.
Thus in order to rename a set of users, you need to create a dm_job_request object for each user and run the dm_UserRename job. Note that by default the dm_UserRename job is inactive, so you can either run it manually, or activate it and schedule if you often rename users.

Here's sample DQL to create a dm_job_request object (to rename 1 user):
create dm_job_request object set object_name='UserRename', set job_name='dm_UserRename', set method_name='dm_UserRename', set request_completed=FALSE, append arguments_keys='OldUserName', append arguments_keys='NewUserName', append arguments_keys='report_only', append arguments_keys='unlock_locked_obj', append arguments_values='userA', append arguments_values='userB', append arguments_values=FALSE, append arguments_values=FALSE

How to change the user_name of a dm_user

In a Documentum repository users are uniquely identified by the user_name attribute. Being a key, the user_name value is used in related objects (for example: owner, r_modifier, r_creator, r_lock_owner, etc.) stating that this user performed an action on this object.
In certain cases we need to change the user_name value: for example when we want to create a new user, but an old one (perhaps deactivated) already exists with the same user_name.
Being a key, we can't simply update this value, you won't even be able to modify it on user property screen. Here are the ways to accomplish this:

1. Manual renaming
There's a simple tool called Reassign User which invokes the dm_UserRename job (Renaming users - how dm_UserRename job works explains how this job works):
a. (Optional) Create a new user with the user_name you want to rename the old user to.
b. Open DA, go to Users node in browsertree, find the desired user, right click on it and choose Reassign User. On the opened page enter/select the new user, select the option to run the reassign job now (and change other settings if required).
c. (Optional) As soon as the dm_RenameUser job has completed, you can remove the original (old) user if it's not needed anymore.
Note that you can skip the creation of a new user and just enter the new user_name on the Reassign User page, it will handle the modification of user_name itself.

2. Automatic renaming
If we want to rename many users, the manual approach would be really annoying, so as usual we turn to some DFC coding.
Renaming users in DFC is quite straightforward, it's enough to call the rename method:
 IDfUser user = session.getUser("userA");
 user.renameUser("userB", true, false, true);
 user.save();


Here's the method description from the API:
void renameUser(String userName,
                boolean isImmediate,
                boolean unlockObjects,
                boolean reportOnly)
                throws DfException

    Renames this user. If this is a new user, the attribute value is set. If this is an existing user, this method invokes a job to rename user.
    Parameters:
        userName - user name to be set or renamed to (existing user)
        isImmediate - Detemines if the job should be run immediately or run on schedule.
        unlockObjects - control if the objects should be unlocked when renaming user. If this is a new user, this parameter does not take any effect.
        reportOnly - control if the job created is to report or actual rename. If this is a new user, this parameter does not take any effect.

How to register and unregister types for indexing

Documentum uses fulltext indexing to enable search on all document properties as well as its contents.  How does fulltext indexing work?
Indexing is performed by a dedicated server (FAST, xPlore, etc.) that polls the queue of the fulltext user. In order to index certain (sub)types of documents, you must register certain events of these types for index user. Thus when a registered event occurs on a document of the registered type, a queue item is created in the fulltext user's queue, which consequently is processed by the Fulltext indexing server, by indexing the document. The events to register are those that usually change the object metadata and/or content: dm_save,dm_readonlysave,dm_checkin,dm_move_content,dm_destroy

By default, (in previous versions of Documentum) all dm_sysobject types and its subtypes are registered for indexing. Usually you don't need all the dm_sysobject to be indexed, so before registering your custom types, you should unregister the dm_sysobject first. You can do that by running the following API script, being connected with the fulltext user (dm_fulltext_index_user):
(Note: if you don't know the password for dm_fulltext_index_user you can generate a login ticket by running following API with a superuser: getlogin,c,dm_fulltext_index_user Read more details about this trick in: How to login to the repository with a ticket).

API for unregistering indexing:
unregister,c,[TYPE_ID],dm_save,,F
unregister,c,[TYPE_ID],dm_readonlysave,,F
unregister,c,[TYPE_ID],dm_checkin,,F
unregister,c,[TYPE_ID],dm_move_content,,F
unregister,c,[TYPE_ID],dm_destroy,,F

[TYPE_ID] is the r_object_id of the dm_type object
Note: If you have more Index servers, you should have an index user for each, so run the unregister script for each user.

You can also do that by unchecking the 'Enable Indexing' checkbox on dm_sysobject type properties in DA.

You can register a custom type for indexing in 2 ways:
1. By executing the following API script, with the fulltext user (dm_fulltext_index_user):
register,c,[TYPE_ID],dm_save,,F
register,c,[TYPE_ID],dm_readonlysave,,F
register,c,[TYPE_ID],dm_checkin,,F
register,c,[TYPE_ID],dm_move_content,,F
register,c,[TYPE_ID],dm_destroy,,F


[TYPE_ID] is the r_object_id of the dm_type object
Note: If you have more Index servers, you should have an index user for each, so run the register script for each user.

2. Manually, by using DA: Open Types node in browsertree, find the desired type, open its properties and check the 'Enable Indexing' checkbox.

To check which types are currently registered for indexing, use the DQL:
select name from dm_type where r_object_id in (select distinct registered_id from dmi_registry where user_name='dm_fulltext_index_user')

Wednesday, February 20, 2013

How to create an id generator (synchronized incrementer)

Pretty often we need a module that generates an id (incremented number), which must be unique: for example auto-numbering modules, etc.
To ensure uniqueness of these ids, we must synchronize the calls in order to exclude possible duplicate values for concurent calls. But if we have a distributed environment, the module is executed in parallel, unsynchronized environments. How to solve this problem?
We won't have synchronization issues if we implement the incrementer at the Database level (which is only one even for multiple Content Server environment).
The most convenient way is to use sequences (for Oracle, SQL Server, etc., other DBMS should also have something similar).
Below you'll find a smart way to implement the incremented number generator. Only DQL queries are used (executing also some SQL commands):
1. Create the DB sequence
execute exec_sql with query = 'create sequence my_sequence start with 1';

2. Create a function get_next_code that will use the sequence and return the incremented value (as string)
execute exec_sql with query = 'create or replace function get_next_code return varchar2 as code number; begin select my_sequence.nextval into code from dual; return to_char(code); end;';

3. Create a view which will pass the incremented value
execute exec_sql with query = 'create or replace view my_counter (my_code) as (select get_next_code() from dual)';

4. Register table (view)
register table dm_dbo.my_counter (
  my_code   char(32)
);

update dm_registered object
set owner_table_permit = 1,
set group_table_permit = 1,
set world_table_permit = 1
where object_name = 'my_counter';

The id generator is ready. Now in order to get the next id, you just need to run the following DQL:
select my_code from dm_dbo.my_counter

Couldn't be easier, isn't it?

Monday, February 11, 2013

How to display a component without authentication in WDK

Sometimes you need to display a component without opening a session on the target repository: for example displaying a page telling the user does not meet the requirements to access the application, etc.
If you simply jump to your component, the internal filters will detect there's no session opened and will automatically forward the request to the login component (according to the authentication schemes defined). To avoid this, you must perform some configurations:
In the file ..\webtop\WEB-INF\classes\com\documentum\web\formext\Environment.properties
you will have more lines like this:
non_docbase_component.32=showtestresult

All you have to do is find the last line of this kind (with the biggest number, in this case 32) and add after it a similar line for your component, incrementing the last index:
non_docbase_component.33=mycomponent

Now your component will be displayed even before logging into any repository.

Thursday, December 13, 2012

How to create and deploy a TBO

BOF (Business Object Framework) provides an easy way of implementing custom business logic. The advantage of using BOFs over application customization is the custom business logic is always executed, regardless of the client program using Documentum (repository).
The first version of BOF, 1.0 kept all of the TBO information outside the repository: BOF registry (list of BOF modules) was kept in a file dbor.properties, and the implementation jars had to be deployed in the classpath of the client (libraries folder). The big lack of this approach is that each client has to be configured in order to use properly the BOFs.
The new version - 2.0 eliminated this lack by using a different approach: BOF registry, the modules and their implementation is stored in the repository, so there's no need to configure the clients, all of them will benefit of BOF functionality deployed in the repository.
There are 3 types of BOF modules: TBO (Typed-based Business Objects), SBO (Service-based Business Objects) and Aspects. TBOs are the most used and serve for modifying and extending the behavior of persistent repository object (custom) types.

In this article I'll focus on creating and deploying a TBO. The steps to follow are:
1. Create a custom type
2. Write TBO source code
3. Create Jar Definition artifacts
4. Create Java Library artifacts (optional)
5. Create Module artifact
6. Deploy the TBO

1. Create a custom type
The very first thing is to have a custom type which your TBO will map to. You can't map a TBO to a standard Documentum type. Your custom type must extend a persistent type (ie: dm_document, dm_folder, dm_user, etc.).

2. Write TBO source code
The first step is to write the Java code of the TBO, the operations that will be executed when the object of the mapped type is changed. Using Eclipse create a new Java Project, add the DFC libraries (and other required libraries) in the build path.
The code and classes structure depends on complexity, but for the TBO you need at least an Interface and an Implementation class, preferably in different packages. I will list here some trivial samples:

a) Interface IAttach:
package com.company.project.attach.tbo;

import com.documentum.fc.client.IDfBusinessObject;
import com.documentum.fc.client.IDfDocument;
import com.documentum.fc.common.DfException;
import com.documentum.fc.common.IDfDynamicInheritance;

public interface IAttach extends IDfBusinessObject, IDfDocument, IDfDynamicInheritance {
public void setDoctype(String doctype) throws DfException;
public String getDoctype() throws DfException;
}

The interface should extend 3 other interfaces:
- IDfBusinessObject - required for modules management
- IDfPersistentObject (usualy IDfDocument) methods defined for the base type you're extending: if your type is a subtype of dm_document, use IDfDocument, for dm_folder - IDfFolder, etc.
- IDfDynamicInheritance - enable some advanced module handling

As you can see the interface declares only 2 custom methods: setDoctype and getDoctype that set and get values for the custom attribute "prj_doctype".
Compile the interface and add pack it into a jar (attach.jar).

b) Implementation class Attach:
package com.company.project.attach.tbo.impl;

import com.company.project.attach.tbo.IAttach;
import com.documentum.fc.client.DfDocument;
import com.documentum.fc.common.DfException;
import com.documentum.fc.common.DfLogger;

public class Attach extends DfDocument implements IAttach {
public void doSave(boolean saveLock, String versionLabel, Object[] extendedArgs) throws DfException {
DfLogger.info(this, "doSave called for object with id: {0}", new String [] {getObjectId().toString()}, null);
setTitle(this.getDoctype());
super.doSave(saveLock, versionLabel, extendedArgs);
}

public void setDoctype(String doctype) throws DfException {
setString("prj_doctype", doctype);
}

public String getDoctype() throws DfException {
return getString("prj_doctype");
}
@Override
public String getVendorString() {
return "Copyright Documentum Guy";
}
@Override
public String getVersion() {
return "1.0";
}
@Override
public boolean isCompatible(String version) {
return getVersion().equals(version);
}
@Override
public boolean supportsFeature(String arg0) {
return false;
}
}

The implementation class contains the implementation for the 2 custom methods, for doSave - overriding method from DfPersistentObject (method called when an object of this type is saved), and also implementation for other 4 methods declared in IDfBusinessObject interface.
According to best practices you should override only methods beginning with do (doSave, doCheckin, doCheckout, etc.), not save, checkin, etc. Usualy you need to add some functionality and not change the default one, so remember to call also the superclass method: super.[Overriden_Method].
Compile the implementation class(es) and pack it into a jar (attach-impl.jar): ensure that interface and implementation are in different jars.

3. Create Jar Definition artifacts
In the past DAB (Documentum Application Builder) was used to manage Documentum artifacts, but it was replaced by Composer since version 6.5.
In composer create a new Documentum project and give it a relevant name. Then right click on the Artifacts folder of your project and choose New->Jar Definition (if you don't have it in the list, choose New->Other and choose Jar Definition under Documentum Artifact category). Type the name of the artifact exactly as the name of the jar file (it's not a restriction, but it's a good habit in order to avoid confusion).
You must create 2 jars: interface and implementation. So for this sample we create attach.jar with Type: Interface and attach-impl.jar with Type: Implementation.

4. Create Java Library artifacts (optional)
All the libraries used by your TBO code must be deployed in the repository and related to the TBO module. These jars are packed in artifacts called Java Libraries.
First you must create a Jar Definition per each required jar. Then right-click Artifacts folder and choose New->Java Library, enter a name, then in the JARs field add the jars you need (all jar artifacts from current project are available for selection).

5. Create Module artifact
Now we can proceed to create the TBO module. Right click the Artifacts folder, select New->Module and type the name of the custom type you want to assign the TBO (it won't work if the module name does not match the type name). In the Type combo choose TBO. Next to Implementation Jars field click Add and select the attach-impl.jar, for Interface Jars click Add and select attach.jar, for Class name click Select and choose the class: com.company.project.attach.tbo.impl.Attach (the list of available classes wil be loaded from the implementation jar you've selected). If your TBO implementation code uses some other modules (TBOs, SBOs, etc.) add them in the Required Modules field. In the bottom-left corner choose Deployment tab and add the required Java Libraries (libraries referenced by your TBO code).

6. Deploy the TBO
Ok, now all the artifacts required for the TBO are ready to be deployed into the repository. Build the composer project (if Build Automatically flag is not checked) by choosing Project->Clean from the menu bar. The output will be a DAR file located in bin-dar folder of the project. You can either install the project directly from Composer (right-click on the project and chooose Install Documentum Project) or using DarDeployer (previous name - DarInstaller).
Select the DAR file, repository, enter user name and password and click Install. After the DAR/project is installed, you can restart the JMS and Application Server (and any other client using this repository) and clean BOF cache to ensure that the last versions of jars will be downloaded from the repository.

That's all, now you can test your TBO (To find how to test your TBO changes without re-deploying, check this article: How to test BOF (TBO/SBO) code changes without re-deployment). It will be called when objects of the mapped type will be changed either by DFC code or DQL queries.
If you'll have these steps in front of you, the creation of TBOs will be pretty easy.

Tuesday, October 30, 2012

How to recover deleted document

If you want to recover an object deleted in the Documentum repository, you have big chances to recover it's content. Here you'll find the steps to recover a document content even without having many details about it.
Object metadata can be recovered only if you have a database backup, made before the deletion.
Anyway, usually the most important thing is the content itself, not the metadata, so we'll focus on the procedure of recovering the content of removed document.

The first and most important thing to do is to disable the dm_DMClean job, which cleans up orphaned objects, including the content ones. Check the job last execution time: if it ran after the document was deleted, I'm sorry - the content is lost (well, if you have both DB & content backup you can recover anything you want).
Also check the job dm_DMFileScan, usually it's disabled, if it's enabled you'd better disable it untill you recover your document.

Next, our task is to find the dmr_content object which has information about the content location.
As there might be thousands of orphaned content objects, try to get as much information as possible about the deleted document:
1) Date/time of deletion and user who deleted the document
2) Date/time of creation / last modification of content (checkin)
3) File format, aprox. size, object name

The query to get the content objects having no associated metadata objects (dm_sysobject) is:
select * from dmr_content where any parent_id is null

The problem is this query most probably will give you too many results, but I guess you don't want to find the right document when you reach 65 years :)

Now let's see how this information can help you to narrow the results:
1) Date/time of deletion and user who deleted the document
Hoping you have auditing enabled, you can get some information from this audit:
select * from dm_audittrail where event_name='dm_destroy' where time_stamp > date('some date before deletion') and user_id = (select r_object_id from dm_user where user_name='USER_WHO_DELETED')

From the results returned, if you find a record that seems to represent the deleted document, grab the object_name value

2) Date/time of creation / last modification of content (checkin):
select r_object_id,full_format from dmr_content where any parent_id is null and set_time > date([time before creation]) and set_time < date([time after creation])

3) File format, aprox. size, object name (possibly grabbed at step 1):
select r_object_id,full_format from dmr_content where any parent_id is null and full_format='[FORMAT]' and content_size > [MIN_SIZE] and content_size < [MAX_SIZE] and set_file like '%[OBJECT NAME]%'

Note: You can combine filters from point 2 & 3 if you have this information. The more filters you use, the less results you'll have.

Ok, so now you have a list of content objects (hopefully not too big). Now you can get corresponding paths to the files, on the file storage.
For each id in the list generate a DQL command:
execute get_path for '[ID]'
where [ID] is the r_object_id of dmr_content object

Executing the obtained script you get a list of file paths. Copy the results to a file.
Now you have 2 options to getting the files:
1) You can get the content files directly (without creating objects in repository), by obtained paths. Generate a script that will copy all the files to your folder. For example, you can use commands like:
cp [OBTAINED PATH] /target_folder/[COUNTER].[FULL_FORMAT]

where COUNTER is a counter (1..n) - to not have name conflicts during the copy operation.

2) Create new objects in the docbase by generating a DQL with queries like:
create my_type object set object_name='Some identifier', link '[Folder path]', setfile '[PATH]' with content_format='[FORMAT]'

If you recovered more documents, you can open them and find the one you were searching for.
Once you're happy with having recovered the deleted document, don't forget to enable back the dm_DMClean job if you disabled it.

Thursday, October 4, 2012

How to make a custom WDK qualifier

WDK features, definitions and settings can be scoped so they are presented only when the user's context or environment matches the scope definition.
In other words you have a filtering mechanism using qualifiers. WDK provides the following standard qualifiers:
- DocbaseNameQualifier (scope: docbase, name of the docbase to which application connects)
- DocbaseTypeQualifier (scope: type, matches the document type)
- PrivilegeQualifier (scope: privilege, matches user privileges)
- RoleQualifier (scope: role, matches user role)
- ClientEnvQualifier (scope: clientenv, values: "webbrowser", "portal", "appintg", or "not appintg")
- AppQualifier (scope:application, matches the application name)
- VersionQualifier (scope: version, mathces the application version)
- EntitlementQualifier (scope:entitlement, checks entitlement evaluation classes)
- ApplicationLocationQualifier (scope:location, matches the navigation location)

However you might need to use custom scoping, so you must create a custom qualifier.
To create a cusom qualifier, you must perform the following steps:
1) Create the custom qualifier class, which implements the IQualifier interface. Here's a sample:
public class CustomQualifier implements IQualifier {

final static public String QUALIFIER_NAME = "customQualifier";

public String[] getAliasScopeValues(String strScopeValue) {
return null;
}
public String[] getContextNames() {
return new String[] { QUALIFIER_NAME };
}
public String getParentScopeValue(String strScopeValue) {
return null;
}
public String getScopeName() {
return QUALIFIER_NAME;
}
public String getScopeValue(QualifierContext context) {
String sCustomQualifier = "";

// custom code here to find the qualifier value to be set
// this code is called often, so it must not be 'heavy'
// consider using the cache

return sCustomQualifier;
}

2) Add your qualifier definition to app.xml file of your custom layer (usually custom folder):

inside <qualifiers> tag add your qualifier's class full name:
<qualifier>com.mycompany.wdk.qualifier.CustomQualifier</qualifier>

3) Use custom scoping & filtering in your components' xml definitions:
a)
<scope customQualifier="someValue">
....your definitions here...
</scope>

b)
<filter customQualifier="someValue">
    ....your definitions here...
</filter>

That's all. Keep in mind that qualifiers impact application performance because the qualifier's class is called on each read of definitions. So try to avoid adding custom qualifiers if you have other options.

Tuesday, October 2, 2012

How to obtain dfc data directory in DFC


DFC data directory is configured in dfc.properties, in dfc.data.dir property . If it's not specified, the default Documentum path is used.
You might need this location in order to read a configuration file, which normally is stored in config folder, under dfc data directory.

If you want to find the location of this folder, the following DFC code can be used:
// get dfc.data.dir:
String dataDir = DfPreferences.access().getDataDirectory();
// get config folder:
File configDir = new File(new File(dataDir), "config");

Wednesday, August 29, 2012

How to delete a Documentum repository

For many years the repository served us truly and faithfuly, but the time has come... and we don't need it anymore.
Yes, we will delete it! Unfortunately it's not such trivial as running a single wizzard.
Below are the steps to do a complete removal:

0. Check one more time you're deleting the right repository (and it's not live production) :)

1. Drop (uninstall) the Index Agent
1.1 Stop the Index Agent (from DA or Index Admin page)
1.2 On the Index Server host Start the Index Agent Configuration Program, select 'Delete Index Agent', then select the Index Agent (if you have more).
1.3 From Index Server Admin page (ie: http://localhost:16000/admin) delete the corresponding collection for the docbase.

2. Remove DTS / ADTS service from the repository
2.1 Start the ADTS Configuration Utility. Check that your docbroker and docbase are running before proceeding.
2.2 Choose 'Remove an instance of ADTS from a docbase', then select the repository, enter installation owner credentials and finish the procedure.

3. Stop any other applications or/and services that are using the repository

* If you don't know the content files location, check it now before removing the repository (dm_location object)

4. Remove the docbase service
4.1 Launch Documentum Server Manager, on Utilities tab click Server Configuration and following the wizzard instructions.
4.2 Choose Custom configuration, Delete an existing repository, choose the repository and complete the procedure.

5. Delete DB tablespace(s) and schema.

6. Delete docbase files from the Content Server host
6.1 Delete the content files
6.2 Delete the log files

Congratulations! You've done it! Next repository? :)

Tuesday, August 28, 2012

How to clone a Documentum repository

Why cloning a Documentum repository? Well sometimes it is required to perform some tests (new software/versions/features) on the real production data. Development environment might not be very relevant because usually it has a very reduced set of data. There might be also plenty of other reasons.
Cloning a repository means creating an identic copy of it, with no impcat on the original repository.
This activity is not trivial and requires more steps to be performed:
* Note: The supposed OS is Windows, for other OS some steps might require appropriate changes.

1. Create a new DB schema and clone the data from the schema of the repository.
2. Stop the repository service.
3. Copy the content and configuration files from the source Content Server file system. On Windows you can use the robocopy command, like this:
robocopy [SOURCE_PATH] [DESTINATION_PATH] /NP /MIR /SEC /R:10 /W:10 /LOG:[LOG_FILE] >> [OUTPUT_FILE]

 Copy the following paths:
  a) [DOCUMENTUM]\dba\config\[repository_name]
  b) [DOCUMENTUM]\dba\auth\[repository_name]
  c) Folder with content files (obtain it from the dm_location object)

4. Execute the SQL script (on the new DB schema):
-- Set the clone host name
update dm_server_config_s set r_host_name='[NEW_HOST]';
update dm_mount_point_s set host_name='[NEW_HOST]';
update dm_server_config_s set web_server_loc='[NEW_HOST]';

-- Force server to recreate views
update dm_type_s set views_valid = 0;

-- Set the clone content location
update dm_location_s set file_system_path='[CONTENT_PATH_NEW]' where file_system_path='[CONTENT_PATH_OLD]';
update dm_location_s set file_system_path='[CONTENT_PATH_NEW]\replicate_temp_store' where 
file_system_path='[CONTENT_PATH_OLD]\replicate_temp_store';
update dm_location_s set file_system_path='[CONTENT_PATH_NEW]\replica_content_storage_01' where file_system_path='[CONTENT_PATH_OLD]\replica_content_storage_01';
update dm_location_s set file_system_path='[CONTENT_PATH_NEW]\content_storage_01' where file_system_path='[CONTENT_PATH_OLD]\content_storage_01';
update dm_location_s set file_system_path='[CONTENT_PATH_NEW]\thumbnail_storage_01' where 
file_system_path=' [CONTENT_PATH_OLD]\thumbnail_storage_01';
update dm_location_s set file_system_path='[CONTENT_PATH_NEW]\streaming_storage_01' where file_system_path='[CONTENT_PATH_OLD]\streaming_storage_01';

-- Set job server execution
update dm_job_s set target_server='[DOCBASE_NAME].[DM_SERVER_CONFIG.OBJECT_NAME]@[HOST]' where target_server = '[DOCBASE_NAME_OLD].[DM_SERVER_CONFIG.OBJECT_NAME_OLD]@[HOST_OLD]'

-- Note: If the cloned repository was served by more CS instances (so more dm_server_config objects present) you must run a query like this per each server config object.
-- The value [DM_SERVER_CONFIG.OBJECT_NAME] can be obtained with this query: select object_name from dm_server_config

-- Disable the jobs (will be reactivated later)
update dm_job_s set is_inactive=1;

-- ACS config: assuming the repository is served by 2 CS
update dm_acs_config_r set acs_base_url='http://[NEW_HOST]:[[PORT]/ACS/servlet/ACS' where acs_base_url in ('http://[OLD_HOST_1]:[PORT]/ACS/servlet/ACS', http://[OLD_HOST_2]:[PORT]/ACS/servlet/ACS') ;
-- PORT: default is 9080

-- Reset the crypto key
update dm_docbase_config_s set i_crypto_key = ' ', i_ticket_crypto_key = ' ';
delete from dmi_vstamp_s where i_application = 'dm_docbase_config_crypto_key_init';
delete from dmi_vstamp_s where i_application = 'dm_docbase_config_ticket_crypto_key_init';

delete dm_sysobject_s where r_object_id = (select r_object_id from dm_public_key_certificate_s where key_type = 1);
delete dm_sysobject_r where r_object_id = (select r_object_id from dm_public_key_certificate_s where key_type = 1);
delete dm_public_key_certificate_s where key_type = 1;

delete dm_sysobject_s where r_object_id = (select r_object_id from dm_cryptographic_key_s where key_type = 1);
delete dm_sysobject_r where r_object_id = (select r_object_id from dm_cryptographic_key_s where key_type = 1);
delete dm_cryptographic_key_s where key_type = 1;

-- Old fast index configuration cleanup (if FullText is installed)
update dm_ftengine_config_r set param_value='[FULLTEXT_HOST]' where param_name='fds_config_host';
update dm_ftengine_config_r set param_value='[FULLTEXT_HOST]' where param_name='query_engine_host_name';

delete from dm_ftindex_agent_config_s;
delete from dm_sysobject_s where r_object_type=’dm_ftindex_agent_config’;

-- Old ADTS configuration cleanup (if ADTS is installed)
delete from cts_instance_info_s;
delete from cts_instance_info_r; (16 row deleted)
delete from dm_sysobject_s where r_object_type=’cts_instance_info’;

commit;

5. Content Server update

5.1 Update server.ini
Edit the server.ini file located in %DOCUMENTUM%\dba\config\[DOCBASE_NAME] and update the following fields:
database_conn [NEW_DB_INSTANCE]
[DOCBROKER_PROJECTION_TARGET]
host = [NEW_DOCBROKER]
port = 1489

5.2 Re-encrypt the Database password
cd %DM_HOME%\bin
dm_encrypt_password -docbase [NEW_DOCBASE_NAME] -rdbms -encrypt [DB PASSWORD]

5.3 Windows service.
Execute the following commands script to create the Windows Service for the repository (you can save it in a .bat file and run it):
@echo off
setlocal

set docbase=[DOCBASE_NAME]
set instowner=[INSTALLATION_OWNER]
set iodom=[DOMAIN]
set iopw=[PASSWORD]

set binPath=%DM_HOME%\product\[VERSION]\bin\documentum.exe -docbase_name %docbase% -security acl -init_file E:\Documentum\dba\config\%docbase%\server.ini -run_as_service -install_owner %instowner% -logfile E:\Documentum\dba\log\%docbase%.log

sc create DmServer%docbase% binPath= "%binPath%" start= demand DisplayName= "Documentum Docbase Service %docbase%" obj= %iodom%\%instowner% password= %iopw%

sc description DmServer%docbase% %docbase%_clone

endlocal

5.4 Add port numbers
Edit %WINDIR%\system32\drivers\etc\services and add the following entries at the end of the file:
dm_[DOCBASE_NAME] [N]/tcp #Documentum Docbase Service [DOCBASE_NAME]
dm_[DOCBASE_NAME]_si [N+1]/tcp #Documentum Docbase Service [DOCBASE_NAME] (secure service)

* where N can be the incremented last port used in the list

5.5 Windows Registry update

Export the docbase registry key from the Windows registry on the old machine, from branch:
"HKLM\SOFTWARE\Documentum\DOCBASES\[DOCBASE]"

Open the exported file and update the following keys:

"DM_DOCBASE_CONNECTION" => "[DB_INSTANCE]"
"DM_HOME" => "[PATH]\\product\\[VERSION]" (ie: C:\\Documentum\\product\\6.7)
"DOCUMENTUM" => "[DOCUMENTUM_PATH]" (ie: C:\\Documentum)
Save the file with UTF-8 encoding.

Update the registry on the target machine by importing the reg file.

5.6 Create session logs folder:
[DOCUMENTUM]\dba\log\[DOCBASE]

6. Start the repository service

7. Post-cloning activities

7.1 Reset the inline passwords
Inline passwords must be reset (usualy they are not working anymore because of encryption changes)
Use a DQL like this:
update dm_user objects set user_password=user_name where user_source='inline password' [AND user_name not in (exception_list)]

or provide an explicit list of users:
update dm_user objects set user_password=user_name where user_name in (update_list)
Usualy update_list contains users: dm_bof_registry, dm_fulltext_index_user, dmc_wdk_presets_owner and others.

7.2 Install other required products: Index Agent, ADTS instance, etc.

Tuesday, August 21, 2012

How to change object type

Documentum allows creation of custom types, which defines custom attributes in addition to the inherited ones. Once your custom type is defined and created in the repository, you can create objects - which are instances of that custom type.
If you have created objects of a certain (sub) type, you can still change their type. The easiest way is to use the following DQL:
CHANGE current_type [(ALL)] objects to new_type [update_list] [WHERE qualification]
* ALL - change type of all object versions
* update list - list of updates on objects attributes to perform during change type operation

Obviously current_type must be the supertype or subtype of the new_type. Thus, you can move the object instances through the types hierarchy up and down.
Ok, if you move from a supertype to subtype you get additional attributes that are empty. But what happens when moving from subtype to supertype? The custom attributes' values of the subtype are lost (you can use the update list if you want to copy the values to other attributes, that are not lost).

You should take note of the following constraints on this operation:
1. You can only change the type of objects that are subtypes of dm_sysobject. (If you want to change the object type of not a dm_sysobject, follow the procedure described here: URL).
2. The object's current type and the new type must have the same type identifier (which is the first two characters of the object ID. For example, dm_document has 09).
3. The old and new types can't be at the same level in the type hierarchy. The new type must be a supertype or subtype of the current type.
For example, you have a my_car and its subtypes my_toyota and my_lexus. In order to change an object of my_toyota to type my_lexus, you must do it in 2 steps:
change objects my_ford to my_car;
go;
change objects my_car to my_lexus;
go;

4. You must have DELETE permission on the objects you are changing.