Saturday, November 29, 2008

Tricking the RemoteGroup

Last time I promised you to write of another useful trick that helped me optimize group queries in Atlassian Crowd.

If you've ever used Crowd web interface you would've noticed that, Crowd when querying for a group also retrieves a list of all the group members. It may be too much of an overhead to do it before even displaying group attributes. Fortunately there's a way to outsmart Crowd!

If you take a look at the RemoteDirectory interface, you'll be able to find those three methods:
  1. public List<RemotePrincipal> findAllGroupMembers(final String groupName);
  2. public RemoteGroup findGroupByName(String groupName, boolean onlyFetchDirectMembers);
  3. public RemoteGroup findGroupByName(String groupName);
First one returns list of principals that are members of the group. Second and the third methods return an object representing group with the given name. Crowd uses latter methods when you click on group name in Crowd's web interface, it then displays a page with group description and its attributes. You may even not want to see the list of principals that is accessible through the Members tab.

Problem is that RemoteGroup instances returned by the second and third methods must already be populated with a list of members; otherwise Members tab will be empty. So there seem to be not much of a choice, either waste time querying for principals or have non functional connector.

Let's take a closer look at RemoteGroup, it has an interesting method setDirectory(Directory directory). It appears that Crowd uses this directory if it sees that the object is not fully initialized. Thus we can return an empty RemoteGroup instance with only group name and activity flag set. Later Crowd will do the following:
directory.getImplementation().findAllGroupMembers(...)
if it needs a list of members.

Here's my extension of the Directory class:
class ReferencingDirectory extends Directory {
  private static final long serialVersionUID = 1L;
  /**
   * Reference to the remote directory to avoid object creation.
   */
  private final RemoteDirectory remoteDirectory;

  public ReferencingDirectory(RemoteDirectory remoteDirectory) {
    Utils.checkNull("Remote directory", remoteDirectory);
    this.remoteDirectory = remoteDirectory;
  }

  @Override
  public RemoteDirectory getImplementation() {
    return remoteDirectory;
  }

  @Override
  public String getImplementationClass() {
    return remoteDirectory.getClass().getCanonicalName();
  }
}

I keep an instance of it in my implementation of RemoteDirectory and pass it to RemoteGroup whenever I need it.

That's it! Queries for groups are much faster now, and if really needed members are queried separately.

No comments: