Advanced Topics
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 1
1.1 Creating the repository type .................................................................................................. 4
1.2 Enabling authentication with the repository type ......................................................... 5
1.3 Add additional actions to the repository type ............................................................... 18
1.4 Enabling the repository configuration panels ............................................................... 32
1.5 Optional Exercise ..................................................................................................................... 37
2.1 Creating a Plug-‐in API ............................................................................................................ 38
2.2 Using a Plug-‐in API .................................................................................................................. 40
3.1 Creating an open action ......................................................................................................... 54
3.2 Enabling the custom open action ....................................................................................... 58
4.1 IBM ECM Task Manager ......................................................................................................... 59
4.2 Custom Asynchronous Tasks ............................................................................................... 59
4.3 Creating a Custom Asynchronous Task ............................................................................ 60
4.4 Registering the Custom Asynchronous Task .................................................................. 64
4.5 Creating a Custom Scheduling Pane .................................................................................. 65
4.6 Scheduling Your Custom Task ............................................................................................. 68
2 IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
In this course you will complete exercises focused on advanced IBM Content
Navigator Plug-‐in development topics. The course builds upon the techniques you learned in the earlier development course.
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 3
There may be cases where you encounter a custom content source or legacy system that you need to integrate into your Content Navigator user interface. Generally it is recommended you first consider creating a Content Management Interoperability
Services (CMIS) provider for this type of source. That allows you to use many of the existing Content Navigator features, without requiring any user interface development, and provides a standard mechanism for accessing the source.
However, it is sometimes a challenge to define a CMIS provider for a specific source type (perhaps because it is not really an enterprise content management system) or the situation requires custom interaction with the source and very little of the
Content Navigator existing features will be used. In that case, you may only want custom authentication and a small number of the existing services in the Content
Navigator framework for the custom content source and custom panels and services for everything else. To accommodate this rare use case, Content Navigator provides the ability to define a custom repository type in a plug-‐in. In this exercise, you will create a simple plug-‐in repository type.
Right-‐click on the “
” package in the eclipse project you created in the base “
” and use the “
” menu to create a “
”. In the new
repository type wizard set the class name to “
”.
4 IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
Figure 1.1
Click “Ok” to create the new repository type. The wizard generates five files, the main Java class, “
”, two JavaScript classes,
“
” and
“
”, and two HTML files, which will serve as the HTML templates for the JavaScript classes. Like other plug-‐in types, the main Java class defines the base repository type. The wizard generates a java.util.Map, which will be used to map the various actions the repository type will support. Initially no actions are mapped, but you will create several to enable this repository type. Similar to other plug-‐in types, the repository type has both an identifier and a name used to represent the repository type in the
Content Navigator administration. The “
” method is responsible to handling an action call to your repository type. When the user initiates an action on your repository type in the user interface, such as a call to log in, Content Navigator will route the call to your “
” method. There is no need for you to implement any user interface source code for this to happen. The Content Navigator
framework handles it automatically.
In addition, to the identifier, name and perform action methods, the repository type definition defines two widget classes with the methods,
“
” and “
”. These allow you to specify custom panels for the repository configuration in the Content
Navigator administration. Typically a repository definition will have a general panel, which is used to collect the basic information necessary to connect to the repository.
Additional panels are available after the administrator has connected to the repository, allowing the administration to add any additional configuration necessary to enable the repository. A plug-‐in provided repository type has the same capability and the wizard generated the shell for the two user interface widgets that
will be used in the repository configuration.
The final methods in the repository type class are, “
”, “
”,
“
” and “
”. The “
” method allows you to provide a custom icon to use in the Content Navigator administration to represent the repository. You can also optionally provide a custom repository
JavaScript model extension for your repository type, using the
“
” method. The “
” and “
” methods are used to handle the authentication and disconnect from the repository. The “logon” method returns a “
” type, which will represent your
repository type on the server-‐side. You must implement this in order to enable your repository type.
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 5
In order to enable the repository type for use within Content Navigator, you will need to create a new class that extends the “
” and enable the log in and log out methods in your repository type. Right-‐click on the
“
” package and create a new package called
“
”. You will use this new package to isolate the classes you create for the repository type.
Figure 1.2
Right-‐click on the newly created package and create a new Java class called
“
”, with the superclass
“
”.
6 IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
Figure 1.3
From the resources directory in the course materials, copy the “repository.json” file to your “
” package. For the remaining exercises, you
will be using this JSON file to simulate the presence of a real source.
Open the newly created “
” file and make the following changes:
package com.ibm.ecm.extension.repositoryType;
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 7
import java.io.InputStream; import com.ibm.ecm.configuration.RepositoryConfig; import com.ibm.ecm.extension.PluginRepositoryConnection; import com.ibm.json.java.JSONObject; public class RepositoryConnection extends PluginRepositoryConnection { private JSONObject repositoryJSON; public RepositoryConnection(String type, String userId,
RepositoryConfig repositoryConfig ) throws Exception { super (type, userId, userId );
// TODO Auto-generated constructor stub
InputStream inStream = null; try {
// Load the repository JSON inStream = this.getClass().getClassLoader().getResourceAsStream("com/ibm/ecm/exten sion/data/repository.json"); repositoryJSON = JSONObject.
parse (inStream);
} finally {
} if (inStream != null) {
} inStream.close();
} public JSONObject getRepositoryJSON() {
} return repositoryJSON;
}
The changes above simply update the constructor to read the repository JSON definition and provide a method that can be used to obtain the parsed JSON. The next step is to enable the “
” and “
” methods in the main repository type
Java class. To do that, you will create action handler classes for logon and logout.
Right-‐click on the “
” package and create a new Java class called “
” and make the following changes to the newly created file:
package com.ibm.ecm.extension.repositoryType; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.ibm.ecm.configuration.RepositoryConfig; import com.ibm.ecm.extension.PluginRepositoryConnection; import com.ibm.ecm.extension.PluginRepositoryLogonException; import com.ibm.ecm.extension.PluginServiceCallbacks;
8 IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
import com.ibm.ecm.json.JSONResponse; import com.ibm.json.java.JSONArray; import com.ibm.json.java.JSONObject; public class RepositoryLogon { public
}
RepositoryLogon() {
// TODO Auto-generated constructor stub
@SuppressWarnings("unchecked") public void performAction(PluginServiceCallbacks callbacks,
RepositoryConfig repositoryConfig, HttpServletRequest request,
HttpServletResponse response) throws Exception {
String loginRepoId = request.getParameter("repositoryId");
String serverName = request.getParameter("serverName");
String userid = request.getParameter("userid");
String password = request.getParameter("password");
JSONResponse jsonResponse = new JSONResponse(); if (repositoryConfig.isEmpty() && serverName != null){ repositoryConfig.setServerName(serverName);
}
PluginRepositoryConnection connection = logon(callbacks, request, userid, password, repositoryConfig); if (connection != null) {
Map<String, PluginRepositoryConnection> pluginRepositoryConnectionList = (Map<String,
PluginRepositoryConnection>)
(request.getSession((true)).getAttribute("plugin_repositories")); if (pluginRepositoryConnectionList == null) { pluginRepositoryConnectionList = new
HashMap<String, PluginRepositoryConnection>(); pluginRepositoryConnectionList); request.getSession(true).setAttribute("plugin_repositories",
} pluginRepositoryConnectionList.put(loginRepoId, connection);
JSONArray servers = new JSONArray(); jsonResponse.put("servers", servers);
JSONObject server = new JSONObject(); server.put("connected", true); server.put("id", loginRepoId); servers.add(server);
}
} callbacks.writeJSONResponse(jsonResponse, response); public PluginRepositoryConnection logon(PluginServiceCallbacks callbacks, HttpServletRequest request, String userid, String password,
RepositoryConfig repositoryConfig) throws
PluginRepositoryLogonException {
// For this example, user1, user2 and ibm will valid with any password
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 9
if ("user1".equalsIgnoreCase(userid) ||
"user2".equalsIgnoreCase(userid) || "ibm".equalsIgnoreCase(userid)) { try {
RepositoryConnection connection = new
RepositoryConnection("IBMDevCourseRepository", userid, repositoryConfig); return connection;
} catch (Exception e) { throw new PluginRepositoryLogonException(e);
} else {
} throw new
PluginRepositoryLogonException(PluginRepositoryLogonException.
LOGON_FAI
LURE_BAD_USERID_OR_PASSWORD );
}
}
}
In this case, since this is merely an example of how to create a repository type, the actual log in method will be hardcoded to accept three user ids with any password and it will generate an instance of the RepositoryConnection class you already created. If the user id entered by the user does not match, you throw a standard invalid credentials exception. The “
” method, which will be called by the main repository type Java class, is checking for a cache of plug-‐in provided repository types in the session first. If none is found, it will create a new HashMap, to store a handle to the repository connection, making it available to other actions that will be initiated after log in. Finally the method uses the PluginServiceCallbacks
to stream a standard Content Navigator repository JSON payload back to the client.
Now, create another class in the “
” package called “
” and make the following changes to the class:
package com.ibm.ecm.extension.repositoryType; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import com.ibm.ecm.configuration.RepositoryConfig; import com.ibm.ecm.extension.PluginRepositoryConnection; import com.ibm.ecm.extension.PluginServiceCallbacks; public class RepositoryLogoff { public RepositoryLogoff() {
// TODO Auto-generated constructor stub
} public void performAction(PluginServiceCallbacks callbacks,
RepositoryConfig repositoryConfig, HttpServletRequest request,
HttpServletResponse response) throws Exception {
String repositoryId = request.getParameter("repositoryId");
RepositoryConnection connection =
1
0
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
(RepositoryConnection)callbacks.getPluginRepositoryConnection(repositor yId);
} logoff(callbacks, request.getSession(false), connection); public void logoff(PluginServiceCallbacks callbacks, HttpSession session, PluginRepositoryConnection connection) throws Exception { callbacks.getLogger().logInfo(this, "logoff", "Logged off");
// Since the connection is a dummy connection there is no actual work to do in this method
}
}
In this case, your plug-‐in repository type is not actually creating any resources or establishing any handles that need to be disconnected, so the log off does not need to do anything. However, if this were a real data source, it is extremely likely this method would be necessary and failing to implement anything here could result in
runtime issues.
Now that you have log on and log off action classes, it is time to enable log on and log off from your main repository type Java class. Open
“
” from the
“
” package and make the following
changes: import java.lang.reflect.Method; import java.util.HashMap; import java.util.Locale; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import com.ibm.ecm.configuration.RepositoryConfig; import com.ibm.ecm.extension.PluginRepositoryConnection; import com.ibm.ecm.extension.PluginRepositoryType; import com.ibm.ecm.extension.PluginServiceCallbacks; import com.ibm.ecm.extension.repositoryType.RepositoryLogoff; import com.ibm.ecm.extension.repositoryType.RepositoryLogon; import com.ibm.ecm.json.JSONMessage; import com.ibm.ecm.json.JSONResponse;
/**
* Provides an abstract class that is extended to create a class defining a custom repository type provided by the
* plug-in. Additional PluginService classes are implemented to provide the different services of the repository type.
* <p>
* For enterprise content servers providing Content Management
Interoperability Services (CMIS), the CMIS repository
* type can be used to access the server. A plug-in provided repository type can be used to extend IBM Content Navigator
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 1
1
* to support access other data sources not traditionally considered enterprise content servers.
* <p>
* Note that additional configuration is needed by the Content
Navigator administrator to define the actual repository
* of the plug-in provided repository type and add it to a desktop.
*/ public class IBMDevCourseRepository extends PluginRepositoryType { private static final Map<String, String>
IBMDevCourseRepositoryActionMap = new HashMap<String, String>() { private static final long serialVersionUID =
7212659949366854547L;
{ put("logon",
"com.ibm.ecm.extension.repositoryType.RepositoryLogon"); put("logoff",
"com.ibm.ecm.extension.repositoryType.RepositoryLogoff");
}
}; public void performAction(PluginServiceCallbacks callbacks,
RepositoryConfig repositoryConfig, String action, HttpServletRequest request, HttpServletResponse response) throws Exception { if ( IBMDevCourseRepositoryActionMap .containsKey(action)) { callbacks.getLogger().logDebug( this , "performAction",
"Executing \"" + action + "\"...");
Object obj =
Class.
forName ( IBMDevCourseRepositoryActionMap .get(action)).newInstance(
);
Method m = obj.getClass().getMethod("performAction",
PluginServiceCallbacks.
class , RepositoryConfig.
class ,
HttpServletRequest.
class , HttpServletResponse.
class ); response);
} else m.invoke(obj, callbacks, repositoryConfig, request,
{ callbacks.getLogger().logError( this , "performAction",
"${PluginClassName} does not support the repository action \"" + action
+ "\".");
// return JSON with an error as well for the client
JSONResponse jsonResponse = new JSONResponse(); jsonResponse.addErrorMessage( new JSONMessage(20005,
"${PluginClassName} does not support the action", "The repository action \""+action+"\" is not supported.", null , null , null )); response.getWriter().write(jsonResponse.serialize());
}
}
@Override public String getId() {
} public return
@Override
"IBMDevCourseRepository";
String getName(Locale locale) { return "IBMDevCourseRepository";
1
2
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
;
} return "IBM Development Course Repository";
@Override public String getConfigurationDijitClass() { return
"iBMDevCoursePluginDojo.IBMDevCourseRepositoryGeneralConfigurationPane"
} public String getConnectedConfigurationDijitClass() { return
"iBMDevCoursePluginDojo.IBMDevCourseRepositoryConfigurationParametersPa
ne";
}
@Override public PluginRepositoryConnection logon(PluginServiceCallbacks callbacks, HttpServletRequest request, String userid, String password,
RepositoryConfig repositoryConfig) throws Exception {
PluginRepositoryConnection connection =
RepositoryLogon.class.newInstance().logon(callbacks, request, userid, password, repositoryConfig); return connection;
}
@Override public void logoff(PluginServiceCallbacks callbacks, HttpSession session, PluginRepositoryConnection connection) throws Exception {
RepositoryLogoff.class.newInstance().logoff(callbacks, session, connection);
}
@Override public String getRepositoryModelClass() {
} return "";
} public String getIconClass() { return "";
}
Both the log on and log off actions need to be added to your action map, they will be available in the performAction method. Likewise, you changed the “
” and
“
” methods in the repository type, so that they now call your new action
handler classes to handle the log on and logoff.
At this point, you are ready to test your new repository type in Content Navigator.
Open up the Content Navigator administration and open your plug-‐in in the plug-‐in
administration. It should now list the new repository type.
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 1
3
Figure 1.4
Go to the repository configuration pane and you should now be able to create a new
IBM Development Course Repository.
Figure 1.5
1
4
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
Create a new “
”. Since you have not add anything to the configuration widgets created by the wizard you will be presented with the default ID and display name. All repository types in Content Navigator must have a unique identifier and a display name to use when showing the repository to
the user. Enter the display name “
” and the
“
” button will be enabled.
Figure 1.6
Click “Connect…” and you will be prompted for credentials. You’re repository type should only accept the user ids “user1”, “user2” and “ibm”. Enter a valid user ID and
you can now access the configuration tab.
Figure 1.8
Save the repository type definition and go to the desktops configuration pane, create a new desktop called “
”, with an ID of “
”. Set the
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 1
5
newly created repository type as the authenticating repository and on the layout tab, select the “
” feature as the only available feature.
Figure 1.9
1
6
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
Figure 1.10
Save the desktop and switch to the “
” desktop to test your new plug-‐in repository type. After logging in you will see an error indicating the repository type
does not support the necessary actions to populate the browse pane.
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 1
7
Figure 1.11
The next step is to add the actions required to populate a folder tree and list view in
Content Navigator. In order to do that, you will need to add several actions to your repository type. However, first you are going to create several new model classes to represent the objects used by the user interface. Right-‐click on the
“
” package and create a new class called
“
” and make the following changes to the class:
package com.ibm.ecm.extension.repositoryType; import com.ibm.json.java.JSONObject;
JSONObject attributeDefinitionJSON) { public class AttributeDefinition { private RepositoryConnection connection = null; private JSONObject attributeDefinitionJSON = null; public AttributeDefinition(RepositoryConnection connection,
} this.connection = connection; this.attributeDefinitionJSON = attributeDefinitionJSON; public String getId() {
1
8
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
} return (String)attributeDefinitionJSON.get("id"); public String getName() { return (String)attributeDefinitionJSON.get("name");
} public String getType() {
} public RepositoryConnection getRepositoryConnection() { return connection;
} public int getMaxLength() { if (attributeDefinitionJSON.containsKey("maxLength")) { return
((Long)attributeDefinitionJSON.get("maxLength")).intValue();
} return 0;
} if (attributeDefinitionJSON.containsKey("type")) {
} return (String)attributeDefinitionJSON.get("type"); return "xs:string"; public boolean isSystem() { if (attributeDefinitionJSON.containsKey("system")) { return
(Boolean)attributeDefinitionJSON.get("system");
}
} return false;
}
This class is used to model the attribute definitions in the repository.json file you imported into your project earlier. You will need to create two more to represent content items and content classes. Create a new Java class called “
” and
make the following changes to the new file: package com.ibm.ecm.extension.repositoryType; import java.io.InputStream; import com.ibm.json.java.JSONArray; import com.ibm.json.java.JSONObject; public class ContentItem { private RepositoryConnection connection; private JSONObject contentItemJSON; public ContentItem(RepositoryConnection connection, JSONObject contentItemJSON) { this.connection = connection;
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 1
9
} this.contentItemJSON = contentItemJSON; public String getContentClassId() { return (String)contentItemJSON.get("contentClassId");
} public ContentClass getContentClass() { return connection.getContentClass(getContentClassId());
} public String getId() { return (String)contentItemJSON.get("id");
} public String getName() {
// By convention we'll use the first defined attribute as the name
AttributeDefinition nameAttributeDef = getContentClass().getAttributeDefinitions()[0]; return getAttributeValue(nameAttributeDef.getId()).toString();
} public int getAttributeCount() {
JSONObject attributes =
(JSONObject)contentItemJSON.get("attributes"); return attributes.size();
}
@SuppressWarnings("unchecked") public String[] getAttributeIds() {
JSONObject attributes =
(JSONObject)contentItemJSON.get("attributes");
String[] attributeIds = new String[attributes.size()]; attributes.keySet().toArray(attributeIds); return attributeIds;
} public Object getAttributeValue(String attributeId) {
JSONObject attributes =
(JSONObject)contentItemJSON.get("attributes"); return attributes.get(attributeId);
} public void setAttribute(String attributeId, Object value) {
// Note: This is not a permanent change to the json file, just temporary to the in-memory copy
JSONObject attributes =
(JSONObject)contentItemJSON.get("attributes"); attributes.put(attributeId, value);
}
/**
* If the item is a folder, this returns folder contents.
* @return
*/
2
0
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
}
@SuppressWarnings("unchecked") public String[] getContentIds() {
JSONArray contentItemIdsJSON =
(JSONArray)contentItemJSON.get("contentItemIds"); if (contentItemIdsJSON == null) { return new String[0];
}
String[] contentItemIds = new
String[contentItemIdsJSON.size()]; contentItemIdsJSON.toArray(contentItemIds); return contentItemIds;
} public boolean isFolder() {
} public boolean isDocument() {
} public String getContentType() {
} return getContentClass().isFolderClass(); return getContentClass().isDocumentClass(); if (isFolder()) { return "folder";
} return (String)contentItemJSON.get("contentType");
Please note the above code will not compile yet. There are additional changes you will need to make to the RepositoryConnection class in order to fully enable the model objects.
Right-‐click on the “com.ibm.ecm.extension.repositoryType” package and create the
last model object, “
” and make the following changes to the new file: package com.ibm.ecm.extension.repositoryType; import com.ibm.json.java.JSONArray; import com.ibm.json.java.JSONObject; public class ContentClass { private RepositoryConnection connection; private JSONObject contentClassJSON; private String[] attributeDefinitionIds; private AttributeDefinition[] attributeDefinitions; public ContentClass(RepositoryConnection connection, JSONObject contentClassJSON) {
} this.connection = connection; this.contentClassJSON = contentClassJSON; public String getId() {
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 2
1
} return (String)contentClassJSON.get("id"); public String getName() {
} return (String)contentClassJSON.get("name");
@SuppressWarnings("unchecked") public String[] getAttributeDefinitionIds() { if (attributeDefinitionIds == null) {
JSONArray attributeDefinitionIdsJSON = (JSONArray) contentClassJSON.get("attributeDefinitionIds"); attributeDefinitionIds = new
String[attributeDefinitionIdsJSON.size()]; attributeDefinitionIdsJSON.toArray(attributeDefinitionIds);
}
} return attributeDefinitionIds; public AttributeDefinition[] getAttributeDefinitions() { if (attributeDefinitions == null) {
String[] attributeDefinitionIds = getAttributeDefinitionIds(); attributeDefinitions = new
AttributeDefinition[attributeDefinitionIds.length]; for (int i = 0; i < attributeDefinitionIds.length; i++) { attributeDefinitions[i] = connection.getAttributeDefinition(attributeDefinitionIds[i]);
}
} return attributeDefinitions;
} public boolean isFolderClass() { return contentClassJSON.containsKey("semanticType") && contentClassJSON.get("semanticType").equals("folder");
} public boolean isDocumentClass() { return contentClassJSON.containsKey("semanticType") && contentClassJSON.get("semanticType").equals("document");
}
}
Open the “
” file and make the following changes: package com.ibm.ecm.extension.repositoryType; import java.io.InputStream; import java.util.Collection; import java.util.Hashtable; import com.ibm.ecm.configuration.RepositoryConfig; import com.ibm.ecm.extension.PluginRepositoryConnection;
2
2
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
import com.ibm.json.java.JSONArray; import com.ibm.json.java.JSONObject; public class RepositoryConnection extends PluginRepositoryConnection { private JSONObject repositoryJSON; private Hashtable<String, AttributeDefinition> attributeDefinitions; private Hashtable<String, ContentClass> contentClasses; public RepositoryConnection(String type, String userId,
RepositoryConfig repositoryConfig) throws Exception { super try
(type, userId, userId);
InputStream inStream =
{ null ;
// Load the repository JSON inStream = this .getClass().getClassLoader().getResourceAsStream("com/ibm/ecm/exten sion/data/repository.json"); repositoryJSON = JSONObject.
parse (inStream);
// Cache the attribute definitions attributeDefinitions = new Hashtable<String,
AttributeDefinition>();
JSONArray attributeDefinitionsJSON = (JSONArray) repositoryJSON.get("attributeDefinitions"); for (int i = 0; i < attributeDefinitionsJSON.size(); i++) {
JSONObject attributeDefinitionJSON =
(JSONObject) attributeDefinitionsJSON.get(i);
String attributeDefinitionId = (String) attributeDefinitionJSON.get("id");
AttributeDefinition attributeDefinition = new
AttributeDefinition(this, attributeDefinitionJSON); attributeDefinition);
} attributeDefinitions.put(attributeDefinitionId,
ContentClass>();
// Cache the content classes contentClasses = new Hashtable<String,
JSONArray contentClassesJSON = (JSONArray) repositoryJSON.get("contentClasses"); for (int i = 0; i < contentClassesJSON.size(); i++) {
JSONObject contentClassJSON = (JSONObject) contentClassesJSON.get(i);
String contentClassId = (String) contentClassJSON.get("id");
ContentClass contentClass = new
ContentClass(this, contentClassJSON); contentClasses.put(contentClassId, contentClass);
}
} finally { if (inStream != null ) {
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 2
3
} public
}
}
} inStream.close();
JSONObject getRepositoryJSON() { return repositoryJSON; public ContentClass[] getContentClasses() {
ContentClass[] contentClassArray = new
ContentClass[contentClasses.size()];
Collection<ContentClass> contentClassCollection = contentClasses.values(); contentClassArray = contentClassCollection.toArray(contentClassArray); return contentClassArray;
} public AttributeDefinition getAttributeDefinition(String attributeDefinitionId) { if
(attributeDefinitions.containsKey(attributeDefinitionId)) { return attributeDefinitions.get(attributeDefinitionId);
} throw new RuntimeException("Attribute definition not found with id " + attributeDefinitionId the repository JSON.");
+ ". There is likely an error in the format of
} public ContentClass getContentClass(String contentClassId) { if (contentClasses.containsKey(contentClassId)) { return contentClasses.get(contentClassId);
} throw new RuntimeException("Content class not found with id
" + contentClassId the repository JSON.");
+ ". There is likely an error in the format of
} public ContentItem getRootItem() throws Exception {
JSONArray contentItemsJSON = (JSONArray) repositoryJSON.get("contentItems");
// By convention, the first item is the root
JSONObject contentItemJSON = (JSONObject) contentItemsJSON.get(0);
} return new ContentItem(this, contentItemJSON); public ContentItem getContentItem(String contentItemId) throws
Exception {
JSONArray contentItemsJSON = (JSONArray) repositoryJSON.get("contentItems");
// Scan the list looking for the first item with the id for (int i = 0; i < contentItemsJSON.size(); i++) {
2
4
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
JSONObject contentItemJSON = (JSONObject) contentItemsJSON.get(i);
{ if (contentItemId.equals(contentItemJSON.get("id")))
} return new ContentItem(this, contentItemJSON);
} throw new Exception("Content item not found with id: " + contentItemId);
}
}
The above changes will allow the other objects you created to compile. The
RepositoryConnection class is now caching attribute definitions and content class definitions found in the repository.json file and provided methods to retrieve attributes, content classes and content items. Next you’re going to add one more utility class, which will contain a method you will use multiple times in two separate actions. Right-‐click on “
” package and create a new class called, “
” with the following source:
package com.ibm.ecm.extension.repositoryType; import java.util.ArrayList; import java.util.Enumeration; import java.util.Hashtable; import com.ibm.ecm.extension.PluginServiceCallbacks; import com.ibm.ecm.json.JSONMessage; import com.ibm.ecm.json.JSONResultSetColumn; import com.ibm.ecm.json.JSONResultSetResponse; import com.ibm.ecm.json.JSONResultSetRow; import com.ibm.json.java.JSONArray; import com.ibm.json.java.JSONObject; public class RepositoryUtils { public static JSONResultSetResponse buildResultSetResponse(PluginServiceCallbacks callbacks,
RepositoryConnection connection, ArrayList<ContentItem> contentItemList) {
JSONResultSetResponse resultSetResponse = new
JSONResultSetResponse(); try { columns that are
// Iterate through all the items and determine the
// needed to present all of the item attributes
Hashtable<String, ContentClass> contentClasses = new
Hashtable<String, ContentClass>(); for (ContentItem contentItem : contentItemList) {
String contentClassId = contentItem.getContentClassId(); if
(!contentClasses.containsKey(contentClassId)) { contentClasses.put(contentClassId,
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 2
5
connection.getContentClass(contentClassId));
}
}
ArrayList<String> joinedAttributeDefIds = new
ArrayList<String>();
Enumeration<ContentClass> elements = contentClasses.elements(); while (elements.hasMoreElements()) { elements.nextElement();
ContentClass contentClass =
String[] attributeStrings = contentClass.getAttributeDefinitionIds(); i++) { for (int i = 0; i < attributeStrings.length; if
(!joinedAttributeDefIds.contains(attributeStrings[i])) { joinedAttributeDefIds.add(attributeStrings[i]);
}
}
}
// Add the columns to the result response resultSetResponse.addColumn(new
JSONResultSetColumn(" ", "multiStateIcon", false, new String[0])); resultSetResponse.addColumn(new
JSONResultSetColumn(" ", "17px", "mimeTypeIcon", null, false)); for (int i = 0; i < joinedAttributeDefIds.size(); i++) {
String attributeDefinitionId = joinedAttributeDefIds.get(i);
AttributeDefinition attributeDef = connection.getAttributeDefinition(attributeDefinitionId);
JSONResultSetColumn(); if (!attributeDef.isSystem()) {
JSONResultSetColumn resultSetColumn = new resultSetColumn.setName(attributeDef.getName()); resultSetColumn.setField(attributeDef.getId()); resultSetColumn.setWidth(Integer.
toString (Math.
min (attributeDef.g
etMaxLength(), 10)) + "em"); resultSetResponse.addColumn(resultSetColumn);
}
} the sample, just
// Add magazine columns for the magazine view. For
// a thumbnail and the first attribute of the class(es) is displayed. Usually
// this would be configurable and likely provide important timestamps resultSetResponse.addMagazineColumn(new
JSONResultSetColumn("thumbnail", "60px", "thumbnail", null, null));
JSONArray fieldsToDisplay = new JSONArray();
2
6
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
if (joinedAttributeDefIds.size() > 0) {
String attributeDefinitionId = joinedAttributeDefIds.get(0);
AttributeDefinition attributeDef = connection.getAttributeDefinition(attributeDefinitionId);
JSONObject jsonObj = new JSONObject(); jsonObj.put("field", attributeDef.getId()); jsonObj.put("displayName", attributeDef.getName()); fieldsToDisplay.add(jsonObj);
} resultSetResponse.addMagazineColumn(new
JSONResultSetColumn("content", "100%", "content", fieldsToDisplay, null));
// Add the rows to the result response for (ContentItem contentItem : contentItemList) {
JSONResultSetRow resultSetRow = new
JSONResultSetRow(contentItem.getContentClassId(), contentItem.getId(), contentItem.getName(), contentItem.getContentType(),
JSONResultSetRow.
PRIV_VIEWDOC | JSONResultSetRow.
PRIV_EXPORT |
JSONResultSetRow.
PRIV_EMAILDOC | JSONResultSetRow.
PRIV_PRINTDOC |
JSONResultSetRow.
PRIV_EDITPROPERTIES ); resultSetRow.setName(contentItem.getName());
String[] attributeIds = contentItem.getAttributeIds(); for (int j = 0; j < attributeIds.length; j++) {
String attributeId = attributeIds[j];
Object attributeValue = contentItem.getAttributeValue(attributeId); resultSetRow.addAttribute(attributeId, attributeValue, null, null, null);
}
} resultSetResponse.addRow(resultSetRow);
} catch (Exception e) { callbacks.getLogger().logError(RepositoryUtils.class,
"buildResultSetResponse", e);
JSONMessage message = new JSONMessage(30000, "The content server is not available.",
"The content server cannot return content items.", "",
"See the server logs for more information on this error: " + e.getLocalizedMessage(), null); resultSetResponse.addErrorMessage(message);
}
}
} return resultSetResponse;
This above static utility method takes an array of ContentItems and creates a response that is suitable for consumption by the Content Navigator FolderTree and
ContentList widgets. Using JSONResultSetResponse class the code creates a
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 2
7
structure for both details and magazine views of the ContentList and adds the items to a list within the JSON structure. With this utility method you are now ready to create the actions necessary to populate a folder tree and list view. Right-‐click on
“
” package and create a new class called,
“
” with the following source: package com.ibm.ecm.extension.repositoryType; import java.util.ArrayList; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.ibm.ecm.configuration.RepositoryConfig; import com.ibm.ecm.extension.PluginServiceCallbacks; import com.ibm.ecm.json.JSONResultSetResponse; public class OpenFolder { public void performAction(PluginServiceCallbacks callbacks,
RepositoryConfig repositoryConfig, HttpServletRequest request,
HttpServletResponse response) throws Exception {
String repositoryId = request.getParameter("repositoryId");
RepositoryConnection connection = (RepositoryConnection) callbacks.getPluginRepositoryConnection(repositoryId);
String docid = request.getParameter("docid"); // id of folder boolean foldersOnly = false; if
("folderSearch".equals(request.getParameter("filter_type"))) { foldersOnly = true; // only nested folders should be returned
}
JSONResultSetResponse jsonResponse = new
JSONResultSetResponse();
ContentItem folder; if (docid.equals("/")) { folder = connection.getRootItem();
} else { folder = connection.getContentItem(docid);
}
String[] contentItemIds = folder.getContentIds();
ArrayList<ContentItem> contentItemList = new
ArrayList<ContentItem>(); for (int i = 0; i < contentItemIds.length; i++) {
ContentItem contentItem = connection.getContentItem(contentItemIds[i]);
} if (!foldersOnly || contentItem.isFolder()) {
} contentItemList.add(contentItem); jsonResponse =
2
8
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
}
RepositoryUtils.
buildResultSetResponse (callbacks, connection, contentItemList); callbacks.writeJSONResponse(jsonResponse, response);
}
The open folder action uses the folder id passed by the client to retrieve the appropriate folder from the respository.json file. If the client passes a folder id of
“
”, it represents the root, so the open folder action obtains the root item in the tree from the RepositoryConnection class. Finally the utility method you created earlier
is used to create the JSON response for the client.
One more action is required to enable the folder browsing capability. Right-‐click on the “
” package again and create a class called “
” and add the following source code to the new file:
package com.ibm.ecm.extension.repositoryType; import java.util.ArrayList; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.ibm.ecm.configuration.RepositoryConfig; import com.ibm.ecm.extension.PluginServiceCallbacks; import com.ibm.ecm.json.JSONResultSetResponse; public class GetContentItems { public void performAction(PluginServiceCallbacks callbacks,
RepositoryConfig repositoryConfig, HttpServletRequest request,
HttpServletResponse response) throws Exception { yId);
String repositoryId = request.getParameter("repositoryId");
RepositoryConnection connection =
(RepositoryConnection)callbacks.getPluginRepositoryConnection(repositor
String docid = request.getParameter("docid"); // id of first document
JSONResultSetResponse jsonResponse = new
JSONResultSetResponse();
ArrayList<ContentItem> contentItemList = new
ArrayList<ContentItem>(); if (docid.equals("/")) { contentItemList.add(connection.getRootItem()); jsonResponse =
RepositoryUtils.
buildResultSetResponse (callbacks, connection, contentItemList);
} else {
String[] docids = request.getParameterValues("docid"); for (int i = 0; i < docids.length; i++) {
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 2
9
} contentItemList.add(connection.getContentItem(docids[i]));
} jsonResponse =
RepositoryUtils.
buildResultSetResponse (callbacks, connection, contentItemList);
}
} callbacks.writeJSONResponse(jsonResponse, response);
This action simply retrieves the content items associated with the document id passed from the client. The last change you need to make is to enable the
“
” and “
” actions in the main repository type Java
class. Open the “
” file and make the following change: public class IBMDevCourseRepository extends PluginRepositoryType { private static final Map<String, String>
IBMDevCourseRepositoryActionMap = new HashMap<String, String>() { private static final long serialVersionUID =
7212659949366854547L;
{ put("logon",
"com.ibm.ecm.extension.repositoryType.RepositoryLogon"); put("logoff",
"com.ibm.ecm.extension.repositoryType.RepositoryLogoff"); put("openFolder",
"com.ibm.ecm.extension.repositoryType.OpenFolder"); put("getContentItems",
"com.ibm.ecm.extension.repositoryType.GetContentItems");
}
}; public void performAction(PluginServiceCallbacks callbacks,
RepositoryConfig repositoryConfig, String action, HttpServletRequest request, HttpServletResponse response) throws Exception { if ( IBMDevCourseRepositoryActionMap .containsKey(action)) { callbacks.getLogger().logDebug( this , "performAction",
"Executing \"" + action + "\"...");
Object obj =
Class.
forName ( IBMDevCourseRepositoryActionMap .get(action)).newInstance(
);
Method m = obj.getClass().getMethod("performAction",
PluginServiceCallbacks.
class , RepositoryConfig.
class ,
HttpServletRequest.
class , HttpServletResponse.
class ); response); m.invoke(obj, callbacks, repositoryConfig, request,
} else { callbacks.getLogger().logError( this , "performAction",
"${PluginClassName} does not support the repository action \"" + action
+ "\".");
// return JSON with an error as well for the client
3
0
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
JSONResponse jsonResponse = new JSONResponse(); jsonResponse.addErrorMessage( new JSONMessage(20005,
"${PluginClassName} does not support the action", "The repository action \""+action+"\" is not supported.", null , null , null )); response.getWriter().write(jsonResponse.serialize());
}
}
@Override public String getId() {
} return
@Override
"IBMDevCourseRepository"; public String getName(Locale locale) {
} return "IBM Development Course Repository";
;
@Override public String getConfigurationDijitClass() { return
"iBMDevCoursePluginDojo.IBMDevCourseRepositoryGeneralConfigurationPane"
} public String getConnectedConfigurationDijitClass() { return
"iBMDevCoursePluginDojo.IBMDevCourseRepositoryConfigurationParametersPa
ne";
}
@Override public PluginRepositoryConnection logon(PluginServiceCallbacks callbacks, HttpServletRequest request, String userid, String password,
RepositoryConfig repositoryConfig) throws Exception {
PluginRepositoryConnection connection =
RepositoryLogon.
class .newInstance().logon(callbacks, request, userid, password, repositoryConfig);
} return connection;
@Override public void logoff(PluginServiceCallbacks callbacks, HttpSession
} session, PluginRepositoryConnection connection) throws Exception {
RepositoryLogoff.
class .newInstance().logoff(callbacks, session, connection);
}
@Override public String getRepositoryModelClass() { return "";
} public
}
String getIconClass() { return "";
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 3
1
With the above changes you should now be able to log on to your repository type
from the browse feature and populate the tree and list view with contents of folders.
Figure 1.12
Although you do not need any additional configuration data for this repository type, it is a useful exercise to create some dummy values for the repository configuration to help you understand how you would go about prompting the administrator for custom properties in a real repository type. Open
“
” located under your
“
” package and
make the following changes:
<div>
<table class= "propertyTable" role= "presentation" >
<tr>
<td class=
Name:</label>
"propertyRowLabel" >
<span class= "required" >*</span>
<label for= "${id}_serverName" >Server
</td>
<td class= "propertyRowValue" >
<div id= "${id}_serverName" data-dojo-attach-point= data-dojo-attach-event=
"serverNameField"
"onKeyUp:
_onParamChange" data-dojotype= "ecm/widget/ValidationTextBox" data-dojo-props= " required: true, trim: true, propercase: false, maxLength: 64 " ></div>
</table>
</div>
</tr>
</td>
This adds a required field to the widget template, prompting the administrator for a
“server name” parameter. This is a parameter provided by default in the standard repository configuration object in Content Navigator and a typical field required for
3
2
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
repositories. Open “
” and make the following changes:
define([
"dojo/_base/declare",
"dijit/_TemplatedMixin",
"dijit/_WidgetsInTemplateMixin",
"ecm/widget/ValidationTextBox",
"ecm/widget/admin/PluginRepositoryGeneralConfigurationPane",
"dojo/text!./templates/IBMDevCourseRepositoryGeneralConfiguration
Pane.html"
], function (declare, _TemplatedMixin, _WidgetsInTemplateMixin,
ValidationTextBox, PluginRepositoryGeneralConfigurationPane, template)
{
/**
* @name iBMDevCoursePluginDojo.IBMDevCourseRepositoryGeneralConfigurationPane
* @class Provides a configuration panel for general repository configuration for the repository type. This panel appears
* on the general tab of the repository configuration page in administration when creating or editing a repository
* of the defined repository type.
* @augments ecm.widget.admin.PluginRepositoryGeneralConfigurationPane
*/ return declare("iBMDevCoursePluginDojo.IBMDevCourseRepositoryGeneralConfigurat
ionPane", [ PluginRepositoryGeneralConfigurationPane, _TemplatedMixin,
_WidgetsInTemplateMixin ], {
/** @lends iBMDevCoursePluginDojo.IBMDevCourseRepositoryGeneralConfigurationPane.p
rototype */ templateString: template, widgetsInTemplate: true , load: function (repositoryConfig) { if (repositoryConfig) { this.serverNameField.set('value', repositoryConfig.getServerName());
}
},
_onParamChange: function() {
}, this.onSaveNeeded(true); validate: function () {
}, if(!this.serverNameField.isValid()) {
} return false; return true ;
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 3
3
; save: function (repositoryConfig) { if (repositoryConfig) { repositoryConfig.setServerName(this.serverNameField.get("value")) params.serverName = this.serverNameField.get("value");
}
});
});
} ,
} getLogonParams: function(params) {
These changes will enable the server name prompt, saving the value in the default
“
” property of the Content Navigator repository configuration. The
“
” method is called by the container to obtain additional information that should be passed to the log in action call to the repository type.
Open the repository configuration for your repository type and there should be a
new required parameter on the general configuration tab.
Figure 1.13
It is also possible to add custom properties that are not available in the standard
Content Navigator repository configuration. To illustrate that you will add some custom configuration parameters to the configuration tab that is enabled after you log in to the repository type in the repository configuration. Open
“IBMDevCourseRepositoryConfigurationParametersPane.html” and add the following:
3
4
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
<div>
<tr>
1:</label>
<table class= "propertyTable" role= "presentation" >
_onParamChange"
<td class=
</td>
"propertyRowLabel"
<span class=
<label for=
<div id=
"required"
"${id}_param1" data-dojotype= "ecm/widget/ValidationTextBox"
>
>*</span>
"${id}_param1"
<td class= "propertyRowValue" >
>Config Param data-dojo-attach-point= data-dojo-attach-event=
"param1Field"
"onKeyUp: data-dojo-props= " required: true, trim: true, propercase: false, maxLength: 64 " ></div>
</tr>
</td>
<tr>
<td class= "propertyRowLabel"
<label for=
>
"${id}_param2" >Config Param
2:</label>
_onParamChange"
</td>
<td class= "propertyRowValue" >
<div id= "${id}_param2" data-dojotype= "ecm/widget/ValidationTextBox" data-dojo-attach-point= data-dojo-attach-event=
"param2Field"
"onKeyUp: data-dojo-props= " required: true, trim: true, propercase: false, maxLength: 64 " ></div>
</tr>
</td>
</table>
</div>
This change will enable two new prompts for the administrator, for dummy parameters you will store in custom configuration parameters on your repository configuration. Open “IBMDevCourseRepositoryConfigurationParametersPane.js” and make the following changes:
define([
"dojo/_base/declare",
"dojo/_base/json",
"dijit/_TemplatedMixin",
"dijit/_WidgetsInTemplateMixin",
"ecm/widget/ValidationTextBox",
"ecm/widget/admin/PluginRepositoryConfigurationParametersPane",
"dojo/text!./templates/IBMDevCourseRepositoryConfigurationParamet ersPane.html"
], function (declare, dojojson, _TemplatedMixin,
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 3
5
_WidgetsInTemplateMixin, ValidationTextBox,
PluginRepositoryConfigurationParametersPane, template) {
/**
* @name iBMDevCoursePluginDojo.IBMDevCourseRepository
* @class Provides a configuration panel for general repository configuration for the repository type. This panel appears
* on the general tab of the repository configuration page in administration when creating or editing a repository
*
* @augments
of the defined repository type. ecm.widget.admin.PluginRepositoryGeneralConfigurationPane
*/ return declare("iBMDevCoursePluginDojo.IBMDevCourseRepositoryConfigurationPara
metersPane", [ PluginRepositoryConfigurationParametersPane,
_TemplatedMixin, _WidgetsInTemplateMixin], {
/** @lends iBMDevCoursePluginDojo.IBMDevCourseRepositoryConfigurationParametersPan
e.prototype */ templateString: template, widgetsInTemplate: true , load: function (repositoryConfig) { var customProperties = repositoryConfig.getCustomProperties(); if (customProperties) { var jsonConfig = dojojson.fromJson(customProperties); this.param1Field.set('value',jsonConfig.configuration[0].value); this.param2Field.set('value',jsonConfig.configuration[1].value);
}
},
_onParamChange: function() {
}, this.onSaveNeeded(true); validate: function () {
}, if(!this.param1Field.isValid()) return false; return true; save: function (repositoryConfig) { var configArray = []; var configString = { name: "param1Field", value: this.param1Field.get('value')
}; configArray.push(configString); configString = {
3
6
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
;
}); name: "param2Field", value: this.param2Field.get('value')
}; configArray.push(configString); var configJson = {
"configuration" : configArray
}; repositoryConfig.setCustomProperties(dojojson.toJson(configJson))
});
}
These changes load and save the custom configuration parameters in a JSON string
on the repository configuration. After these changes you should now find the new prompts on the repository configuration tab after you log in to your repository type.
Figure 1.14
Add new actions to handle the other aspects of the browse pane. These actions include “openItem”, “openContentClass”, “getContentClasses”, “getContentItems”,
“editAttributes” and “getDocument”.
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 3
7
In addition to creating extensions for features contained within your plug-‐in you may find the need to expose server-‐side APIs that other plug-‐ins can leverage. A good example, is the External Data Services (EDS) plug-‐in provided by Content
Navigator. Although this plug-‐in is primarily used to augment the standard services available within Content Navigator, it contains an API that is used to handle a call to an external service, which is configured as part of the plug-‐in configuration during deployment. Other plug-‐ins may want to leverage this same external service, so it is beneficial for the EDS plug-‐in to provide a mechanism for other plug-‐ins to leverage this configured external source. For this capability, Content Navigator’s plug-‐in framework provides the option to create a Plug-‐in API. A Plug-‐in API is server-‐side extension that exposes a method call for another plug-‐in. In this exercise, you will create a plug-‐in API that allows another plug-‐in to leverage the configuration stored by your existing plug-‐in. You will then create a new plug-‐in to read these values and display them in the plug-‐in configuration of the new plug-‐in.
Right-‐click on the “
” package in your plug-‐in project and
use the Content Navigator menu to create a new “
”.
Figure 2.1
3
8
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
The Plug-‐in API Java class, like other server extensions, is a simple class structure. It includes only an identifier and an execute method. Make the following changes to
the execute method of the Plug-‐in API Java class: package com.ibm.ecm.extension; import javax.servlet.http.HttpServletRequest; import com.ibm.json.java.JSONObject;
/**
* Provides an abstract class that is extended to create a class implementing an API provided by the plug-in. A plug-in
* API can be invoked from other plug-ins, allowing one plug-in to provide services that another plug-in service can
* use. A plug-in API is similar to a plug-in service, receiving an instance of PluginServiceCallbacks and the original
* HttpServletRequest object, allowing access to any session state and underlying APIs. </p> Follow best practices for
* servlets when implementing a plug-in API. In particular, always assume multi-threaded use and do not keep unshared
* information in instance variables.
*/ public class DevCourseConfigAPI extends PluginAPI { public
}
String getId() { return "DevCourseConfigAPI";
/**
* Performs the action of this API.
*
* @param callbacks
* An instance of the
<code>PluginServiceCallbacks</code> class that contains several functions that can
* be used by the plug-in API. These functions provide access to the plug-in configuration and content
* server APIs.
* @param request
* The <code>HttpServletRequest</code> from the current request being processed. The service can access
* the invocation parameters from the request as well as session state.
* @param arguments
* An object array containing the input arguments to the API. The particular structure of these object is
* defined by the plug-in API writer. The classes of the objects used as arguments should be J2SE classes
* or instances of com.ibm.ecm.json as any plug-in specific classes will be loaded by different
* classloaders causing class cast exceptions when attempting to pass instances from one plug-in to
* another.
* @returns An object containing the response the API. This object can be of any structure and is defined by the
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 3
9
}
* plug-in API writer. The class of the object should be a J2SE class or instance of com.ibm.ecm.json as
* any plug-in specific classes will be loaded by different classloaders causing class cast exceptions when
* attempting to use the object by the invoker.
* @throws Exception
* For exceptions that occur when the API is running.
If the logging level is high enough to log errors,
* information about the exception is logged by IBM
Content Navigator.
*/ public Object execute(PluginServiceCallbacks callbacks,
HttpServletRequest request, Object[] arguments) throws Exception {
JSONObject jsonObj = new JSONObject();
String[] configData = callbacks.loadConfigurations(new
String[] {"1"}); if (configData != null) { jsonObj = JSONObject.
parse (configData[0]);
} return jsonObj;
}
The plug-‐in API is going to do nothing more than retrieve the custom configuration from our plug-‐in and return it as a JSONObject.
In order to use the Plug-‐in API, you need to create a new Plug-‐in to leverage the API.
In eclipse, create a new “IBM Content Navigator” Plug-‐in project called
“
” and use descriptive name “
”, the package name “
” and the class name “
”.
4
0
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
Figure 2.2
Since this plug-‐in will leverage a Plug-‐in API provided by our first plug-‐in, it must register the IBMDevCoursePlugin as a dependency. Make the following changes to
your newly created plug-‐ins main plug-‐in Java class, “
”: public class IBMDevCoursePlugin2 extends Plugin { private PluginAction[] pluginActions = new PluginAction[0];
private PluginOpenAction[] pluginOpenActions = new
PluginOpenAction[0]; private PluginRequestFilter[] pluginRequestFilters = new
PluginRequestFilter[0]; private PluginResponseFilter[] pluginResponseFilters = new
PluginResponseFilter[0]; null ; private PluginService[] pluginServices = new PluginService[0]; private PluginODAuthenticationService odAuthenticationService = private PluginViewerDef[] pluginViewerDefs = new
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 4
1
PluginViewerDef[0]; private PluginLayout[] pluginLayouts = new PluginLayout[0]; private PluginFeature[] pluginFeatures = new PluginFeature[0]; private PluginMenuType[] pluginMenuTypes = new PluginMenuType[0]; private PluginMenu[] pluginMenus = new PluginMenu[0]; private PluginRepositoryType[] pluginRepositoryTypes = new
PluginRepositoryType[0]; private PluginAPI[] pluginAPIs = new PluginAPI[0]; private String[] pluginDependencies = new String[0];
/**
* Initializes this plug-in in the web client. A plug-in can perform
* initialization that would be shared for all users of the application in
* the method. This method differs from the constructor because this method
* is called only when the plug-in is used within the web application. This
* method is invoked when the application is initializing. This is an optional
* method and may be removed from the plug-in if no applicationInit is required.
*/ public void applicationInit(HttpServletRequest request,
PluginServiceCallbacks callbacks) throws Exception {
}
/**
* Provides an identifier for the plug-in.
* <p>
* <strong>Important:</strong> This identifier is used in path names and
* URLs so it must contain only alphanumeric characters.
* </p>
*/ public String getId() { return "IBMDevCoursePlugin2";
}
/**
* Provides a descriptive name for this plug-in. The name identifies the
* plug-in in the IBM Content Navigator administration tool
*
* @parm locale The locale currently in use. The name should be returned in
* the language for this locale if it is translated.
*/ public String getName(Locale locale) {
} return "IBM Development Course Plugin 2";
/**
* Provides a version indicator for this plug-in. The version information is
* displayed in the administration tool to help the administrator
4
2
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
validate
* the version of the plug-in that is being used. The plug-in writer needs
* to update this indicator when redistributing a modified version of the
* plug-in.
*
* @return A <code>String</code> representation of the version indicator for
* the plug-in.
*/ public
}
String getVersion() { return "2.0.3";
/**
* Provides the copyright license for this plug-in. The information is
* displayed in the About dialog Plugins tab. The license is provided by the
* plugin creator. This is an optional method and may be removed from the plug-in
* if no copyright is specified.
*
* @return A <code>String</code> representation of the license for the
* plug-in.
* @since 2.0.2
*/ public String getCopyright() {
} return "Optionally add a Copyright statement here";
/**
* Returns the name of a JavaScript file provided by this plugin. This file
* is downloaded to the web browser during the initialization of the web
* client, before login. A script can be used to perform customization that
* cannot be performed by other extension mechanisms, such as features or
* layouts. However, it is preferable to use these other extension
* mechanisms to provide more flexibility to the administrator when
* configuring desktops.
*/ public
}
/**
String getScript() { return "IBMDevCoursePlugin2.js";
* Returns the name of a debug version of the JavaScript file provided by
* getScript(). The default implementation invokes getScript().
*
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 4
3
*
*/ public
}
@since 2.0.2
String getDebugScript() { return getScript(); in the
/**
* Returns the name of a Dojo module or widget that is contained
* resources for this plug-in. IBM Content Navigator performs the necessary
* <code>dojo.registerModulePath</code> mapping to allow modules or widgets
* with mapped path names to be loaded by using the
* <code>dojo.require</code> method. A specified module can be the directory
* or package name for a set of Dojo modules or widgets.
*/ public
}
String getDojoModule() { return "iBMDevCoursePlugin2Dojo";
/**
* Returns the name of a CSS file that contains styles for this plug-in. IBM
* Content Navigator generates the necessary style tag to pull in this file
* when IBM Content Navigator loads the plug-in.
*/ public String getCSSFileName() {
} return "IBMDevCoursePlugin2.css"; getCSSFileName. The
/**
* Returns a debug version of the CSS file returned by
* default implementation invokes getCSSFileName.
*
* @since 2.0.2
* @return
*/ public String getDebugCSSFileName() { return getCSSFileName();
}
/**
* Returns the name of a Dojo <code>dijit</code> class that provides a
* configuration interface widget for this plug-in. The widget must extend
* the <code>ecm.widget.admin.PluginConfigurationPane</code> widget. An
* instance of the widget is created and displayed in the IBM
Content
* Navigator administration tool for configuration that is specific to the
* plug-in.
4
4
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
* <p>
* Refer to the documentation on
* {@link ecm.widget.admin.PluginConfigurationPane
PluginConfigurationPane}
* for more information on what is required for a plug-in configuration user
* interface.
* </p>
*/ public String getConfigurationDijitClass() {
} return "iBMDevCoursePlugin2Dojo.ConfigurationPane";
/**
* Provides a list of actions that this plug-in adds to the main toolbar of
* the web client.
*
* @return An array of
* <code>{@link com.ibm.ecm.extension.PluginAction
PluginAction}</code>
* objects. The plug-in should return the same set of objects on
* every call.
*/ public
}
PluginAction[] getActions() { return pluginActions;
/** supported
* Provides a list of open actions that this plug-in provides for
* items.
*
* @since 2.0.2
* @return An array of
* <code>{@link com.ibm.ecm.extension.PluginOpenAction
PluginOpenAction}</code>
* objects. The plug-in should return the same set of objects on
* every call.
*/ public PluginOpenAction[] getOpenActions() {
}
/** return pluginOpenActions;
* Provides a list of filters that are run before a requested service. This
* method can be used to modify the request or block the request.
*
* @return An array of
* <code>{@link com.ibm.ecm.extension.PluginRequestFilter
PluginRequestFilter}</code>
* objects.
*/ public PluginRequestFilter[] getRequestFilters() {
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 4
5
} return pluginRequestFilters;
/**
* Provides a list of filters that are run after a requested service. This
* list of filters can be used to modify the response that is returned.
*
* @return An array of
* <code>{@link com.ibm.ecm.extension.PluginResponseFilter PluginResponseFilter}</code>
* objects.
*/ public
}
PluginResponseFilter[] getResponseFilters() { return pluginResponseFilters;
/**
* Provides a list of services that are provided by this plug-in.
The
* services run on the web server, and can be called by the web browser
* logic component of the plug-in.
*
* @return An array of {@link com.ibm.ecm.extension.PluginService
* PluginService} objects. The plug-in should return the same set of
* objects on every call. If there are no services defined by the
* plug-in, the call should return an empty array.
*/ public PluginService[] getServices() {
}
/** return pluginServices;
* Provides a custom service used for Content Manager OnDemand single
* sign-on (SSO). This is an optional service that will be called when SSO
* is enabled on a Content Manager OnDemand server. The result of the
* service will be the information passed through the Content
Manager
* OnDemand Web Enablement Kit "passThru" API.
*
* @since 2.0.2
{
* @return A {@link com.ibm.ecm.extension.PluginODAuthenticationService
* PluginODAuthenticationService} object used as an authentication
* exit for Content Manager OnDemand single sign-on.
*/ public PluginODAuthenticationService getODAuthenticationService() return odAuthenticationService;
4
6
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
}
/**
* Provides a list of viewers that are provided by this plug-in.
The viewers
* become available in the viewer configuration area of the IBM
Content
* Navigator administration tool. The viewers can be mapped to be used to
* view certain document types.
* <p>
* <strong>Note:</strong> Typically, a plug-in does not define multiple and an
* viewers. However, this method can be used to provide multiple
* configurations of the same viewer, such as a view-only version
* editing mode version of the same viewer.
* </p>
*
* @return An array of {@link ecm.widget.admin.PluginViewerDef
* PluginViewerDef} objects describing the viewers provided by the
* plug-in.
*/ public
}
PluginViewerDef[] getViewers() { return pluginViewerDefs;
/**
* Specifies one or more custom layouts that are provided by this plug-in.
* Custom layouts can display the features and other user interface
* components of IBM Content Navigator in a different arrangement. The IBM
* Content Navigator administration tool is used to select the layout to use
* for a desktop.
*
* @return
*/ public
An array of plug-in layout objects.
PluginLayout[] getLayouts() { return pluginLayouts;
}
/**
* Specifies custom features that are provided by this plug-in.
Features are the major user interface sections,
* which appear as icons on the left side of the user interface in the default layout. Examples of features include
* Search, Favorites, and Teamspaces.
*
* @return An array of custom plug-in feature objects.
*/ public PluginFeature[] getFeatures() {
} return pluginFeatures;
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 4
7
/**
* Provides a list of new menu types defined by the plug-in.
*
*
}
@return
*/
An array of new menu type objects. public PluginMenuType[] getMenuTypes() { return pluginMenuTypes;
/**
* Provides a list of menus defined by the plug-in.
*
* @return An array of plug-in menu objects.
*/ public PluginMenu[] getMenus() { return pluginMenus;
}
/**
* Provides a list of one or more custom repositories that are provided by this plug-in.
*
* @return An array of plug-in repository types.
*/ public PluginRepositoryType[] getRepositoryTypes() {
} return pluginRepositoryTypes;
/**
* Provides a list of one or more custom APIs that are provided by this plug-in.
*
*
*/ public
}
@return An array of plug-in APIs.
PluginAPI[] getPluginAPIs() { return pluginAPIs;
/**
* Provides a list of identifiers (the plug-in Ids) of all the plug-ins that this plug-in depends on.
*
* @return An array of plug-in identifier strings.
* @since 2.0.3
*/ public String[] getPluginDependencies() { if (pluginDependencies.length == 0) { pluginDependencies = new String[] {
"IBMDevCoursePlugin" };
}
} return pluginDependencies;
}
4
8
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
Now that you have registered the dependency the next step is to create a
PluginService that will leverage the Plugin API from the first plug-‐in. Right-‐click on the “
” package in your new plug-‐in and create a new Service called “
”.
Figure 2.3
Make the following changes to the newly created ConfigService class:
it must public class ConfigService extends PluginService {
/**
* Returns the unique identifier for this service.
* <p>
* <strong>Important:</strong> This identifier is used in URLs so
* contain only alphanumeric characters.
* </p>
*
* @return A <code>String</code> that is used to identify the service.
*/ public String getId() {
} return "ConfigService";
/**
* Returns the name of the IBM Content Navigator service that this service
* overrides. If this service does not override an IBM Content
Navigator
* service, this method returns <code>null</code>.
*
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 4
9
*
*/ public
}
@returns The name of the service.
String getOverriddenService() { return null ;
/**
* Performs the action of this service.
*
* @param callbacks
* An instance of the
<code>PluginServiceCallbacks</code> class
* that contains several functions that can be used by the
* service. These functions provide access to the plug-in
* configuration and content server APIs.
* @param request
* The <code>HttpServletRequest</code> object that provides the
* request. The service can access the invocation parameters from
* the request.
* @param response
* The <code>HttpServletResponse</code> object that is generated
* by the service. The service can get the output stream and
* write the response. The response must be in JSON format.
* @throws Exception
* For exceptions that occur when the service is running. If the
* logging level is high enough to log errors, information about
* the exception is logged by IBM Content Navigator.
*/ public void execute(PluginServiceCallbacks callbacks,
HttpServletRequest request, HttpServletResponse response) throws
Exception {
PluginLogger logger = callbacks.getLogger(); logger.logEntry(this, "execute");
JSONResponse jsonResponse = new JSONResponse();
Object obj = callbacks.executePluginAPI("IBMDevCoursePlugin", "DevCourseConfigAPI", null); try { if (obj != null) { jsonResponse.put("data", obj);
}
} catch (Exception e) { logger.logError(this, "execute", e); jsonResponse.addErrorMessage(new JSONMessage(20002,
"Error occurred attempting to process a request to the ConfigService in plug-in IBMDevCoursePlugin2", null, null, null, null));
} finally {
5
0
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
}
PluginResponseUtil.
writeJSONResponse (request, response, jsonResponse, callbacks, "ConfigurationService");
} logger.logExit(this, "execute");
// Send response to the client
}
The following changes leverage the PluginServiceCallbacks method
“executePluginAPI” to call the Plugin API provided by your first plugin. The method takes the plug-‐in ID and the ID of its Plugin API as parameters. The third parameter is an array of objects, which allows you to pass arguments to the Plugin API. In this case, you’re API does not require any arguments, so it is ok to pass null for the third parameter.
The last step is to update the configuration widget for this new plugin, to enable it to display the configuration parameters from the first plugin. Open the
“
” file located under the
“
” package and make the following changes:
<div>
<!-- Please add your configuration pane -->
<!-- For example
<table class="propertyTable" role="presentation">
<tr>
<td class="propertyRowLabel">
<span class="required">*</span>
<label for="${id}_ecServerURI">Server:</label>
<div data-dojo-type="ecm/widget/HoverHelp" data-dojoattach-point="ecServerURIHoverHelp" message="Specify a server address."></div>
</td>
<td class="propertyRowValue">
<div id="${id}_ecServerURI" data-dojo-attachpoint="ecServerURI" data-dojo-attach-event="onKeyUp: _onFieldChange" data-dojo-type="ecm/widget/ValidationTextBox" required="true" trim="true" propercase="false"></div>
</td>
</tr>
</table>
-->
<h1>Parameters from IBM Development Course Plug-in</h1>
<table class= "propertyTable" role= "presentation" >
<tr>
<td class= "propertyRowLabel" >
<label>Parameter 1:</label>
</td>
<td class= "propertyRowValue" data-dojo-attachpoint= "param1Value" >
</td>
</tr>
<tr>
<td class= "propertyRowLabel" >
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 5
1
<label>Parameter 2:</label>
</td>
<td class= "propertyRowValue" data-dojo-attachpoint= "param2Value" >
</td>
</tr>
</table>
</div>
Now, update the “
” file under
“
”:
}); define([
],
"dojo/_base/declare",
"dojo/_base/lang",
"dijit/_TemplatedMixin",
"dijit/_WidgetsInTemplateMixin",
"ecm/model/Request",
"ecm/widget/admin/PluginConfigurationPane",
"dojo/text!./templates/ConfigurationPane.html" function (declare, lang, _TemplatedMixin, _WidgetsInTemplateMixin,
Request, PluginConfigurationPane, template) { return declare("IBMDevCoursePlugin2Dojo.ConfigurationPane",
[ PluginConfigurationPane, _TemplatedMixin, _WidgetsInTemplateMixin], { templateString: template, widgetsInTemplate: load:
"ConfigService", function true ,
(callback) {
Request.invokePluginService("IBMDevCoursePlugin2", function(response) { response.data.param1) {
{ requestCompleteCallback: lang.hitch(this,
// success if (response.data && this.param1Value.innerHTML = response.data.param1; response.data.param2) {
} if (response.data && this.param2Value.innerHTML = response.data.param2;
},
);
}); validate:
}
} function return true
})
() {
;
}
5
2
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
This change will cause the configuration to call your newly created service immediately when it loads and display the configuration parameters saved by the other plug-‐in.
Figure 2.4
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 5
3
There may be cases where you may need to override the default behavior of the open action, avoiding the standard path associated with opening a particular document type. For example, Content Navigator stores search templates as JSON files in the repository. However, when the user clicks on a search template in a list, they do not want to open the JSON file in the viewer. Instead, they expect the search itself to open on the search feature in ICN. You may encounter similar cases, where you want to add custom logic when the user opens a particular type.
For this exercise you will add a JSON file containing search criteria to the Invoice class and create a custom open action to run a search based on the defined criteria.
The development course materials “
” directory contains a file name
“
”, which you will use for this exercise. Create a new
“
” document using the “
” file as the content for the document. You can use Content Navigator to create the file by clicking on the “
” action on the “
” in the Content Navigator application.
Figure 3.1
5
4
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
After you create the new file you are ready to enable your open action. Right-‐click on the “
” package in your eclipse project and use the
“
” menu to access the “
” wizard. In the wizard, specify the name “
”, adding the “
” content
type and indicate the open action only supports the “
” repository type:
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 5
5
Figure 3.2
The wizard creates a new file, “
” and adds a new
JavaScript method for your open action to the “
” file. As with other plug-‐in types you have created in previous exercises, the
5
6
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
“DevCourseOpenAction.java” file defines your open action for the Content Navigator plug-‐in framework. It includes both the typical methods to return the identifier and name of your open action. Additionally it contains the methods
“
”, “
” and “
”. The
“getOpenActionFunction” returns the name of the JavaScript function to run when the Content Navigator framework invokes the open action. The “getContentTypes” method returns a String array containing the mime types supported by this open action. In this case, you specified the action only supports the “
” mime type. Finally, the “getServerTypes” method returns a String array containing the list of repository types supported by the open action. Since you specified the open action applies only to the FileNet P8 repository type, the array contains only one entry, “p8”.
Open the “IBMDevCoursePlugin.js” file and add the following to the newly created
“
” function: require(["dojo/_base/declare",
"dojo/_base/lang",
"ecm/model/Request",
"iBMDevCoursePluginDojo/Decorators",
"iBMDevCoursePluginDojo/ResultSet",
"iBMDevCoursePluginDojo/CurrencyPropertyFormatter",
"iBMDevCoursePluginDojo/DevCourseFeature",
"iBMDevCoursePluginDojo/FeatureConfigurationPane",
"iBMDevCoursePluginDojo/ConfigurationPane",
"iBMDevCoursePluginDojo/VideoViewer"], function (declare, lang, Request, Decorators, ResultSet, messages) {
/**
* Use this function to add any global JavaScript methods your plug-in requires.
*/ lang.setObject("customListViewAction", function (repository, items, callback, teamspace, resultSet, parameterMap) {
ResultSet(response);
Request.invokePluginService("IBMDevCoursePlugin",
"ListViewService",
{ requestCompleteCallback: function (response) { if (parameterMap.widget && parameterMap.widget.isInstanceOf(ecm.widget.listView.ContentList)) { response.repository = repository; var resultSet = new parameterMap.widget.setResultSet(resultSet);
}
}
}
);
}); lang.setObject("devCourseOpenAction", function (repository, contentItems) {
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 5
7
/*
* Add custom code for your open action here. For example, your action might launch a custom document view in a dialog.
*/
}); console.debug("Open action called");
});
Now, re-‐load your plug-‐in and find the invoice document you created earlier in this exercise. Open the document, with the browser developer tools enabled in your
browser, and the print statement you added above should display in the console.
Now that you have a working open action, the next step is to enable it to run the
search criteria contained within the JSON file you added earlier in this chapter.
5
8
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
An asynchronous task is basically a long running process that operates in the background without blocking the user interface. There are many use cases for this type of tasks. Some common scenarios include:
1) A long-‐running search or report
2) Any sweep-‐like jobs that are CPU intensive and may take hours to complete.
3) Batched operations such as large uploads or multi-‐deletes that require a bit of time to finish.
In IBM Content Navigator, asynchronous tasks are used to perform teamspace decommissioning, which deletes all data in a teamspace. Other products such as
IBM Enterprise Records use asynchronous tasks to do their retention sweeps which can take hours or days to finish.
The IBM ECM Task manager is the task execution framework used to manage asynchronous tasks. It is bundled along with an IBM Content Navigator installation and deployment and can be enabled or disabled in the Navigator’s administration
console.
Some additional features of the IBM ECM Task manager include:
1) Flexible scheduling capabilities such as single, recurring, and calendar scheduling
2) Batch and parallel processing support for performance and scale of heavy tasks
3) Clustered deployment for fail-‐over safety
4) Full auditing capabilities
5) Completely extensible with custom tasks
The IBM ECM Task manager is repository agnostic and can be used to run against
any ECM repositories or any other external repositories as well.
As stated above, the IBM ECM Task Manager supports custom asynchronous task development. Users are able to create custom tasks and use the task manager to
schedule and run their tasks.
There are a full set of APIs (javascript, plugin, and Task manager) and UIs (task panes and schedulers) available for use.
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 5
9
In this exercise, we will focus on creating a custom task. This exercise will walk you through the following:
1) Creating a custom asynchronous task to perform stimulated batched operation. Usually something more substantial will be done in a custom task but focus on something smaller just so it will finish within a reasonable time frame.
2) Registering the custom task with IBM Content Navigator so it can be used within the default Navigator Task Pane.
3) Creating a custom scheduler that prompts the user for custom fields and scheduling information
Creating a custom asynchronous task requires extending from the base task called
“
– the foundational class for all asynchronous tasks. You will need to implement the “
” method and provide what your custom task is supposed to do.
Let’s get started by opening your Eclipse and pointing it to the last working copy of the
project. If you don’t have a previous working copy, you can take the completed solution from another chapter.
In order to create your custom take, you will need to first bring in some task
manager libraries. There are a few other libraries that are needed (JPA persistence and WINK) and typically not provided with IBM Content Navigator. All the libraries are located under :
. Make a copy of all the libraries.
Go back to your Eclipse and paste all the library files under the lib folder. You should have this now.
6
0
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
Right click
and choose Properties and then Libraries tab. Click on Add JARs and add all the libraries that are under the lib folder.
With all the libraries loaded, you are now ready to create your custom task. Create
a class file under
and name it “
”.
Add to the class and make it extend from BaseTask. At the same time, add the method stubs that your custom task will need to implement as well. In the end, you should have something similar to this:
} package com.ibm.ecm.extension; import java.io.File; import java.io.IOException; import com.ibm.ecm.task.commonj.work.BaseTask; import com.ibm.ecm.task.entities.Task; public class IBMDevAsyncTask extends BaseTask { public IBMDevAsyncTask(Task task, File logDirectory) throws
IOException { super (task, logDirectory);
// TODO Auto-generated constructor stub
}
@Override public void performTask() {
}
// TODO Auto-generated method stub
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 6
1
Inside the BaseTask, there are a few variables that will provide valuable information that will help you in implementing your task.
1) Task – This variable holds all the information about the current Task which is serialized and stored into the tasks database. This includes the start time of your task, who the creator is, run time status (failed, errored out, paused, or not yet started), the end time, the task’s schedule, and any custom task’s information (stored into the task’s info as a JSON object).
2) TaskExecutionRecord – When a single task runs on a recurring basis or is repeated, there are no subsequent tasks that are created for each run. What will happen is that a single task will be created and then for each subsequent runs, a task execution record will be instantiated to represent that next run.
A task execution record contains similar but a smaller subset of a task’s information. It will always contain a taskId which points back to the originating task.
3) LogDirectory – This is the directory where any logs for the task will be logged into. By default, this log directory is set as “taskManager” and located as a child of the log directory of IBM Content Navigator.
4) Email notifications – A task is able to notify users when a task completes, fails, or errors out. There are methods to set the content and subject of how the email will be presented when one of these statuses of the task occur.
As noted above, we are going to be writing a simple stimulated task. Change
IBMDevAsyncTask to look like this:
package com.ibm.ecm.extension; import java.io.File; import java.io.IOException; import com.ibm.ecm.task.Constants; import com.ibm.ecm.task.TaskLogger; import com.ibm.ecm.task.commonj.work.BaseTask; import com.ibm.ecm.task.entities.Task; import com.ibm.ecm.task.utils.Utils; import com.ibm.json.java.JSONObject; public class IBMDevAsyncTask extends BaseTask { public IBMDevAsyncTask(Task task, File logDirectory) throws
IOException {
} super
//
(task, logDirectory);
TODO Auto-generated constructor stub public void performTask() { try {
6
2
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
TaskLogger.
fine (IBMDevAsyncTask.
class .toString(),
"performTask" , "Starting task." );
//grabbing information from the task info
JSONObject taskInfo =
JSONObject.
parse ( task .getTaskInfo());
Thread.
sleep (5000); successfully!" );
JSONObject results = results.put( new JSONObject();
"default_label" , "This task completed
} saveTaskInfo(results, taskInfo); catch (Exception exp){ this .addError( new Long(0),
Utils.
captureStackTrace (exp)); setTaskStatus(Constants.
TASK_STATUS_FAILED );
}
} public void saveTaskInfo(JSONObject results, JSONObject taskInfo) throws Exception {
.
String resultsString = results.serialize(); if ( this .
taskExecutionRecord != null &&
( this .
task .getTaskMode() == Constants.
TASK_RECURRING || this .
task .getTaskMode() == Constants.
TASK_CALENDAR_ACTION )){
); this .
taskExecutionRecord .setTaskExecutionInfo(results.serialize() this .updateTaskExecutionRecord();
} else this .
taskInfo.put( task
"results" , resultsString);
.setTaskInfo(taskInfo.serialize());
}
} this .updateTask();
What this task is doing is that it’s deserializing the task’s info. It will then simulate some type of batched operations by sleeping for 5 seconds. At the end, it will save a json value back into the task’s info to indicate that it has successfully. In the save, it checks to see if this is a recurring task, if it is, it will save to the task execution record instead.
Once you have finished creating your task, you are now required to add this class into the IBM ECM Task Manager for it to process. Use Windows Explorer and navigate to where your IBMDevCoursePlugin is located. Browse into and make a copy of
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 6
3
With the class file, using Windows Explorer again, browse to
and paste in the class file.
Once the class added , now you will need to restart Websphere by going into
Windows Start Icon and choosing
and then
Once
Websphere restarts, your custom task is now ready to be used.
In order for Navigator to recognize the custom asynchronous task and display it within the Navigator’s Task Pane, you need to register it in your plugin by creating a
PluginAsyncTaskType which represents every unique custom task you have.
Navigate to Eclipse and create a new java class called
“
” under “
”. Put the following content in:
package com.ibm.ecm.extension;
import java.util.Locale;
import com.ibm.ecm.extension.PluginAsyncTaskType;
public class IBMDevAsyncTaskType extends PluginAsyncTaskType {
public String getClassHandlerName(){ return "com.ibm.ecm.extension.IBMDevAsyncTask";
}
public String getName(Locale locale){
} return "Dev Async Task"; public String getTaskCreationDialogDijitClass() {
} return "iBMDevCoursePluginDojo/TaskCreationDialog";
public String getIconClass() { return "ftWordProcessing";
}
}
In IBMDevAsyncType, you are returning the full classpath name of your custom async task created previously in the getClassHandlerName(). You are also
6
4
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
returning a custom task name that you want to display to the user. For the
TaskCreationDialogDijitClass, you are presenting a custom user interface that will be displayed to the user when scheduling your task.
Navigate to Eclipse and create these two new javascript files under
define([
"dojo/_base/declare",
"dojo/_base/lang",
"ecm/widget/taskManager/BaseTaskCreationDialog",
"iBMDevCoursePluginDojo/TaskCreationPane",
"dojo/text!./templates/TaskCreationDialog.html",
], function(declare, lang, BaseTaskCreationDialog, TaskCreationPane, contentString)
{ return declare("iBMDevCoursePluginDojo.TaskCreationDialog", [
BaseTaskCreationDialog
], { contentString: contentString,
});
widgetsInTemplate: true, postCreate: function() {
}, this.inherited(arguments); this.taskCreationPane = new TaskCreationPane(); this.taskSchedulerPane.addTitlePaneSection("General", this.taskCreationPane, 0);
});
onSchedule: function() {
}
var valid = this.taskCreationPane.validate(); if (valid == true) {
} this.inherited(arguments);
define([
"dojo/_base/declare",
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 6
5
"dojo/_base/lang",
"dijit/layout/ContentPane",
"dijit/_TemplatedMixin",
"dijit/_WidgetsInTemplateMixin",
"dojo/text!./templates/TaskCreationPane.html"
], function(declare, lang, ContentPane, TemplatedMixin, WidgetsInTemplateMixin,
contentString) {
return declare("iBMDevCoursePluginDojo.TaskCreationPane", [ContentPane,
TemplatedMixin, WidgetsInTemplateMixin ], {
templateString: contentString, widgetsInTemplate: true,
/**
* Returns true if this pane contains all valid values.
*/
});
});
validate: function() {
} return true;
In the end, you should have this:
6
6
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
You will also need to create two template files to correspond with the two panes under
1) TaskCreationDialog.html
<div class="ecmCommonPropertiesPane" data-‐dojo-‐attach-‐ point="contentContainerNode" data-‐dojo-‐type="dijit/layout/ContentPane" style="width:100%; height:100%;">
</div>
2) TaskCreationPane.html
<table class="propertyTable">
<tbody>
property:</label>
<tr>
<td class="propertyRowLabel">
</td>
<label for="${id}_property">Sample
<td class="propertyRowValue">
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 6
7
<div id="${id}_property" data-‐dojo-‐ type="dijit/form/TextBox" data-‐dojo-‐attach-‐point="number"
data-‐dojo-‐props="value:'abc', trim: true, intermediateChanges: true, required: true" style="width:150px;"></div>
</tr>
</tbody>
</td>
</table>
In the end you should see this:
What you are doing is creating a custom task creation dialog which extends from the
widget. Inside the dialog, you will present a custom pane that will include a single textbox that will prompt the user for values. The value from these prompts can then be passed along into your custom task for processing.
Once you have all the files created, let’s start getting the Navigator Task Pane running and scheduling some tasks.
6
8
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
Launch Navigator in your browser. Click on the administration feature icon. Click on Desktops and then edit your default desktop (Demo) . Go to the Layout tab and
in the feature list, enable the Asynchronous Tasks feature and then Save and Close.
Refresh your browser. Once Navigator has loaded, click on the Asynchronous Tasks
Feature Pane on the left-‐hand side.
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 6
9
This Asynchronous Feature Pane displays all the asynchronous tasks you have in your system. This is also the location where you can schedule new async tasks as well. Since you have your AsyncTaskType registered, you should see your
under the Schedule button.
Click on schedule task action and it should launch the custom task creation dijits and panes that you added.
7
0
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT
Enter a name and description for your task. You can also change the schedule as well. Once you are done, click on the Schedule button. Once your task has been scheduled, you should see a new field in async task list. The new task should be in a temporary
status. Refresh the list again and it should be
status and then finally in a
status.
Click on your completed task and you will see a detail tab that contains more
information about your task.
IBM CONTENT NAVIGATOR PLUG-‐IN DEVELOPMENT 7
1