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.

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.