Being without dfc.properties

If you ever tried to implement some kind of integration between j2ee application and Documentum, you probably know that putting DFC libraries into j2ee environment is always a pain in ass because DFC follows neither best practices nor common sense:

To resolve second problem I invented following class:

/**
 * @author Andrey B. Panfilov <andrew@panfilov.tel>
 */
public class DfcPropertiesLoader {

    private final Map<String, Object> _properties;

    public DfcPropertiesLoader(Map<String, Object> properties) {
        _properties = properties;
    }

    public final void load() {
        Properties filtered = getKnownProperties(_properties);
        if (filtered == null || filtered.isEmpty()) {
            return;
        }
        DfPreferences.getInstance().loadProperties(filtered, false);
    }

    private Properties getKnownProperties(Map<String, Object> properties) {
        Properties filtered = new Properties();
        for (Field field : DfPreferences.class.getDeclaredFields()) {
            Preference preference = field.getAnnotation(Preference.class);
            if (preference == null) {
                continue;
            }
            String preferenceName;
            try {
                preferenceName = toString(field.get(null));
            } catch (IllegalAccessException e) {
                continue;
            }
            if (!properties.containsKey(preferenceName)) {
                continue;
            }
            Object value = properties.get(preferenceName);
            if (value == null) {
                continue;
            }
            if (preference.repeating()) {
                filtered.put(preferenceName, toArray(value));
            } else {
                if (value instanceof List || value.getClass().isArray()) {
                    continue;
                }
                filtered.put(preferenceName, toString(value));
            }
        }
        return filtered;
    }

    private String[] toArray(Object object) {
        if (object instanceof List) {
            List list = (List) object;
            String[] result = new String[list.size()];
            for (int i = 0, n = result.length; i < n; i++) {
                result[i] = toString(list.get(i));
            }
            return result;
        }
        if (!object.getClass().isArray()) {
            return new String[] {toString(object) };
        }
        String[] result = new String[Array.getLength(object)];
        for (int i = 0, n = result.length; i < n; i++) {
            result[i] = toString(Array.get(object, i));
        }
        return result;
    }

    private String toString(Object object) {
        if (object instanceof String) {
            return (String) object;
        }
        return object.toString();
    }

}

which allows to build DFC preferences from any context without using dfc.properties file, for example, below is a configuration for Spring:

<bean id="dfcPropertiesLoader" class="DfcPropertiesLoader" init-method="load">
    <constructor-arg name="properties">
        <map>
            <entry key="dfc.docbroker.host" value="docu70dev02"/>
            <entry key="dfc.docbroker.port" value="1489"/>
            <entry key="dfc.bof.registry.repository" value="ssc_dev"/>
            <entry key="dfc.bof.registry.username" value="dm_bof_registry"/>
            <entry key="dfc.bof.registry.password" value="dm_bof_registry"/>
        </map>
    </constructor-arg>
</bean>

Q & A. V

Hi Andrey,
do you have a working example/configuration how to use Principal Authentication in Documentum?
I just read it here: http://www.emc.com/collateral/software/white-papers/h8843-dfc-session-management-wp.pdf
Thanks, Jens

Below you can find a very basic demonstration of Principal Authentication concept:

public static void main(String[] args) throws Exception {
	// this is just a demo code which writes trust.properties
	// file - perform manual configuration instead!
	ClassLoader classLoader = Test.class.getClassLoader();
	URL dfcProp = classLoader.getResource("dfc.properties");
	String trustLocation = dfcProp.getPath()
			.replace("dfc.properties", "trust.properties");
	FileOutputStream trustStream = new FileOutputStream(new File(trustLocation));
	trustStream.write("ssc_dev.username=dmadmin\n".getBytes());
	trustStream.write("ssc_dev.password=dmadmin\n".getBytes());
	trustStream.write("ssc_dev.domain=\n".getBytes());
	trustStream.close();

	IDfClientX clientX = new DfClientX();
	IDfSessionManager sessionManager = clientX.getLocalClient().newSessionManager();
	sessionManager.setPrincipalName("dmadmin");
	IDfSession session = sessionManager.getSession("ssc_dev");
	System.out.println(session.getLoginInfo().getPrincipalMode());
}

i.e. to leverage functionality of com.documentum.fc.client.DfDefaultPrincipalSupport class (default implementation of com.documentum.fc.client.IDfPrincipalSupport) we need to put into classpath file named “trust.properties” which must have following format:

# to setup credentials for all docbases
# use asterisk (*) instead of docbase name
docbase_name.username=<superuser name>
docbase_name.password=<superuser password>
docbase_name.domain=...
docbase_name.userArg1=...
docbase_name.userArg2=...
docbase_name.securityMode=...

DFC has two interfaces responsible for Principal Authentication:

  • com.documentum.fc.client.IDfPrincipalSupport – creates session for specific userprincipal, default implementation is com.documentum.fc.client.DfDefaultPrincipalSupport, custom implementation may be injected either through IDfClient.setPrincipalSupport() method or through dfc.principal.support system property, i.e. -Ddfc.principal.support=implementation_class
  • com.documentum.fc.client.IDfTrustManager – provides superuser’s IDfLoginInfo, used only by com.documentum.fc.client.DfDefaultPrincipalSupport, default implementation is com.documentum.fc.client.DfDefaultTrustManager, custom implementation may be injected through dfc.trust.manager system property, i.e. -Ddfc.trust.manager=implementation_class

I would suggest to check com.documentum.web.formext.session.UserPrincipalAuthenticationScheme class for understanding how this feature works on application server side. Also you may override com.documentum.fc.client.DfDefaultPrincipalSupport class to make some mapping between principals and docbase users, for example:

IDfPrincipalSupport ps = new DfDefaultPrincipalSupport() {

	@Override
	public IDfSession getSession(String docbaseName, String principalName) 
			throws DfPrincipalException {
		return super.getSession(docbaseName, "dmadmin");
	}
};

IDfClientX clientX = new DfClientX();
IDfClient client = clientX.getLocalClient();
client.setPrincipalSupport(ps);

IDfSessionManager sessionManager = client.newSessionManager();
sessionManager.setPrincipalName("fake_user");
IDfSession session = sessionManager.getSession("ssc_dev");
System.out.println(session.getLoginUserName());

UPD.

What is the benefit of this instead of using dmadmin credentials and creating a ticket for other users?

Both options are unreliable:

  • on the one hand, principal authentication allows you separate logic between different layers, it’s always good
  • on the other hand, by default recent DFC versions do not leverage functionality of session manager’s private session pool, this means that every IDfSessionManager#getSession() call actually performs IDfSessionManager#newSession(), that in case of “principal authentication” is accompanied by generation of new ticket and extra authentication – this is really slow
  • on the another hand, ticket concept is broken in DFC, I would even say that implementation was written by idiots and never worked properly, let’s explain. Every login ticket has expiration time, to prevent unexpected expiration when login ticket is in use (actual for long running workflow tasks) DFC does following:
    • upon successful login DFC replaces old ticket with brand new one that has expiration interval equal to max_login_ticket_timeout in dm_server_config
    • also DFC spawns special task (see com.documentum.fc.client.impl.session.TicketWatchdog) that intended to renew expiring tickets
    • code below (sorry for poor quality) demonstrates that this implementation is broken (I specially set max_login_ticket_timeout to 10 to not wait 30 days):
      /**
       * @author Andrey B. Panfilov <andrew@panfilov.tel>
       */
      public class Test {
      
          public static void main(String[] args) throws Exception {
      
              IDfClientX clientX = new DfClientX();
              IDfClient client = clientX.getLocalClient();
              IDfSessionManager sessionManager = client.newSessionManager();
              String docbase = "ssc_dev";
              sessionManager.setIdentity(docbase, new DfLoginInfo("dmadmin",
                      "dmadmin"));
              IDfSession session = sessionManager.getSession(docbase);
              int timeout = session.getServerConfig().getInt(
                      "max_login_ticket_timeout");
              String ticket = session.getLoginTicket();
              sessionManager.release(session);
              sessionManager.clearIdentities();
              sessionManager.setIdentity(docbase, new DfLoginInfo("dmadmin", ticket));
              dumpTicket(sessionManager, docbase);
              sessionManager.release(sessionManager.getSession(docbase));
              dumpTicket(sessionManager, docbase);
              while (true) {
                  try {
                      System.out.println("sleeping " + ((timeout + 1) * 60 * 1000));
                      Thread.sleep((timeout + 1) * 60 * 1000);
                      sessionManager.release(sessionManager.getSession(docbase));
                      dumpTicket(sessionManager, docbase);
                  } catch (InterruptedException ex) {
                      Thread.currentThread().interrupt();
                  }
              }
          }
      
          private static void dumpTicket(IDfSessionManager sessionManager,
                  String docbase) throws Exception {
              Object loginInfoManager = getValue(sessionManager, "m_loginInfoManager");
              if (loginInfoManager == null) {
                  return;
              }
              Method getEffectiveLoginInfo = getMethod(loginInfoManager.getClass(),
                      "getEffectiveLoginInfo", IDocbaseSpec.class);
              if (getEffectiveLoginInfo == null) {
                  return;
              }
              IDfLoginInfo loginInfo = (IDfLoginInfo) getEffectiveLoginInfo.invoke(
                      loginInfoManager, new DocbaseSpec(docbase));
              String ticket = loginInfo.getPassword();
              if (!ticket.startsWith("DM_TICKET=")) {
                  return;
              }
              String decoded = new String(new BASE64Decoder().decodeBuffer(ticket
                      .substring("DM_TICKET=".length())));
              System.out.println(extractLong(decoded, "expire_time INT S 0")
                      - extractLong(decoded, "create_time INT S 0"));
          }
      
          private static long extractLong(String string, String pattern) {
              int index = string.indexOf(pattern);
              if (index < 0) {
                  return 0;
              }
              return Long.valueOf(string.substring(index + pattern.length() + 1,
                      index + pattern.length() + 11));
          }
      
          public static Field getField(Class<?> clazz, String name) {
              Class<?> cls = clazz;
              while (true) {
                  try {
                      Field field = cls.getDeclaredField(name);
                      field.setAccessible(true);
                      return field;
                  } catch (NoSuchFieldException e) {
                      if (cls == Object.class) {
                          return null;
                      }
                      cls = cls.getSuperclass();
                  }
              }
          }
      
          public static Object getValue(Object object, String fieldName)
              throws IllegalAccessException {
              Field field = getField(object.getClass(), fieldName);
              if (field == null) {
                  return null;
              }
              return field.get(object);
          }
      
          public static Method getMethod(Class<?> clazz, String name,
                  Class<?>... parameterTypes) {
              Class<?> cls = clazz;
              while (true) {
                  try {
                      Method method = cls.getDeclaredMethod(name, parameterTypes);
                      method.setAccessible(true);
                      return method;
                  } catch (NoSuchMethodException e) {
                      if (cls == Object.class) {
                          return null;
                      }
                      cls = cls.getSuperclass();
                  }
              }
          }
      
      }
      
      

      result is:

      [dmadmin@docu70dev01 ~]$ java Test
      300
      600
      sleeping 660000
      Exception in thread "main" DfAuthenticationException:: THREAD: main; MSG: [DM_SESSION_E_AUTH_FAIL]error:  "Authentication failed for user dmadmin with docbase ssc_dev."; ERRORCODE: 100; NEXT: null
              at com.documentum.fc.client.impl.docbase.DocbaseExceptionMapper.newException(DocbaseExceptionMapper.java:52)
              at com.documentum.fc.client.impl.connection.docbase.MessageEntry.getException(MessageEntry.java:39)
              at com.documentum.fc.client.impl.connection.docbase.DocbaseMessageManager.getException(DocbaseMessageManager.java:137)
              at com.documentum.fc.client.impl.connection.docbase.netwise.NetwiseDocbaseRpcClient.checkForMessages(NetwiseDocbaseRpcClient.java:310)
              at com.documentum.fc.client.impl.connection.docbase.netwise.NetwiseDocbaseRpcClient.applyForObject(NetwiseDocbaseRpcClient.java:653)
              at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection$8.evaluate(DocbaseConnection.java:1313)
              at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection.evaluateRpc(DocbaseConnection.java:1072)
              at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection.applyForObject(DocbaseConnection.java:1305)
              at com.documentum.fc.client.impl.docbase.DocbaseApi.authenticateUser(DocbaseApi.java:1867)
              at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection.authenticate(DocbaseConnection.java:432)
              at com.documentum.fc.client.impl.connection.docbase.DocbaseConnectionManager.authenticateConnection(DocbaseConnectionManager.java:350)
              at com.documentum.fc.client.impl.connection.docbase.DocbaseConnectionManager.assignConnection(DocbaseConnectionManager.java:196)
              at com.documentum.fc.client.impl.connection.docbase.DocbaseConnectionManager.getDocbaseConnection(DocbaseConnectionManager.java:99)
              at com.documentum.fc.client.impl.session.SessionFactory.newSession(SessionFactory.java:23)
              at com.documentum.fc.client.impl.session.PrincipalAwareSessionFactory.newSession(PrincipalAwareSessionFactory.java:44)
              at com.documentum.fc.client.impl.session.PooledSessionFactory.newSession(PooledSessionFactory.java:49)
              at com.documentum.fc.client.impl.session.SessionManager.getSessionFromFactory(SessionManager.java:120)
              at com.documentum.fc.client.impl.session.SessionManager.newSession(SessionManager.java:70)
              at com.documentum.fc.client.impl.session.SessionManager.getSession(SessionManager.java:177)
              at Test.main(Test.java:43)
      
    • nobody knows about this bug because typical Documentum application never lives so long (30 days) without restart šŸ™‚

Do we have to use the dmadmin account or any other superuser?

Any superuser account

Do we have to set the dmadmin password in plain text?

You may generate aek.key (do not use aek.key installed on CS!), put it into application’s classpath and encrypt password using this aek.key. Alternatively implement your own com.documentum.fc.client.IDfTrustManager.

My apologize for the last comment

Actually, I always try do double-check anything before posting it in my blog, but today I made a mistake: I decided that comment from Dearash is a 1st April joke, but after some research I realized it was not a joke – that was a home truth about support. Dearash’s comment had came from EMC’s network:



So, it looks like Dearash wanted to emphasize that EMC support is unable to help customers with their difficulties – original quote from Sukumar is:

Because in my case iā€™m facing some aspect related issue which creating the object through webtop and it neither allows me to create nor open the existing document instances of that type..Looks like a bug and still SR is pending with EMC about this issue

and suggest to look for alternative sources of support.

Sorry for stupid joke about ECN.

The anniversary 10th Documentum release will be written on Java

Today I had a private conversation with IIG ECD semi-god:

Actually, John did want to present Documentum 10 roadmap on the upcoming EMC World, but I’m not going to attend with event, so, I’m sharing Documentum 10 roadmap in this blog. EMC realized that Content Server is a black sheep in their Documentum product stack because it is written on C/C++ (all other products are written on Java) which causes a lot of issues related to development, maintenance, performance, security, etc. And now they are going to completely rewrite Content Server on Java. What we should expect from this renovation? I bet you cannot event imagine all advantages of upcoming changes, the brief description of some of them is:

  • REST and DFS will be a part of Content Server – no more dedicated servers for these services
  • Content Server will include a generic HTTP-gateway for content transfer operations – no more ACS and thumbnail servers
  • you will be able to work with Documentum objects using EJB API
  • no more client-side TBOs – all custom logic will be proceeded on Content Server side
  • embedded scripting language for server-side customization – no more dmbasic, method server, etc
  • Quartz-based job scheduling – more flexible job scheduling
  • instant processing of workflow activities
  • on-demand content transformations
  • the grammar of DQL will be greatly extended, for example you will able to write something like: grant read to ‘user/group_name’ on dm_document objects where …

etc, etc …