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