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.

Friday, November 30, 2012

Documentum 6.7 CTS troubleshooting: after installation

If you've came here I guess you're in same trouble I was while installing and configuring Documentm CTS in 6.7.x versions.
If you've read the first part of my CTS Troubleshooting Guide, but didn't find the solution, try your luck by reading this article too.
1. Server returns HTTP response code 404
If you find in the logs the following error:
com.documentum.cts.plugin.advancedpdf.AdvancedPDFProcessor - Exception within  processResponse() method: Server returned HTTP response code: 404 for URL: http://localhost/exponentwsa/exponentwsa.asmx/DeleteJob

Open in the browser the following URL: http://localhost:80/exponentwsa/exponentwsa.asmx/DeleteJob
The page shows "HTTP 404.2 - Not Found" error.
Solution: To fix this, open IIS Manager (from Server Manager) then find and open 'ISAPI and CGI Restriction'. The entries ASP.NET vXXX must have Restriction value 'Allowed'.

2. Server returns HTTP response code 500
This error occurs when you find in the logs something like:
com.documentum.cts.plugin.advancedpdf.AdvancedPDFProcessor - Exception within  processResponse() method: Server returned HTTP response code: 500 for URL: http://localhost/exponentwsa/exponentwsa.asmx/AddJob

Open the URL in the browser to be check you really have 500 code response  Then open IIS Manager and go to Application Pools. Check all the pool ExponentWSA is started. Open the pool settings and check that .NET Framework v2.0.XXs is selected. If you changed the value, restart the server.

3. CTS does not create renditions
One possible cause of CTS malfunction is the user set to run the CTS & Adlib services. There are 7 services, from which 3 of them (Adlib FMR, Adlib Process Manager and Documentum CTS Admin. Agent) must run as 'Local System', while the remaining 4 (Adlib Exponent Connector, Adlib Exponent Manager, Documentum Content Transformation Services, Documentum Content Transformation Monitor Services) with [DOMAIN\]SUPERUSER (SUPERUSER - normaly the installation owner).
Restart the CTS services if you've changed any settings.

4. CTS can perform a transformation only when the server is remotely connected (Windows 2008 R2 x64)
There's an known issues for Windows 2008 R2 x64: renditions work only when the server is remotely connected. The process adexps.exe is closed when the install owner logs off.
To fix this, you have to edit the file: ..\Program Files (x86)\Adlib\Process Manager\ProcessManagerInitSettings.xml (default path):
Change lines (all found):
<ProcessLaunchType>LaunchAndWatch</ProcessLaunchType>
<ProcessSessionType>UserSession</ProcessSessionType>
to
<ProcessLaunchType>LaunchAndWatchSession</ProcessLaunchType>
<ProcessSessionType>SystemSession</ProcessSessionType>

Save the file and restart the CTS services.

These are the main issues encountered with CTS. If you face other issues, feel free to post them into comments, I could give some ideas.

Documentum 6.7 CTS troubleshooting: before installing

Installing Documentum CTS (including DTS/ADTS/MTS/etc.) became a bit tricky in versions 6.7.x since it requires more pre-requisite components to be installed:
1. IIS Server
2. ASP.NET
3. Message Queuing
4. .NET Framework

Moreover you have to install a certain set of subfeatures. These services/features can be installed in Windows using Server Manager. Start by adding the role Web Server (IIS), then check the following features/services to be installed for it:

IIS Web Server: ASP.NET, ISAPI



- Under Application Development:
* ASP.NET
* .NET Extensibility
* ISAPI Extensions
* ISAPI Filters



IIS Management Tools: IIS Management Compatibility




- Under Management Tools:
* IIS Management Console
* IIS Management Compatibility + all subfeatures





Message Queuing Services

Then go to Add features section and add the feature Message Queuing Services with the following subfeatures:
* Message Queuing Server
* Directory Service Integration
* HTTP Support





Well this could be enough to go on with the CTS products installation, however your system still might miss some configurations that will prevent your CTS from working properly.
First, go to IIS Manager, open Sites and check you have a site called: Default Web Site
Open the browser (for ex. IE) and open the following address: http://localhost 
If default IIS web site is working you will see IIS welcome page (Welcome in different languages).

Ensure you have Microsoft Office installed (Word, Excel, PowerPoint). 

If you've done all the steps above, you can go on with installation of CTS.
During the installation you might encounter the following error: The installation cannot continue until the following conditions are met: Microsoft Office must be installed.
Well, even you've installed Microsoft Office, the CTS installer might not 'see' it, so you have to manually intervene in Windows Registry. Check if you have the following keys in the Registry (if you don't - create them):
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\office_outlook
Name: DisplayName
Type: String
Value: Microsoft Office Outlook
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\office_word
Name: DisplayName
Type: String
Value: Microsoft Office Word
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\office_excel
Name: DisplayName
Type: String
Value: Microsoft Office Excel
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\office_powerpoint
Name: DisplayName
Type: String
Value: Microsoft Office PowerPoint

Now the installation should complete successfully. You can start the CTS services. All looks fine, but... no, some errors, again!
If you have a 64-bit OS you might find in the CTS log the following kind of errors:
Unable to instantiate the following MP: com.documentum.cts.plugin.advancedpdf.AdvancedPDFPlugin
java.lang.UnsatisfiedLinkError: D:\Documentum\CTS\lib\JNI_WindowsService.dll: Can't load AMD 64-bit .dll on a IA 32-bit platform

The cause is the CTS installer which, unlike the CS installer, includes just a 32-bit JDK, while for your OS you need the 64-bit one. So, preferably before configuring a CTS instance for a repository, you have to perform the following workaround, (described also in the CTS installation guide):
1. Install a 64-bit 1.6.x JVM in a folder, then rename the Documentum's 32-bit java folder (for ex: %Documentum%\java\1.6.0_17) by adding _32 (%Documentum%\java\1.6.0_17_32), then create in the same path a new folder with the original java name (%Documentum%\java\1.6.0_17). Then copy all the contents from the 64-bit Java to the newly created folder (%Documentum%\java\1.6.0_17)

2. Set environment variables (JAVA_HOME, PATH) to point to this Java version installation.

3. Once the Java has been updated, update the Windows registry value for CTS Admin Agent to use the older 32-bit Java:
Key:
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Appache Software
Foundation\Procrun 2.0\CtsAdminAgent\Parameters\Java
Property Name: JVM
Property Value: C:\PROGRA~1\DOCUME~1\java\1.6.0_17_32\jre\bin\server\jvm.dll

Now you can configure CTS instances for your repository(ies).
If you encounter further issues with CTS performance, see the second part of this CTS Troubleshooting Guide.

Tuesday, October 30, 2012

JMS 6.7 SP1 error connecting to inexisting docbase

Recently I've installed Content Server 6.7 SP1 and the patch 08. When I start the Java Method Service, I find the following error in the logs:
INFO [STDOUT] (main) 10:02:37,932 ERROR [main] com.documentum.mthdservlet.MethodConfig - DfNoServersException:: THREAD: main; MSG: [DM_DOCBROKER_E_NO_SERVERS_FOR_DOCBASE]error: "The DocBroker running on host ([HOST]:1489) does not know of a server for the specified docbase ([INSTALLATION_OWNER])"; ERRORCODE: 100; NEXT: null

So it seems JMS client tries to connect to a docbase which has a name equal to the installation owner of the current docbase. Error in JMS configuration? Nope - checked all configurations, everything's correct. So what's the problem?
Ok, I checked again the stack trace and saw the problem is coming from method populateDocbaseNames of MethodConfig class, which is in mthdservlet.jar library.
Decompiled the jar, opened the method and here's the big surprise from EMC developers:
...
  String str1 = (String)localEnumeration.nextElement();
  if ((!Utils.isNull(str1)) && (str1.toLowerCase().startsWith("docbase")))
...

these lines read all the docbase names from web.xml, found in ...\ServerApps.ear\DmMethods.war\WEB-INF\
Opening the file we see the tags:
    <init-param>
      <param-name>docbase-my_docbase</param-name>
      <param-value>my_docbase</param-value>
    </init-param>

So it should read this docbase and all other available & configured repositories, the tag <param-name> having values of format 'docbase-[DOCBASE_NAME]'.
Ok, but I have only 1 repository configured. Scrolling a bit, I find another tag:
    <init-param>
      <param-name>docbase_install_owner_name</param-name>
      <param-value>dmadmin</param-value>
    </init-param>

Having the code above - startsWith("docbase") it will read also this tag and interpret it as a docbase name. Ok, then I decompiled an older version of mthdservlet.jar and found a bit different code:
  if ((!Utils.isNull(str1)) && (str1.toLowerCase().startsWith("docbase-")))

Here it is! A genious EMC developer removed that dash after docbase: startsWith("docbase-") Well, sh*t happens, even to geniuses.

So while we wait for a patch for this patch :) we can use the old version of mdthdservlet.jar or just ignore this error, as it has no impact on JMS work.

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");