Jive SBS uses various pieces of the Spring framework for dependency injection, security, data access, and more.
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. 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 application 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, application 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:
<!-- Root application definition - in spring-managerContext.xml -->
<bean id="ratingManagerImpl" class="com.jivesoftware.clearspace.community.RatingManagerImpl"
parent="jiveManager">
. . .
</bean>
<!-- Override -- possibly in your plugin spring.xml -->
<bean id="ratingManagerImpl" class="com.my.customized.MyRatingManagerImpl">
</bean>
-Djive.ws.disabled=true
What this nifty class does is allow you to override properties for any bean definition. So, say you want to change the cron expression for the user sync task. Whip up a spring.xml file in <jiveHome>/etc, and add this bit of XML:
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="properties">
<util:map>
<entry key="userDataSynchronizationTask.cronExpression" value="0 0 1 * * ?"></entry>
</util:map>
</property>
</bean>
Boom -- you've just changed the task timing of task from midnight everyday to 1AM (assuming I got the cron expression right ). It gets better: you're not limited to overriding scalar values. You can actually override what bean gets injected where by using "value-ref" instead of "value".
JivePropertyOverrideConfigurer does the same thing, but it grabs the value to override from JiveGlobals. Here it is in action setting which group manager implementation to use:
<bean class="com.jivesoftware.community.lifecycle.spring.JivePropertyOverrideConfigurer">
<property name="jivePropertyMappings">
<util:map>
<!-- beanID.beanProperty = jiveProperty value -->
<entry key="groupManagerImpl.groupManagerClassName" value="GroupManager.className"></entry>
</util:map>
</property>
</bean>