Showing posts with label Development. Show all posts
Showing posts with label Development. Show all posts

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).

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

Custom job method DFC code sample

Implementing a custom Documentum job method requires writing some non trivial DFC code. Below you'll find a sample using an abstract class which can be extended and re-used for any implementation of a job method.
Connection/helping methods are encapsulated into the AbstractJobMethod abstract class, so you can write custom classes that extend it. The only method to be implemented is execute(), where you have an active session (handled by the abstract class) and can perform the required actions against the repository.
The abstract class uses the CustomJobArguments, which is an extension of standard DfStandardJobArguments, providing some additional methods to access the passed job arguments.
Check How to create a custom job in Documentum for details on creation of custom job and method.

public class SomeCustomMethod extends AbstractJobMethod {
  public int execute() throws Exception {
    DfLogger.debug(this, "Started custom method implementation with user: " + session.getLoginUserName(), null, null);
    // custom method logic goes here
    return 0;
  }
}
////
public abstract class AbstractJobMethod implements IDfMethod, IDfModule {
// for BOF 1.0 the class must implement only the IDfMethod interface, the jar is deployed on JMS
// for BOF 2.0 it implements also IDfModule because it will be deployed into the repository and will run as a BOF module

  protected CustomJobArguments jobArguments;
  protected IDfTime startDate;
  protected IReportWriter reportWriter;
  protected IDfSession session;
  protected IDfSessionManager sessionManager;
  protected IDfClientX clientx;
  protected IDfClient client;

  public int execute(Map args, PrintWriter writer) throws Exception {
    setup(args, writer);
    try {
      int retCode = execute();
      printJobStatusReport(retCode);
      return retCode;
    } catch (Exception e) {
      DfLogger.error(this, "", null, e);
      reportWriter.emitLine("Error encountered: " + e.getMessage());
      reportWriter.closeOut(false);
      throw e;
    } finally {
      if (reportWriter != null)
      reportWriter.close();
      if (session != null)
        sessionManager.release(session);
    }
  }

  // the only method to be implemented in concrete subclasses
  public abstract int execute() throws Exception;

  public void setup(Map args, PrintWriter writer) throws DfMethodArgumentException, DfException, Exception {
    startDate = new DfTime();
    jobArguments = new CustomJobArguments(new DfMethodArgumentManager(args));
    setupMethodFactory(writer);
    String username = jobArguments.getUserName();
    String password = jobArguments.getString("password");
    String domain = jobArguments.getString("domain");
    String docbase = jobArguments.getDocbaseName();
    setupSessionManager(username, password, domain, docbase);
  }

  private void setupSessionManager(String username, String password, String domain, String docbase) throws DfServiceException, DfException {
    DfLogger.debug(this, String.format("setupSessionManager-> username[%s] password[%s] domain[%s] docbase[%s]", username, password, domain, docbase), null, null);
    clientx = new DfClientX();
    client = clientx.getLocalClient();
    sessionManager = client.newSessionManager();
    IDfLoginInfo loginInfoObj = clientx.getLoginInfo();
    loginInfoObj.setUser(username);
    if (password != null && !password.equals(""))
        loginInfoObj.setPassword(password);
    loginInfoObj.setPassword(password);
    loginInfoObj.setDomain(domain);
    sessionManager.setIdentity(docbase, loginInfoObj);
    session = sessionManager.getSession(docbase);
  }

  private void setupMethodFactory(PrintWriter writer) throws Exception {
    try {
      IDfId jobId = jobArguments.getJobId();
      ReportFactory reportfactory = new ReportFactory();
      if (writer != null) {
        DfLogger.debug(this, "uso reportFactory", null, null);
        reportWriter = reportfactory.getReport(jobArguments.getDocbaseName(), jobArguments.getUserName(), "", jobArguments.getMethodTraceLevel(), jobId, writer);
      } else {
        DfLogger.warn(this, "writer == null, uso SimpleReportWriter", null, null);
        reportWriter = new SimpleReportWriter();
      }
    } catch (Exception e) {
      DfLogger.error(this, "Failed to create report writer. Error: " + e.getMessage(), null, e);
      throw e;
    }
  }

  private void printJobStatusReport(int retCode) throws Exception {
    reportWriter.emitLineToReport("Return Code-> " + retCode);
    String jobStatus = null;
    IDfTime end_date = new DfTime();
    long min_duration = Utility.timeDiff(startDate, end_date) / 60L;
    if (retCode == 0)
      jobStatus = "Custom Job completed at " + end_date.asString("yyyy/mm/dd hh:mi:ss") + ". Total duration: " + min_duration + " minutes.";
    else if (retCode > 0)
      jobStatus = "Custom job completed with Warnings at " + end_date.asString("yyyy/mm/dd hh:mi:ss") + ". Total duration: " + min_duration + " minutes.";
    else
      jobStatus = "Custom job completed with Errors at " + end_date.asString("yyyy/mm/dd hh:mi:ss") + ". Total duration: " + min_duration + " minutes. Check job report for details.";
    updateJobStatus(jobStatus, jobArguments.getJobId());
    reportWriter.closeOut(retCode >= 0);
  }

  public void updateJobStatus(String sJobStatus, IDfId jobId) throws Exception {
    if (session == null) {
      DfLogger.error(this, "setJobStatus: (session==null)", null, null);
      throw new NullPointerException("setJobStatus: (session==null)");
    }
    try {
      IDfPersistentObject job = session.getObject(jobId);
      if (job == null)
        throw new DfException("Failed to retrieve dm_job object from id '" + jobId.getId() + "'.");
      job.setString("a_current_status", sJobStatus);
      job.save();
    } catch (Exception e) {
      throw e;
    }
  }
}
////
public class CustomJobArguments extends DfStandardJobArguments {

  protected IDfMethodArgumentManager methodArgumentManager;
 
  public CustomJobArguments(IDfMethodArgumentManager manager) throws DfMethodArgumentException {
    super(manager);
    methodArgumentManager=manager;
  }

  public String getString(String paramName) throws DfMethodArgumentException {
    return methodArgumentManager.getString(paramName);
  }

  public int getInt(String paramName) throws DfMethodArgumentException {
    return methodArgumentManager.getInt(paramName).intValue();
  }
}

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.

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.

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

Saturday, February 18, 2012

How to start a Documentum Workflow from DFC

Launching and running a Documentum Workdlow from DFC code is not such a trivial task.
Below is a DFC code sample of starting a Workflow and attaching a package to it:

public void startWorkflow(IDfSession session, String processName, String wfName, String supervisorName) throws DfException {
if (processName == null || processName.equals("")) {
DfLogger.error(this, "the process is empty", null, null);
throw new IllegalArgumentException("The process name not specified");
}

IDfProcess process = (IDfProcess) session.getObjectByQualification("dm_process where object_name = '" + processName + "'");
if (process == null) {
DfLogger.error(this, "process not found!", null, null);
throw new DfException("startWorkflow - No such process: " + processName);
}

IDfWorkflowBuilder workflowBuilder = session.newWorkflowBuilder(process.getObjectId());
workflowBuilder.getWorkflow().setObjectName(wfName);
workflowBuilder.getWorkflow().setSupervisorName(supervisorName);
if ((workflowBuilder.getStartStatus() != 0) || (!(workflowBuilder.isRunnable()))) {
DfLogger.warn(this, "startWorkflow - workflow '" + wfName + "' is not runnable or StartStauts=0!", null, null);
throw new DfException("cannot start Workflow!");
}
workflowBuilder.runWorkflow();

// Adding attachments:
IDfList attachIds = new DfList();
attachIds.appendId(new DfId("09024a8580235bc4"));

IDfList startActivities = workflowBuilder.getStartActivityIds();
int packageIndex = 0;
for (int i = 0; i < startActivities.getCount(); i++) {
IDfActivity activity = (IDfActivity) session.getObject(startActivities.getId(i));
workflowBuilder.addPackage(activity.getObjectName(), activity.getPortName(packageIndex),
activity.getPackageName(packageIndex), activity.getPackageType(packageIndex), null, false, attachIds);
}
}

How to save a SysObject with new version in DFC

If you want to save an (udpated) object with a new version, you won't find a method for that in the IDfSysObject implementation.
You will have to do 2 operations: checkout, then a checkin specifying a new version.
Well, there are 2 approaches to do that: using the IDfCheckoutOperation and IDfCheckinOperation operations or methods of the IDfSysObject class: checkout() and checkin(boolean keepLock, String versionLabels).
The second approach is quite simple as requires just 2 lines of code, while the first one is more complex, so let's see the code sample.
Beware: if you don't set the 'CURRENT' version on your new version, it will be the last, but not the current one. So it will be 'hidden'.
Find below code samples for the operations.

public IDfId saveAsNewVersion(IDfSysObject object) throws DfException {
try {
checkOut(object);
// update object here if necessary
IDfClientX clientX = new DfClientX();
IDfCheckinOperation ciop = clientX.getCheckinOperation();
ciop.setCheckinVersion(IDfCheckinOperation.NEXT_MAJOR);

IDfCheckinNode ciNode = (IDfCheckinNode) ciop.add(object);
// if you don't specify the CURRENT version this document won't be the current version
ciNode.setVersionLabels("CURRENT");
if (! ciop.execute()) {
DfLogger.error(this,"saveAsNewVersion: Save signed document with new version failed!", null, null);
}
IDfId id = ciNode.getNewObjectId();
DfLogger.debug(this, "Signed document saved with new version, ID: " + id.getId(),null,null);
return id;
} catch (DfException e) {
DfLogger.error(this, "",null,e);
throw e;
}
}

public void checkOut(IDfSysObject object) throws DfException {
IDfClientX clientX = new DfClientX();
IDfCheckoutOperation coop = clientX.getCheckoutOperation();
IDfCheckoutNode coNode = (IDfCheckoutNode) coop.add(object);
if (coNode == null || ! coop.execute()) {
throw new DfException("Could not checkout document: " + object.getObjectId());
}
}

Tuesday, January 24, 2012

How to hide Add Repository browser tree node in Webtop

Probably many developers faced the problem: how to hide the 'Add repository' node from the browser tree? No, there's no OOTB configuration for this.
The WDK development Guide says you have to make your custom component, with your class, jsp and even a tld. That's cumbersome!
There's a trick that will hide that node faster: just copy the browsertree.jsp into your custom layer, add a browsertree_component.xml definition (perhaps you already have it) extending it from the webtop layer and set your custom browsertree.jsp page. Now open your custom browsertree.jsp file and add at the end of it, inside the last javascript tag, the following JS code:

var addRepository = findMatchingElements('selectrepository','div');
addRepository.style.visibility='hidden';

function findMatchingElements(toMatch, tagname) {
var reMatch = new RegExp( toMatch, "i" ); // match and ignore case
if ( tagname == null ) tagname = "*"; // if not tagname passed, search all
var elems = document.getElementsByTagName(tagname);
for ( var e = 0; e < elems.length; ++e ) {
if (elems[e].id.match(reMatch))
return elems[e];
}
}

That's all! Deploy and see it ... hidden.

Saturday, December 3, 2011

Error while creating a copy of ACL template in DFC

If you try to create a copy of an ACL template (which has alias references) using the method saveAsNew(), you will receive an error like this:
2011-12-02 11:53:01,138 ERROR [main] com.mycompany.documentum.job.method - [DM_ACL_E_USER_NOT_EXIST]error: "The owner_name or accessor_name '%alias' given in the ACL 'myACL' does not exist."
DfException:: THREAD: main; MSG: [DM_ACL_E_USER_NOT_EXIST]error: "The owner_name or accessor_name '%alias' given in the ACL 'myACL' does not exist."; ERRORCODE: 100; NEXT: null

This is caused by a bug in DFC method saveAsNew() of DfACL - it does not copy the value of attribute acl_class, which defines the ACL type (template, regular, etc.)

So you have to make a clone with custom code, something like this:

public IDfACL cloneACL(IDfACL acl, String name) throws DfException {
IDfACL copy = (IDfACL) acl.getSession().newObject("dm_acl");
copy.setObjectName(acl.getObjectName());
copy.setDescription(acl.getDescription());
copy.setString("owner_name", acl.getString("owner_name"));
copy.setACLClass(acl.getACLClass());
for (int i = 0; i < acl.getAttrCount(); i++) {
if (acl.getAttr(i).isRepeating()) {
String attrName = acl.getAttr(i).getName();
for (int j = 0; j < acl.getValueCount(attrName); j++) {
copy.setRepeatingValue(attrName, j, acl.getRepeatingValue(attrName, j));
}
}
}
copy.save();
return copy;
}