IBM Content Navigator - Advanced Development Course

advertisement
 IBM Content Navigator Plug-­‐in Development Advanced Topics Brett Morris, IBM IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 1 Table of Contents Introduction .............................................................................................................................. 3 1. Plug-­‐in Repository Types ................................................................................................. 4 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. Creating a Plug-­‐in API ..................................................................................................... 38 2.1 Creating a Plug-­‐in API ............................................................................................................ 38 2.2 Using a Plug-­‐in API .................................................................................................................. 40 3. Open Actions ...................................................................................................................... 54 3.1 Creating an open action ......................................................................................................... 54 3.2 Enabling the custom open action ....................................................................................... 58 4. Asynchronous tasks ......................................................................................................... 59 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 Introduction 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 1. Plug-­‐in Repository Types 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. 1.1 Creating the repository type Right-­‐click on the “com.ibm.ecm.extension” package in the eclipse project you created in the base “Content Navigator Development Course” and use the “IBM Content Navigator” menu to create a “New Repository Type”. In the new repository type wizard set the class name to “IBMDevCourseRepository”. 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, “IBMDevCourseRepository.java”, two JavaScript classes, “IBMDevCourseRepositoryGeneralConfigurationPane.js” and “IBMDevCourseRepositoryConfigurationParametersPane.js”, 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 “performAction” 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 “performAction” 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, “getConfigurationDijitClass” and “getConnectedConfigurationDijitClass”. 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, “logon”, “logoff”, “getRepositoryModelClass” and “getIconClass”. The “getIconClass” 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 “getRepositoryModelClass” method. The “logon” and “logoff” methods are used to handle the authentication and disconnect from the repository. The “logon” method returns a “PluginRepositoryConnection” type, which will represent your repository type on the server-­‐side. You must implement this in order to enable your repository type. 1.2 Enabling authentication with the 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 “PluginRepositoryConnection” and enable the log in and log out methods in your repository type. Right-­‐click on the “com.ibm.ecm.extension” package and create a new package called “repositoryType”. 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 “RepositoryConnection”, with the superclass “com.ibm.ecm.extension.PluginRepositoryConnection”. 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 “com.ibm.ecm.extension.data” package. For the remaining exercises, you will be using this JSON file to simulate the presence of a real source. Open the newly created “RepositoryConnection.java” 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 “logon” and “logout” 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 “com.ibm.ecm.extension.repositoryType” package and create a new Java class called “RepositoryLogon” 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
import
import
import
com.ibm.ecm.configuration.RepositoryConfig;
com.ibm.ecm.extension.PluginRepositoryConnection;
com.ibm.ecm.extension.PluginRepositoryLogonException;
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>();
request.getSession(true).setAttribute("plugin_repositories",
pluginRepositoryConnectionList);
}
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 “performAction” 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 “com.ibm.ecm.extension.repositoryType” package called “RepositoryLogoff” 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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 0 (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 “IBMDevCourseRepository.java” from the “com.ibm.ecm.extension.repositoryType” package and make the following changes: import
import
import
import
java.lang.reflect.Method;
java.util.HashMap;
java.util.Locale;
java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import
import
import
import
import
import
import
import
com.ibm.ecm.configuration.RepositoryConfig;
com.ibm.ecm.extension.PluginRepositoryConnection;
com.ibm.ecm.extension.PluginRepositoryType;
com.ibm.ecm.extension.PluginServiceCallbacks;
com.ibm.ecm.extension.repositoryType.RepositoryLogoff;
com.ibm.ecm.extension.repositoryType.RepositoryLogon;
com.ibm.ecm.json.JSONMessage;
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);
m.invoke(obj, callbacks, repositoryConfig, request,
response);
} else {
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() {
return "IBMDevCourseRepository";
}
@Override
public String getName(Locale locale) {
return "IBMDevCourseRepository";
1 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 2 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 “logon” and “logoff” 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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 4 Create a new “IBM Development Course Repository”. 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 “Development Course Plugin Demo” and the “connect” 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 “Dev Course Desktop 3”, with an ID of “devcourse3”. 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 “browse” feature as the only available feature. Figure 1.9 1 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 6 Figure 1.10 Save the desktop and switch to the “devcourse3” 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 1.3 Add additional actions to the repository type 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 “com.ibm.ecm.extension.repositoryType” package and create a new class called “AttributeDefintion” and make the following changes to the class: package com.ibm.ecm.extension.repositoryType;
import com.ibm.json.java.JSONObject;
public class AttributeDefinition {
private RepositoryConnection connection = null;
private JSONObject attributeDefinitionJSON = null;
public AttributeDefinition(RepositoryConnection connection,
JSONObject attributeDefinitionJSON) {
this.connection = connection;
this.attributeDefinitionJSON = attributeDefinitionJSON;
}
public String getId() {
1 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 8 return (String)attributeDefinitionJSON.get("id");
}
public String getName() {
return (String)attributeDefinitionJSON.get("name");
}
public String getType() {
if (attributeDefinitionJSON.containsKey("type")) {
return (String)attributeDefinitionJSON.get("type");
}
return "xs:string";
}
public RepositoryConnection getRepositoryConnection() {
return connection;
}
public int getMaxLength() {
if (attributeDefinitionJSON.containsKey("maxLength")) {
return
((Long)attributeDefinitionJSON.get("maxLength")).intValue();
}
return 0;
}
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 “ContentItem” 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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 0 @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() {
return getContentClass().isFolderClass();
}
public boolean isDocument() {
return getContentClass().isDocumentClass();
}
public String getContentType() {
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, “ContentClass” 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
private
private
private
RepositoryConnection connection;
JSONObject contentClassJSON;
String[] attributeDefinitionIds;
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 “RepositoryConnection.java” 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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 2 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(type, userId, userId);
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);
// 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);
attributeDefinitions.put(attributeDefinitionId,
attributeDefinition);
}
// Cache the content classes
contentClasses = new Hashtable<String,
ContentClass>();
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 inStream.close();
}
}
}
public 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
+ ". There is likely an error in the format of
the repository JSON.");
}
public ContentClass getContentClass(String contentClassId) {
if (contentClasses.containsKey(contentClassId)) {
return contentClasses.get(contentClassId);
}
throw new RuntimeException("Content class not found with id
" + contentClassId
+ ". There is likely an error in the format of
the repository JSON.");
}
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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 4 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 “com.ibm.ecm.extension.repositoryType” package and create a new class called, “RepositoryUtils” with the following source: package com.ibm.ecm.extension.repositoryType;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import
import
import
import
import
import
import
com.ibm.ecm.extension.PluginServiceCallbacks;
com.ibm.ecm.json.JSONMessage;
com.ibm.ecm.json.JSONResultSetColumn;
com.ibm.ecm.json.JSONResultSetResponse;
com.ibm.ecm.json.JSONResultSetRow;
com.ibm.json.java.JSONArray;
com.ibm.json.java.JSONObject;
public class RepositoryUtils {
public static JSONResultSetResponse
buildResultSetResponse(PluginServiceCallbacks callbacks,
RepositoryConnection connection, ArrayList<ContentItem>
contentItemList) {
JSONResultSetResponse resultSetResponse = new
JSONResultSetResponse();
try {
// Iterate through all the items and determine the
columns that are
// 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()) {
ContentClass contentClass =
elements.nextElement();
String[] attributeStrings =
contentClass.getAttributeDefinitionIds();
for (int i = 0; i < attributeStrings.length;
i++) {
if
(!joinedAttributeDefIds.contains(attributeStrings[i])) {
joinedAttributeDefIds.add(attributeStrings[i]);
}
}
}
// Add the columns to the result response
resultSetResponse.addColumn(new
JSONResultSetColumn("&nbsp;", "multiStateIcon", false, new String[0]));
resultSetResponse.addColumn(new
JSONResultSetColumn("&nbsp;", "17px", "mimeTypeIcon", null, false));
for (int i = 0; i < joinedAttributeDefIds.size();
i++) {
String attributeDefinitionId =
joinedAttributeDefIds.get(i);
AttributeDefinition attributeDef =
connection.getAttributeDefinition(attributeDefinitionId);
if (!attributeDef.isSystem()) {
JSONResultSetColumn resultSetColumn = new
JSONResultSetColumn();
resultSetColumn.setName(attributeDef.getName());
resultSetColumn.setField(attributeDef.getId());
resultSetColumn.setWidth(Integer.toString(Math.min(attributeDef.g
etMaxLength(), 10)) + "em");
resultSetResponse.addColumn(resultSetColumn);
}
}
// Add magazine columns for the magazine view. For
the sample, just
// 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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 6 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 “com.ibm.ecm.extension.repositoryType” package and create a new class called, “OpenFolder” 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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 8 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 “com.ibm.ecm.extension.repositoryType” package again and create a class called “GetContentItems” 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 {
String repositoryId = request.getParameter("repositoryId");
RepositoryConnection connection =
(RepositoryConnection)callbacks.getPluginRepositoryConnection(repositor
yId);
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 “OpenFolder” and “GetContentItems” actions in the main repository type Java class. Open the “IBMDevCoursePlugin.java” 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);
m.invoke(obj, callbacks, repositoryConfig, request,
response);
} 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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 0 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 "IBMDevCourseRepository";
}
@Override
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 1.4 Enabling the repository configuration panels 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 “IBMDevCourseRepositoryGeneralConfigurationPane.html” located under your “com.ibm.ecm.extension.iBMDevCoursePluginDojo.templates” package and make the following changes: <div>
<table class="propertyTable" role="presentation">
<tr>
<td class="propertyRowLabel">
<span class="required">*</span>
<label for="${id}_serverName">Server
Name:</label>&nbsp;
</td>
<td class="propertyRowValue">
<div id="${id}_serverName"
data-dojo-attach-point="serverNameField"
data-dojo-attach-event="onKeyUp:
_onParamChange"
data-dojotype="ecm/widget/ValidationTextBox"
data-dojo-props="required: true, trim:
true, propercase: false, maxLength: 64"></div>
</td>
</tr>
</table>
</div>
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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 2 repositories. Open “IBMDevCourseRepositoryGeneralConfigurationPane.js” 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"))
;
}
},
getLogonParams: function(params) {
params.serverName =
this.serverNameField.get("value");
}
});
});
These changes will enable the server name prompt, saving the value in the default “serverName” property of the Content Navigator repository configuration. The “getLogonParams” 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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 4 <div>
<table class="propertyTable" role="presentation">
<tr>
<td class="propertyRowLabel">
<span class="required">*</span>
<label for="${id}_param1">Config Param
1:</label>&nbsp;
</td>
<td class="propertyRowValue">
<div id="${id}_param1"
data-dojo-attach-point="param1Field"
data-dojo-attach-event="onKeyUp:
_onParamChange"
data-dojotype="ecm/widget/ValidationTextBox"
data-dojo-props="required: true, trim:
true, propercase: false, maxLength: 64"></div>
</td>
</tr>
<tr>
<td class="propertyRowLabel">
<label for="${id}_param2">Config Param
2:</label>&nbsp;
</td>
<td class="propertyRowValue">
<div id="${id}_param2"
data-dojo-attach-point="param2Field"
data-dojo-attach-event="onKeyUp:
_onParamChange"
data-dojotype="ecm/widget/ValidationTextBox"
data-dojo-props="required: true, trim:
true, propercase: false, maxLength: 64"></div>
</td>
</tr>
</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
*
of the defined repository type.
* @augments
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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 6 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 1.5 Optional Exercise 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 2. Creating a Plug-­‐in API 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. 2.1 Creating a Plug-­‐in API Right-­‐click on the “com.ibm.ecm.extension” package in your plug-­‐in project and use the Content Navigator menu to create a new “API”. Figure 2.1 3 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 8 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. 2.2 Using a Plug-­‐in API 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 “IBMDevCoursePlugin2” and use descriptive name “IBM Development Course Plugin 2”, the package name “com.ibm.ecm.extension.devcourse” and the class name “IBMDevCoursePlugin2”. 4 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 0 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, “IBMDevCoursePlugin2.java”: 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];
private PluginService[] pluginServices = new PluginService[0];
private PluginODAuthenticationService odAuthenticationService =
null;
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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 2 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 * @since 2.0.2
*/
public String getDebugScript() {
return getScript();
}
/**
* Returns the name of a Dojo module or widget that is contained
in the
* 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";
}
/**
* Returns a debug version of the CSS file returned by
getCSSFileName. The
* 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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 4 * <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;
}
/**
* Provides a list of open actions that this plug-in provides for
supported
* 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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 6 }
/**
* 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
* viewers. However, this method can be used to provide multiple
* configurations of the same viewer, such as a view-only version
and an
* 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 An array of plug-in layout objects.
*/
public 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.
*
* @return An array of plug-in APIs.
*/
public 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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 8 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 “com.ibm.ecm.extension.devcourse” package in your new plug-­‐in and create a new Service called “ConfigService”. Figure 2.3 Make the following changes to the newly created ConfigService class: public class ConfigService extends PluginService {
/**
* Returns the unique identifier for this service.
* <p>
* <strong>Important:</strong> This identifier is used in URLs so
it must
* 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 * @returns The name of the service.
*/
public 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();
try {
Object obj =
callbacks.executePluginAPI("IBMDevCoursePlugin", "DevCourseConfigAPI",
null);
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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 0 logger.logExit(this, "execute");
// Send response to the client
PluginResponseUtil.writeJSONResponse(request,
response, jsonResponse, callbacks, "ConfigurationService");
}
}
}
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 “ConfigurationPane.html” file located under the “com.ibm.ecm.extension.devcourse.WebContent.iBMDevCoursePlugin2Dojo.te
mplates” 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>&nbsp;
<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>&nbsp;
</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>&nbsp;
</td>
<td class="propertyRowValue" data-dojo-attachpoint="param2Value">
</td>
</tr>
</table>
</div> Now, update the “ConfigurationPane.js” file under “com.ibm.ecm.extension.devcourse.WebContent.iBMDevCoursePlugin2Dojo”: 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: true,
load: function(callback) {
Request.invokePluginService("IBMDevCoursePlugin2",
"ConfigService",
{
requestCompleteCallback: lang.hitch(this,
function(response) {
// success
if (response.data &&
response.data.param1) {
this.param1Value.innerHTML =
response.data.param1;
}
if (response.data &&
response.data.param2) {
this.param2Value.innerHTML =
response.data.param2;
}
})
}
);
},
validate: function() {
return true;
}
});
});
5 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 2 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 3. Open Actions 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. 3.1 Creating an open action 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 “resources” directory contains a file name “openActionSearch.json”, which you will use for this exercise. Create a new “Invoice” document using the “openActionSearch.json” file as the content for the document. You can use Content Navigator to create the file by clicking on the “Add Document” action on the “browse feature” in the Content Navigator application. Figure 3.1 5 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 4 After you create the new file you are ready to enable your open action. Right-­‐click on the “com.ibm.ecm.extension” package in your eclipse project and use the “Content Navigator” menu to access the “Open Action” wizard. In the wizard, specify the name “DevCourseOpenAction”, adding the “application/json” content type and indicate the open action only supports the “IBM FileNet P8” repository type: IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 5
5 Figure 3.2 The wizard creates a new file, “DevCourseOpenAction.java” and adds a new JavaScript method for your open action to the “IBMDevCoursePlugin.js” file. As with other plug-­‐in types you have created in previous exercises, the 5 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 6 “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 “getOpenActionFunction”, “getContentTypes” and “getServerTypes”. 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 “application/json” 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 “devCourseOpenAction” 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) {
Request.invokePluginService("IBMDevCoursePlugin",
"ListViewService",
{
requestCompleteCallback: function(response) {
if (parameterMap.widget &&
parameterMap.widget.isInstanceOf(ecm.widget.listView.ContentList)) {
response.repository = repository;
var resultSet = new
ResultSet(response);
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. 3.2 Enabling the custom open action 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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 8 4. Asynchronous tasks 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. 4.1 IBM ECM Task Manager 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. 4.2 Custom Asynchronous Tasks 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 4.3 Creating a Custom Asynchronous Task Creating a custom asynchronous task requires extending from the base task called “com.ibm.ecm.task.commonj.work.BaseTask.” – the foundational class for all asynchronous tasks. You will need to implement the “performTask()” 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 IBMECMDevCoursePlugin 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 : C:\ICN Development Course Materials\Advanced Course Materials\Libs. 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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 0 Right click IBMECMDevCourseProject 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 src/com/ibm/ecm/extension and name it “IBMDevAsyncTask”. 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
import
import
import
import
import
com.ibm.ecm.task.Constants;
com.ibm.ecm.task.TaskLogger;
com.ibm.ecm.task.commonj.work.BaseTask;
com.ibm.ecm.task.entities.Task;
com.ibm.ecm.task.utils.Utils;
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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 2 TaskLogger.fine(IBMDevAsyncTask.class.toString(),
"performTask", "Starting task.");
//grabbing information from the task info
JSONObject taskInfo =
JSONObject.parse(task.getTaskInfo());
Thread.sleep(5000);
JSONObject results = new JSONObject();
results.put("default_label", "This task completed
successfully!");
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
taskInfo.put("results", resultsString);
this.task.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 IBMDevCoursePlugin\bin\com\ibm\ecm\extension\IBMDevAsyncTask.class IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 6
3 With the class file, using Windows Explorer again, browse to C:\IBM\ECMClient\configure\explodedformat\taskManager\taskManagerWe
b\WEB-­‐INF\dropins 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 Stop AppSrv01 and then Start AppSrv01. Once Websphere restarts, your custom task is now ready to be used. 4.4 Registering the Custom Asynchronous Task 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 “IBMDevCoursePluginAsyncTaskType” under “com/ibm/ecm/extension”. 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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 4 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. 4.5 Creating a Custom Scheduling Pane Navigate to Eclipse and create these two new javascript files under com\ibm\ecm\extension\WebContent\iBMDevCoursePluginDojo 1) TaskCreationDialog.js 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); } } }); }); 2) TaskCreationPane.js 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 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 6 You will also need to create two template files to correspond with the two panes under com/ibm/ecm/extension/WebContent/iBMDevCoursePluginDojo/templates 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> <tr> <td class="propertyRowLabel"> <label for="${id}_property">Sample property:</label> </td> <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> </td> </tr> </tbody> </table> In the end you should see this: What you are doing is creating a custom task creation dialog which extends from the BaseTaskCreationDialog 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. 4.6 Scheduling Your Custom Task Once you have all the files created, let’s start getting the Navigator Task Pane running and scheduling some tasks. 6 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 8 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 Dev Async Task under the Schedule button. Click on schedule task action and it should launch the custom task creation dijits and panes that you added. 7 IBM CONTENT NAVIGATOR PLUG-­‐IN DEVELOPMENT 0 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 Scheduled status. Refresh the list again and it should be In-­‐Processing status and then finally in a Completed 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 
Download
Related flashcards
Create Flashcards