Here's a run down of the changes you might need to make to get customizations written for version 1 working on version 2. This topic looks at upgrading actions, macros, widgets, themes, and web services (client and server components), and other customizations.

Summary of Version 2 Changes

General Plugin Changes

Upgrading FTL Files

Upgrading Themes

Upgrading Widgets

Upgrading Macros

Upgrading Actions

Upgrading Web Services

Upgrading Custom Authentication Providers

Upgrading API Uses

Summary of Version 2 Changes

Version 2 includes a few big changes to the conventions and frameworks on which the application is built. Many of these changes were made to improve extensibility by letting the application and extensions be more loosely coupled — and so be more durable during upgrade. Other changes that impact customizations were simply feature improvements in which some change to code is required.

Here's a high-level list of the changes that effect extension and customization code:

General Plugin Changes

For those extensions and customizations deployed as plugins (widgets, actions, macros, and web services), there are a few changes in version 2 with broad effect.

Upgrading FTL Files

Each new version includes changes to FreeMarker template (FTL) files and version 2 is no exception. Typically these changes are needed to support new or enhanced features. The FTL changelog included with a release provides a list of the FTL files that were changed from the previous version. However, changes in version 2 have pretty much touched every template. In most cases these changes require a simple search-and-replace to fix. But some changes are more substantial.

The information on upgrading themes suggests an incremental process for making your changes that could save you some aggravation.

The following list suggests the tips and best practices for upgrading your templates.

Removed and Renamed FTL Files

Version 1 FTL File Version 2 Change
community-document-picker.ftl Renamed to container-document-picker.ftl
community-thread-picker.ftl Renamed to container-thread-picker.ftl
import-callback-communitynntp.ftl Removed.
import-directory-error.ftl Removed.
import-directory-updatetags.ftl Removed.
import-directory.ftl Removed.
datepicker.ftl Renamed to datetimepicker.ftl

Upgrading Themes

Nearly all of your work upgrading themes will focus on upgrading FreeMarker template (FTL) files. The way you build and deploy themes is unchanged from version 1.

But wait — there's more. Having said that, the best practice recommendation in version 2 is to use widget-customized pages wherever possible as an alternative to custom FTLs. You can use widgets in more places than in version 1; check out the section on upgrading widgets for more.

Here's why you should use widgets:

Suggested Upgrade Process

Use the following steps as a systematic way to upgrade your themes.

  1. Consider which custom FTLs in your themes can be replaced by customizing the page with widgets.
  2. Use jive.devMode Jive property for debugging. By default, the application hides FreeMarker errors in themes. With dev mode on, you'll see them.
  3. Disable themes while upgrading them.
  4. Enable your custom FTL files one at a time. Debugging incrementally will make the process smoother.

Upgrading Widgets

Widgets can be used much more broadly in version 2 than previously, so most of your widget-specific upgrade changes are related to this broader support. In version 1 a system or space administrator could use widgets only to assemble a space or community overview page. In version 2 both administrators and users can customize page layouts in several areas. Widgets can be used on the community home page (which has been rendered from main.ftl), a user's personal home page, a community/space overview page, a project overview page.

Note that unless your widget is extremely simple, you're likely to also need to keep in mind API changes and FreeMarker changes. For example, version 2 requires that widgets acquire references to manager beans using Spring. The rest of widget development model — artifacts involved, how you deploy them, and so on — is unchanged from version 1.

When upgrading a widget (or developing a new one on version 2), you use the @WidgetTypeMarker annotation to specify which of the display contexts your widget is allowed in. Deciding which contexts to allow is an important part of your widget's design. For example, you might decide that a widget that takes a very individual-oriented set of property values (such as the Tag widget, which displays content associated with a particular tag) isn't useful in high-level contexts such as the instance or space home pages (where the broad set of people viewing might want views on a large variety of tags).

The @WidgetTypeMarker annotation supports values from the WidgetType enumeration. Here's an example that includes all of those values:

@WidgetTypeMarker({WidgetType.HOMEPAGE, WidgetType.PERSONALIZEDHOMEPAGE, 
    WidgetType.COMMUNITY, WidgetType.PROJECT})
@PropertyNames("myProperty")
public class MyWidget extends BaseWidget {
    // Implementation code omitted.
}

In order to reduce the tight coupling between the widget views (which are typically built using FreeMarker) and the rest of the application, the widget context that was previously populated with a reference to the current context via a JiveContext instance has been modified so that the widget view no longer has access to that instance. That means that you'll need to provide everything that your widget view needs through the properties that the widget interface requires. Typically you'll create a widget class that extends BaseWidget and then you'll override this method:

protected Map<String, Object> loadProperties(WidgetContext widgetContext, ContainerSize size)

So in the previous version, you might have had something like this in the FTL file that's your widget's view:

${widgetContext.jiveContext.communityManager.rootCommunity.ID?c}

Because the JiveContext instance has been removed from the WidgetContext class, you'll need to provide your view with the properties it needs explicitly in your widget. Here's an example:

public class MyWidget extends BaseWidget {

    // Declare a property variable and setter for injection by Spring.
    private CommunityManager communityManager;
    public void setCommunityManager(CommunityManager cm) {
        this.communityManager = cm;
    }

    protected Map<String, Object> loadProperties(WidgetContext widgetContext, 
        ContainerSize size)
    {
        Map<String, Object> properties = super.loadProperties(widgetContext, size);
// Implementation code omitted. properties.put("rootCommunityID", communityManager.getRootCommunity().getID()); return properties; } }

Finally, because you can create a widget that exists in multiple parts of the application (the homepage, a personalized homepage, a community, a project), you'll sometimes want to change the behavior of your widget based on where the widget is being rendered. You can determine the render context of your widget by checking the type of the WidgetContext class that you're given in the loadProperties method. Here's some example code that shows how you can determine what context the widget is in:

public class MyWidget extends BaseWidget {
    protected Map<String, Object> loadProperties(WidgetContext widgetContext, ContainerSize size) {
        Map<String, Object> properties = super.loadProperties(widgetContext, size);
        if (widgetContext.getWidgetType() == WidgetType.COMMUNITY) {
            CommunityWidgetContext cwc = (CommunityWidgetContext)widgetContext;
            // Do something specific for the community
        }
        else if (widgetContext.getWidgetType() == WidgetType.HOMEPAGE) {
            HomepageWidgetContext hwc = (HomepageWidgetContext)widgetContext;
            // Do something specific for the homepage
        } 
        else if (widgetContext.getWidgetType() == WidgetType.PERSONALIZEDHOMEPAGE) {
            PersonalizedHomepageWidgetContext phwc = 
                (PersonalizedHomepageWidgetContext)widgetContext;
            // Do something specific for the personalized homepage    
        }
        else if (widgetContext.getWidgetType() == WidgetType.PROJECT) {
            ProjectWidgetContext wwc = (ProjectWidgetContext)widgetContext;
            // Do something specific for the project
        }
    
        properties.put("rootCommunityID", communityManager.getRootCommunity().getID());
        return properties;
    }
}

Upgrading Macros

Information about upgrading macros will arrive in a future release.

Upgrading Actions

Version 2 uses Struts rather than WebWork. Actually WebWork 2 became Struts 2 when the two open source communities merged. The application was simply upgraded to use the new Struts, which is the latest version of WebWork. And Struts. Well, you get the idea.

In addition to FreeMarker syntax changes, you'll find migration changes described in the Struts documentation from Apache. The Apache site also describes the key differences between WebWork and Struts. Also, be sure to see the information on upgrading FTLs, upgrading API uses, and general plugin changes.

Here are a couple of things you'll notice immediately:

Important: Overriding actions, which was occasionally successful in version 1, is likely to get you into trouble in version 2. Best not to do it.

Upgrading Web Services

Several changes were made that could impact your web services code, whether the code is for a client or new service.

Upgrading Custom Authentication Providers

As in version 1, in version 2 you can write your own components to provide custom authentication.

Upgrading an Authentication Provider

Authentication is substantially changed in version 2. For example, you no longer need to pass an AuthToken instance with method calls. Every request is guaranteed to have an authentication context. In version 2, the app uses Spring Security (formerly Acegi), which is designed to fit well with the Spring framework. If you're implementing your own authentication provider, your components are based on Spring Security; in some cases you might be able to use the classes included with Spring Security, such as Authentication implementations.

For more detail, see Authentication and Authorization. Also, be sure to read the Spring Security documentation, which includes information you'll find useful.

Upgrading API Uses

Some of the most important changes in version 2 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 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 the application must use the same conventions in order to interact reliably with the application. 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 the app's 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 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, 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:

Work with Containers for Projects and Spaces/Communities

Version 2 introduces projects, which, like spaces, 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 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 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 the app 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