Benefits of open source in real life

04.05.2012.

Many things have been said about advantages of open source – lower cost, fewer bugs, unlimited usage, wide user community etc. But it is quite rare to find accounts of how open source helps developers and administrators in solving critical problems with the software they are using. Here is one example where we used availability of the source code to solve a critical configuration issue.

[16:14:12] Damir Kovačić: So the problem is that Liferay, when using LDAP for authentication, does not create users in LDAP when they are created in Liferay. Changes work ok.
[16:15:00] Marko Grbić: Yes, the problem appears for new users only.
[16:17:03] Goran Petanjek: Can you paste the stack trace?
[16:25:00] Marko Grbić: Caused by: javax.naming.InvalidNameException: Invalid name: cn=bruno,null; remaining name 'cn=bruno,null'
        at javax.naming.ldap.Rfc2253Parser.doParse(Rfc2253Parser.java:86)
        at javax.naming.ldap.Rfc2253Parser.parseDn(Rfc2253Parser.java:49)
        at javax.naming.ldap.LdapName.parse(LdapName.java:772)
        at javax.naming.ldap.LdapName.<init>(LdapName.java:108)
        at com.sun.jndi.ldap.LdapCtx.addRdnAttributes(LdapCtx.java:902)
        at com.sun.jndi.ldap.LdapCtx.c_bind(LdapCtx.java:393)
        at com.sun.jndi.ldap.LdapCtx.c_bind(LdapCtx.java:357)
        at com.sun.jndi.toolkit.ctx.ComponentContext.p_bind(ComponentContext.java:596)
        at com.sun.jndi.toolkit.ctx.PartialCompositeContext.bind(PartialCompositeContext.java:183)
        at javax.naming.InitialContext.bind(InitialContext.java:404)
        at com.liferay.portal.security.ldap.PortalLDAPExporterImpl.addUser(PortalLDAPExporterImpl.java:330)
        at com.liferay.portal.security.ldap.PortalLDAPExporterImpl.exportToLDAP(PortalLDAPExporterImpl.java:157)
        at com.liferay.portal.security.ldap.PortalLDAPExporterUtil.exportToLDAP(PortalLDAPExporterUtil.java:43)
        at com.liferay.portal.model.UserListener.exportToLDAP(UserListener.java:96)
        at com.liferay.portal.model.UserListener.onAfterUpdate(UserListener.java:72)
        at com.liferay.portal.model.UserListener.onAfterUpdate(UserListener.java:1)
[16:39:28] Goran Petanjek: Here is the line that causes the exception:
[16:39:43] Goran Petanjek: ldapContext.bind(name, new PortalLDAPContext(attributes));
[16:39:57] Goran Petanjek: because the name is: 'cn=bruno,null'
[16:40:09] Goran Petanjek: and it is so because LDAP creates it like that:
[16:41:33] Goran Petanjek: public String getUserDNName(
   long ldapServerId, User user, Properties userMappings)
  throws Exception {

  Binding userBinding = PortalLDAPUtil.getUser(
   ldapServerId, user.getCompanyId(), user.getScreenName(),
   user.getEmailAddress());

  if (userBinding != null) {
   return PortalLDAPUtil.getNameInNamespace(
    ldapServerId, user.getCompanyId(), userBinding);
  }

  StringBundler sb = new StringBundler(5);

  sb.append(
   GetterUtil.getString(
    userMappings.getProperty(_userDNFieldName), _DEFAULT_DN));
  sb.append(StringPool.EQUAL);
  sb.append(PropertyUtils.getProperty(user, _userDNFieldName));
  sb.append(StringPool.COMMA);
  sb.append(PortalLDAPUtil.getUsersDN(ldapServerId, user.getCompanyId()));

  return sb.toString();
 }
[16:42:03] Goran Petanjek: is it possible that PortalLDAPUtil.getUsersDN(ldapServerId, user.getCompanyId()) returns null?
[16:43:03] Marko Grbić: Why does it need companyId?
[16:43:10] Damir Kovačić: It is obviously required
[16:43:33] Goran Petanjek: public static String getUsersDN(long ldapServerId, long companyId)
  throws Exception {

  String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);

  return PrefsPropsUtil.getString(
   companyId, PropsKeys.LDAP_USERS_DN + postfix);
 }
[16:44:15] Goran Jakovljevic: Ldap is hierarcical, so that why it needs to provide company ID
[16:46:49] Marko Grbić: null should be replaced with ldap.users.dn=ou=users,dc=my-domain,dc=com
[16:47:20] Marko Grbić: somehow it fails to load this  and then we get null
[16:47:58] Damir Kovačić: I see. So the question is what is the value of the postfix variable in getUsersDN
[16:49:00] Goran Petanjek: public static String getPropertyPostfix(long ldapServerId) {
  return StringPool.PERIOD + ldapServerId;
 }
[16:49:48] Damir Kovačić: ok. Marko, what is ldapServerId? You can see that when you define LDAP server, can't you?
[16:50:38] Goran Petanjek:  public static final String LDAP_USERS_DN = "ldap.users.dn";
[16:51:31] Damir Kovačić: And StringPool.PERIOD? It is constant?
[16:51:38] Marko Grbić: ldap server id is 0
[16:51:39] Goran Petanjek: PERIOD is probably a dot.
[16:52:14] Marko Grbić: I see now that in the config file I have defined these properties without .0 at the end.
[16:52:17] Goran Petanjek: I think you just need to define property "ldap.users.dn.NN" where NN is long id of the ldap server
[16:52:35] Goran Jakovljevic: Well, that is solved, right?
[16:52:35] Marko Grbić: I defined the properties additionally in the GUI.
[16:52:51] Marko Grbić: that might be a problem, too.
[16:53:04] Marko Grbić: I corrected it and restarted.
[16:55:44] Marko Grbić: Now it created a user
[16:56:24] Damir Kovačić: What else to say but...use the source....

How was this achieved? It is straightforward to configure Eclipse projects to use source code attached to jars for the debugging purposes. For this example, attaching the source code to liferay libraries was done using following steps:

  1. Download source code from Liferay website
  2. Extract zip file to folder of choice
  3. Right click on a jar for which you want to attach the source code and chose Properties
  4. In “Java Source Attachment -> Location path:” browse to the location of the source directory in the selected jar name directory and chose OK.

Now all class files in the selected jar can be opened by Eclipse without using a decompiler. After attaching the source code to the jar file, you can open a class from the stack trace and put a breakpoint on the line where the error occurs (eg. PortalLDAPExporterImpl.java:330):

The last step is to restart the server in the debug mode and wait or make changes which cause the error so that the debugger can stop on the breakpoint. After that, it is much easier to figure out where lies the source of the problem by inspecting how the error was generated.