This post is the seventh and final post in a series of blog posts about customizing for Clearspace 2.x. The previous posts covered:

  1. Customizations in Clearspace 2.x

  2. Upgrading Themes and FTL Files.

  3. Widgets in Clearspace 2.x

  4. Macros for Clearspace 2.0

  5. Web Services

  6. Custom Authentication and User Data Providers

 

Some of the most important changes in version 2 of Clearspace required that the underlying API be changed. To get things working on version 2, you'll need to change not only references to these in your Java classes, but also references to them in your FreeMarker templates.

 

The following describes the most significant changes.

  • Inject Dependencies with Spring

  • Work with Containers for Projects and Spaces/Communities

  • Use Transaction Support When Updating Multiple Tables

  • Use Manager Interfaces to Work with Managed Things

  • Handle Content As XML

  • Other API Changes

 

Inject Dependencies With Spring

For version 2, the Clearspace codebase was refactored to support Spring, a framework with modules for inversion of control, transaction management, authentication, and more. This has potentially the biggest impact on your code because components that extend Clearspace must use the same conventions in order to interact reliably with Clearspace. For example, as described below, dependency injection replaces version 1 conventions for using JiveContext and factory classes to get manager instances.

 

Dependency injection is a way to obtain a dependency, such as an object on which your code relies, by having the instance "injected" at run time  -- that is, the instance is set with a JavaBeans-style accessor method. In Clearspace code, manager class instances are generally now injected rather than obtained via a context or factory class method.

 

A class that supports injection includes a class-level ++ variable for holding the instance it depends on and provides a set* accessor method through which Spring can inject the instance (in other words, your code provides a property of the interface's type). Your setter implementation assigns the injected instance to the variable, which your code can then use to access the instance. By specifying the interface on which your code depends  -- rather than an implementation of the interface  -- you have a loosely-coupled dependency. This lets you avoid breakage that might occur if the implementation changes.

 

Note: You can ensure that the setter you provide actually has a configured type associated with it by annotating the setter method with @org.springframework.beans.factory.annotation.Required. If the injected type is not known to Spring configuration, then the Spring container will throw an exception at run time.

 

 

The following example illustrates the basics of dependency injection with Spring: declare private class-level variables for manager instances and declare setter methods through which Spring will inject the instances. Code for version 1 techniques has been commented out in favor of the Spring conventions.

 

// Variables to hold the manager instances.
private DocumentManager documentManager;
private FreemarkerManager freemarkerManager;


// Setters for injecting manager instances.
public void setDocumentManager(DocumentManager documentManager) {
    this.documentManager = documentManager;
}
public void setFreemarkerManager(FreemarkerManager freemarkerManager) {
    this.freemarkerManager = freemarkerManager;
}

private String getDocContent(String documentID)
{
    // ... Declare variables ... 
    
        // Don't use JiveContext (from JiveApplication) to get a DocumentManager 
        // instance. The instance has been injected through the setter above.
        // DocumentManager documentManager = JiveApplication.getContext(AuthFactory
        //    .getSystemAuthToken()).getDocumentManager();
        
        // Use the manager to get a document by its ID.
        document = documentManager.getDocument(documentID);
        Map properties = new HashMap();
        
        // ... Set FreeMarker properties from the document, then apply 
        // a template with them ...
        result = applyFreemarkerTemplate(properties, FREEMARKER_HTML_FILE);
        
    // ... catch exceptions ...

    return result;
}

private String applyFreemarkerTemplate(Map properties,
        String templateName) {
        
    // ... Declare variables ... 
    
        // Don't use getInstance to get the FreemarkerManager instance. It has 
        // been injected.
        // FreemarkerManager freemarkerManager = FreemarkerManager.getInstance();
        
        // ... Use the manager to set up FreeMarker configuration ...
        config = freemarkerManager.getConfiguration(ServletActionContext.getServletContext());
        if (properties != null) {
            // Process the FreeMarker template and store the resulting HTML as a String
            result = applyFreemarkerTemplate(config, properties, templateName);
        }
        
    // ... catch exceptions ...

    return result;
}

While you can optionally name the specific interface implementation you want to have injected, Spring in Clearspace supports a feature known as autowiring. In autowiring, you merely include the setter method (using JavaBeans naming conventions) with a single parameter of the interface's type. To optionally specify the implementation you want injected, you include a spring.xml file that associates the implementation class with your setter.

 

Remove JiveContext uses that retrieve managers. In version 1 the JiveContext interface was a popular convention to get instances of the various manager interfaces  -- CommunityManager, UserManager, DocumentManager, and so on. In version 2 this convention is, depending on the case, either unavailable or unreliable. For example, if you've written a plugin that has a static initializer that tries to bootstrap with call to JiveContext, Clearspace startup will likely fail.

 

Instead, use dependency injection as described above. In fact, to stay out of trouble, the general rule in version 2 is "Don't use JiveContext."

 

Remove getInstance calls that retrieve managers. This includes FreemarkerManager.getInstance, but also all of the *Factory.getInstance methods you might have used in version 1. In version 2 most of the factory classes have been removed. You should replace your calls to their getInstance methods with a property for setting the instance from Spring as described above. Here's a list of the removed classes:

 

  • GroupManagerFactory

  • UserManagerFactory

  • AvatarManagerFactory

  • BanManagerFactory

  • PollManagerFactory

  • SearchQueryLoggerFactory

  • StatusLevelManagerFactory

  • TagManagerFactory

  • WidgetDAOFactory

 

Work with Containers for Projects and Spaces/Communities

Version 2 introduces projects, which, like spaces (known as communities in Clearspace Community), collect content such as documents, discussions, and blogs. Projects also add tasks as a content type. To organize the conceptually similar projects and spaces, the API was changed to introduce the idea of a container. The interfaces that represent projects and spaces  -- Project and Community  -- now inherit from a common JiveContainer interface.

 

In practical terms, this means that some methods taking an instance of the Community interface now take a JiveContainer instance instead. The following code works to get the latest document in either a project or space, for example, because they inherit from JiveContainer and both can contain documents. To see how your code might be affected, search the Javadoc index for occurrences of JiveContainer in method signatures and return types.

 

public Document getLatestDocument(JiveContainer container) {
    if (getDocumentCount(container) == 0) {
        return null;
    }
    DocumentResultFilter filter = new DocumentResultFilter();
    filter.setSortOrder(DocumentResultFilter.DESCENDING);
    filter.setSortField(JiveConstants.MODIFICATION_DATE);

    // Round down the lower date boundary by 1 day.
    filter.setModificationDateRangeMin(ThreadResultFilter
        .roundDate(container.getModificationDate(), 24 * 60 * 60));
    filter.setNumResults(10);
    Iterable<Document> documents = documentManager.getDocuments(container, filter);
    if (documents.iterator().hasNext()) {
        return documents.iterator().next();
    }
    else {
        return null;
    }
}

By the way, this getLatestDocument method is now exposed by the DocumentManager interface; in version 1 this method was exposed by the Document interface. Methods such as this one were moved as part of a larger effort to migrate such functionality into managers.

 

Use Transaction Support When Updating Multiple Tables

Version 2 supports transaction management through Spring with the @Transactional annotation. When the work of your code results in updates (including deletes) to data in multiple database tables, supporting transactional behavior can help to ensure that the updates are made consistently (that is, all of the effected tables are updated or none are).

 

If you're explicitly supporting transactions with @Transactional, you'll need to add an AspectJ compilation step. The AspectJ compiler weaves into your code the support needed to include at load time your transactional method calls in a transaction context.

 

You'll want to add transaction support if your code updates multiple database tables as while executing a method. While the need to support transactions is fairly rare if you're doing all your work through the Clearspace API, keep in mind that your calls to API set* methods might result in database updates. If you're not sure whether your code's work results in database updates, it's not a bad idea probably to add the @Transactional annotation to the method.

 

Adding support for transactions is pretty easy. Here's what you do:

 

 

Use Manager Interfaces to Work with Managed Things

Much of the Clearspace code was rewritten to clarify (or create) relationships between instances of manager interfaces and instances of what they manage. The result is an API that's more intuitive, but it also might mean changes to your code.

 

In general, methods designed to handle types of things  -- documents, attachments, discussion messages, and so on  -- were moved from a "containing" type to a manager type. For example, in version 1 the method getAttachmentCount was exposed by the interfaces BlogPost, Document, and ForumMessage ; each of these could have attachments, and getAttachmentCount was how you got the count of them. In version 2, you get the count of attachments with the AttachmentManager.getAttachmentCount(AttachmentContentResource) method. The AttachmentContentResource is a marker interface extended by BlogPost, Document, ForumMessage, and other things that can have attachments.

 

So in version 2 the rule of thumb, when looking for a way to manage types of things, is to look for a manager of those things.

 

Handle Content As XML

Version 2 includes significant changes in the way Clearspace handles content. Improving the rich text editor (which blogs, documents, and discussions threads all use) included completely reworking the way that content is handled in conversions between plain text (wiki markup), rich text, and HTML (for previewed and published content).

 

In particular, content is set and received as XML. In version 1, the content-related accessors that get and set content body, subject and so on received and returned String instances. In version 2, these methods handle content as instances of org.w3c.dom.Document. Version 2 interfaces that used content accessor methods  -- including Document, PrivateMessage, ForumMessage, BlogPost, and others  -- still include methods such as getPlainBody for retrieving content without markup.

 

For plugins that include actions, you can extend JiveActionSupport for several methods helpful in converting content from one format to another.

 

While version 2 features a model for setting and getting content as XML, there's also an exception: the Macro interface render method still returns content as a String. It has to parse as well-formed XML, but it's still a string.

 

Other API Changes

 

  • In some cases, methods that took a JiveObject instance now take an instance of an interface that extends JiveObject. The marker interfaces CommentContentResource and AttachmentContentResource were created to indicate an object's support for comments or attachments. Look for these types in the Javadoc index to find out whether you need to update your own code.

 

 

The information above along with more details can be found in the Upgrading Extensions to 2.0 documentation.