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  “ will  route  the  call  to  your  “ framework  handles  it  automatically.  

performAction 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   ”  method.  There  is  no  need  for  you  to   implement  any  user  interface  source  code  for  this  to  happen.  The  Content  Navigator     In  addition,  to  the  identifier,  name  and  perform  action  methods,  the  repository  type   definition  defines  two  widget  classes  with  the  methods,   “

getConfigurationDijitClass

”  and  “

getConnectedConfigurationDijitClass

Additional  panels  are  available  after  the  administrator  has  connected  to  the   repository,  allowing  the  administration  to  add  any  additional  configuration   will  be  used  in  the  repository  configuration.   ”.  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.   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     The  final  methods  in  the  repository  type  class  are,  “

logon

”,  “

logoff

”,     “ “

getRepositoryModelClass getRepositoryModelClass

returns  a  “ repository  type.   ”  and  “

getIconClass

”  method.  The  “

PluginRepositoryConnection

”.  The  “

logon

”  and  “

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  

logoff

”  methods  are  used  to   handle  the  authentication  and  disconnect  from  the  repository.  The  “logon”  method   ”  type,  which  will  represent  your   repository  type  on  the  server-­‐side.  You  must  implement  this  in  order  to  enable  your    

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  “ “ “

com.ibm.ecm.extension

repositoryType

for  the  repository  type.  

PluginRepositoryConnection

”  package  and  create  a  new  package  called   ”  and   enable  the  log  in  and  log  out  methods  in  your  repository  type.  Right-­‐click  on  the   ”.  You  will  use  this  new  package  to  isolate  the  classes  you  create   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

{

this.getClass().getClassLoader().getResourceAsStream("com/ibm/ecm/exten sion/data/repository.json"); super // TODO Auto-generated constructor stub InputStream inStream = null; try { } finally { }

(type, userId,

// Load the repository JSON inStream = repositoryJSON = JSONObject.

if (inStream != null) { } userId

);

inStream.close();

parse

(inStream);

}

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  “ Right-­‐click  on  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.  

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 com.ibm.ecm.configuration.RepositoryConfig; import com.ibm.ecm.extension.PluginRepositoryConnection; import com.ibm.ecm.extension.PluginRepositoryLogonException; import com.ibm.ecm.extension.PluginServiceCallbacks; 8   IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

   

   

import com.ibm.ecm.json.JSONResponse; import com.ibm.json.java.JSONArray; import com.ibm.json.java.JSONObject; public class

RepositoryLogon {

public

RepositoryLogon() { } //

TODO

Auto-generated constructor stub

@SuppressWarnings("unchecked") public void performAction(PluginServiceCallbacks callbacks, RepositoryConfig repositoryConfig, HttpServletRequest request, HttpServletResponse response) throws Exception { String loginRepoId = request.getParameter("repositoryId"); request, userid, password, repositoryConfig); 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, if (connection != null) { pluginRepositoryConnectionList = (Map) (request.getSession((true)).getAttribute("plugin_repositories")); Map if (pluginRepositoryConnectionList == null) { HashMap(); pluginRepositoryConnectionList); request.getSession(true).setAttribute("plugin_repositories", } pluginRepositoryConnectionList = new 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); } public PluginRepositoryConnection logon(PluginServiceCallbacks callbacks, HttpServletRequest request, String userid, String password, RepositoryConfig repositoryConfig) throws PluginRepositoryLogonException { callbacks.writeJSONResponse(jsonResponse, response); // 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("IBMDevCourseRepository", userid, repositoryConfig); } else { } RepositoryConnection connection = new return connection; } catch (Exception e) { throw new PluginRepositoryLogonException(e); 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 0  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

   

(RepositoryConnection)callbacks.getPluginRepositoryConnection(repositor yId); logoff(callbacks, request.getSession(false), connection); } session, PluginRepositoryConnection connection) throws Exception { public void logoff(PluginServiceCallbacks callbacks, HttpSession callbacks.getLogger().logInfo(this, "logoff", "Logged off"); actual work to do in this method

}

} // Since the connection is a dummy connection there is no

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

java.lang.reflect.Method;

import

java.util.HashMap;

import

java.util.Locale;

import

java.util.Map;

import

javax.servlet.http.HttpServletRequest;

import

javax.servlet.http.HttpServletResponse;

import

javax.servlet.http.HttpSession;

import

com.ibm.ecm.configuration.RepositoryConfig;

import

com.ibm.ecm.extension.PluginRepositoryConnection;

import

com.ibm.ecm.extension.PluginRepositoryType;

import import

com.ibm.ecm.extension.PluginServiceCallbacks;

import com.ibm.ecm.extension.repositoryType.RepositoryLogoff; import com.ibm.ecm.extension.repositoryType.RepositoryLogon;

com.ibm.ecm.json.JSONMessage;

import

com.ibm.ecm.json.JSONResponse; /** * Provides an abstract class that is extended to create a class defining a custom repository type provided by the * plug-in. Additional PluginService classes are implemented to provide the different services of the repository type. *

* 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. *

* 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

IBMDevCourseRepositoryActionMap

=

new

HashMap() {

private static final long

serialVersionUID

= 7212659949366854547L; {

"com.ibm.ecm.extension.repositoryType.RepositoryLogon"); "com.ibm.ecm.extension.repositoryType.RepositoryLogoff");

};

public

}

void put("logon", put("logoff",

performAction(PluginServiceCallbacks callbacks, RepositoryConfig repositoryConfig, String action, HttpServletRequest request, HttpServletResponse response)

throws

Exception {

if

(

IBMDevCourseRepositoryActionMap

.containsKey(action)) { callbacks.getLogger().logDebug(

this

, "performAction", "Executing \"" + action + "\"..."); Object obj = Class.

forName

(

IBMDevCourseRepositoryActionMap

.get(action)).newInstance( ); Method m = obj.getClass().getMethod("performAction", PluginServiceCallbacks.

class

, RepositoryConfig.

class

, HttpServletRequest.

class

, HttpServletResponse.

class

); response); + "\"."); }

else

{ m.invoke(obj, callbacks, repositoryConfig, request, callbacks.getLogger().logError(

this

, "performAction", "${PluginClassName} does not support the repository action \"" + action // return JSON with an error as well for the client "${PluginClassName} does not support the action", "The repository action \""+action+"\" is not supported.", } } JSONResponse jsonResponse =

new

JSONResponse(); jsonResponse.addErrorMessage(

null new

, JSONMessage(20005,

null

,

null

)); response.getWriter().write(jsonResponse.serialize()); @Override

public

String getId() { }

public return

"IBMDevCourseRepository"; @Override String getName(Locale locale) {

return

"IBMDevCourseRepository";

1 2  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

    }

return "IBM Development Course Repository";

@Override

public

String getConfigurationDijitClass() { "iBMDevCoursePluginDojo.IBMDevCourseRepositoryGeneralConfigurationPane" ; }

public return

String getConnectedConfigurationDijitClass() { "iBMDevCoursePluginDojo.IBMDevCourseRepositoryConfigurationParametersPa

ne"; } @Override

public return

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 {

session, connection);

}

RepositoryLogoff.class.newInstance().logoff(callbacks,

@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 4  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

   

    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 6  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

    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 8  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

   

} public String getName() { } } } } return (String)attributeDefinitionJSON.get("id"); 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")) { (Boolean)attributeDefinitionJSON.get("system"); } return 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 getContentClass().getAttributeDefinitions()[0]; getAttributeValue(nameAttributeDef.getId()).toString(); (JSONObject)contentItemJSON.get("attributes"); } public int getAttributeCount() { AttributeDefinition nameAttributeDef = return JSONObject attributes = return attributes.size(); (JSONObject)contentItemJSON.get("attributes"); } @SuppressWarnings("unchecked") public String[] getAttributeIds() { } JSONObject 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) { just temporary to the in-memory copy (JSONObject)contentItemJSON.get("attributes"); } // Note: This is not a permanent change to the json file, JSONObject attributes = attributes.put(attributeId, value); /** * If the item is a folder, this returns folder contents. * @return */ 2 0  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

   

(JSONArray)contentItemJSON.get("contentItemIds");

}

@SuppressWarnings("unchecked") public String[] getContentIds() { String[contentItemIdsJSON.size()]; } public boolean isFolder() { } public boolean isDocument() { } public String getContentType() { } JSONArray contentItemIdsJSON = if (contentItemIdsJSON == null) { } return new String[0]; String[] contentItemIds = new contentItemIdsJSON.toArray(contentItemIds); return contentItemIds; return getContentClass().isFolderClass(); return getContentClass().isDocumentClass(); if (isFolder()) { return "folder"; } return (String)contentItemJSON.get("contentType");

  Please  note  the  above  code  will  not  compile  yet.  There  are  additional  changes  you   will  need  to  make  to  the  RepositoryConnection  class  in  order  to  fully  enable  the   model  objects.     Right-­‐click  on  the  “com.ibm.ecm.extension.repositoryType”  package  and  create  the   last  model  object,  “

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 RepositoryConnection connection; private JSONObject contentClassJSON; private String[] attributeDefinitionIds; private AttributeDefinition[] attributeDefinitions; public ContentClass(RepositoryConnection connection, JSONObject contentClassJSON) { this.connection = connection; } this.contentClassJSON = contentClassJSON; public String getId() { IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT   2 1

 

} public String getName() { } return (String)contentClassJSON.get("id"); return (String)contentClassJSON.get("name"); contentClassJSON.get("attributeDefinitionIds"); String[attributeDefinitionIdsJSON.size()]; @SuppressWarnings("unchecked") public String[] getAttributeDefinitionIds() { } if (attributeDefinitionIds == null) { JSONArray attributeDefinitionIdsJSON = (JSONArray) attributeDefinitionIds = new 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++) { connection.getAttributeDefinition(attributeDefinitionIds[i]); } } attributeDefinitions[i] = return attributeDefinitions; } public boolean isFolderClass() { } return contentClassJSON.containsKey("semanticType") && contentClassJSON.get("semanticType").equals("folder"); public boolean isDocumentClass() { contentClassJSON.get("semanticType").equals("document");

}

} return contentClassJSON.containsKey("semanticType") &&

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 2  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

   

import

com.ibm.json.java.JSONArray;

import

com.ibm.json.java.JSONObject;

public class

RepositoryConnection

extends

PluginRepositoryConnection {

private

JSONObject repositoryJSON;

private Hashtable attributeDefinitions; private Hashtable contentClasses; public

RepositoryConnection(String type, String userId, RepositoryConfig repositoryConfig)

throws

Exception {

super try

(type, userId, userId); InputStream inStream = {

null

;

this

.getClass().getClassLoader().getResourceAsStream("com/ibm/ecm/exten sion/data/repository.json"); repositoryJSON = JSONObject.

parse

(inStream);

AttributeDefinition>(); repositoryJSON.get("attributeDefinitions");

// Load the repository JSON inStream =

// Cache the attribute definitions attributeDefinitions = new Hashtable(); repositoryJSON.get("contentClasses"); JSONArray contentClassesJSON = (JSONArray) for (int i = 0; i < contentClassesJSON.size(); i++) { // Cache the content classes contentClasses = new Hashtable

} }

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 contentClassCollection = contentClasses.values(); contentClassArray = contentClassCollection.toArray(contentClassArray); return contentClassArray; } attributeDefinitionId) { (attributeDefinitions.containsKey(attributeDefinitionId)) { public AttributeDefinition getAttributeDefinition(String attributeDefinitions.get(attributeDefinitionId); if } throw new RuntimeException("Attribute definition not found with id " + attributeDefinitionId return + ". 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 4  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

   

contentItemsJSON.get(i); JSONObject contentItemJSON = (JSONObject) 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 com.ibm.ecm.extension.PluginServiceCallbacks; import com.ibm.ecm.json.JSONMessage; import com.ibm.ecm.json.JSONResultSetColumn; import com.ibm.ecm.json.JSONResultSetResponse; import com.ibm.ecm.json.JSONResultSetRow; import com.ibm.json.java.JSONArray; import com.ibm.json.java.JSONObject; public class

RepositoryUtils {

public static JSONResultSetResponse buildResultSetResponse(PluginServiceCallbacks callbacks, RepositoryConnection connection, ArrayList contentItemList) { JSONResultSetResponse resultSetResponse = new JSONResultSetResponse(); try { columns that are Hashtable(); contentItem.getContentClassId(); (!contentClasses.containsKey(contentClassId)) { // Iterate through all the items and determine the // needed to present all of the item attributes Hashtable contentClasses = new for (ContentItem contentItem : contentItemList) { String contentClassId = if contentClasses.put(contentClassId, IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT   2 5

 

connection.getContentClass(contentClassId)); } ArrayList(); } ArrayList joinedAttributeDefIds = new Enumeration 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(" ", "multiStateIcon", false, new String[0])); JSONResultSetColumn(" ", "17px", "mimeTypeIcon", null, false)); resultSetResponse.addColumn(new for (int i = 0; i < joinedAttributeDefIds.size(); i++) { joinedAttributeDefIds.get(i); connection.getAttributeDefinition(attributeDefinitionId); String attributeDefinitionId = AttributeDefinition attributeDef = if (!attributeDef.isSystem()) { JSONResultSetColumn(); JSONResultSetColumn resultSetColumn = new resultSetColumn.setName(attributeDef.getName()); resultSetColumn.setField(attributeDef.getId()); resultSetColumn.setWidth(Integer.

toString

(Math.

min

(attributeDef.g

etMaxLength(), 10)) + "em"); resultSetResponse.addColumn(resultSetColumn); } } the sample, just // Add magazine columns for the magazine view. For // a thumbnail and the first attribute of the class(es) is displayed. Usually // this would be configurable and likely provide important timestamps resultSetResponse.addMagazineColumn(new JSONResultSetColumn("thumbnail", "60px", "thumbnail", null, null)); JSONArray fieldsToDisplay = new JSONArray(); 2 6  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

   

if (joinedAttributeDefIds.size() > 0) { String attributeDefinitionId = joinedAttributeDefIds.get(0); AttributeDefinition attributeDef = connection.getAttributeDefinition(attributeDefinitionId); JSONObject jsonObj = new JSONObject(); jsonObj.put("field", attributeDef.getId()); jsonObj.put("displayName", attributeDef.getName()); fieldsToDisplay.add(jsonObj); JSONResultSetColumn("content", "100%", "content", fieldsToDisplay, null)); } resultSetResponse.addMagazineColumn(new // Add the rows to the result response for (ContentItem contentItem : contentItemList) { contentItem.getName(), JSONResultSetRow resultSetRow = new JSONResultSetRow(contentItem.getContentClassId(), contentItem.getId(), contentItem.getContentType(), JSONResultSetRow.

PRIV_VIEWDOC

| JSONResultSetRow.

PRIV_EXPORT

| JSONResultSetRow.

PRIV_EMAILDOC

| JSONResultSetRow.

PRIV_PRINTDOC

| JSONResultSetRow.

PRIV_EDITPROPERTIES

); contentItem.getAttributeIds(); resultSetRow.setName(contentItem.getName()); String[] attributeIds = for (int j = 0; j < attributeIds.length; j++) { contentItem.getAttributeValue(attributeId); attributeValue, null, null, null); } } String attributeId = attributeIds[j]; Object attributeValue = resultSetRow.addAttribute(attributeId, resultSetResponse.addRow(resultSetRow); } catch (Exception e) { "buildResultSetResponse", e); content server is not available.", callbacks.getLogger().logError(RepositoryUtils.class, JSONMessage message = new JSONMessage(30000, "The "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"); callbacks.getPluginRepositoryConnection(repositoryId); RepositoryConnection connection = (RepositoryConnection) 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(); JSONResultSetResponse jsonResponse = new ContentItem folder; ArrayList(); if (docid.equals("/")) { } folder = connection.getRootItem(); } else { folder = connection.getContentItem(docid); String[] contentItemIds = folder.getContentIds(); ArrayList contentItemList = new for (int i = 0; i < contentItemIds.length; i++) { connection.getContentItem(contentItemIds[i]); ContentItem contentItem = if (!foldersOnly || contentItem.isFolder()) { } } contentItemList.add(contentItem); jsonResponse = 2 8  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

   

RepositoryUtils.

buildResultSetResponse

(callbacks, connection, contentItemList); callbacks.writeJSONResponse(jsonResponse, response);

}

}

The  open  folder  action  uses  the  folder  id  passed  by  the  client  to  retrieve  the   appropriate  folder  from  the  respository.json  file.  If  the  client  passes  a  folder  id  of   “

/

”,  it  represents  the  root,  so  the  open  folder  action  obtains  the  root  item  in  the  tree   from  the  RepositoryConnection  class.  Finally  the  utility  method  you  created  earlier   is  used  to  create  the  JSON  response  for  the  client.     One  more  action  is  required  to  enable  the  folder  browsing  capability.  Right-­‐click  on   the  “

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)callbacks.getPluginRepositoryConnection(repositor yId); first document JSONResultSetResponse(); RepositoryConnection connection = String docid = request.getParameter("docid"); // id of JSONResultSetResponse jsonResponse = new ArrayList contentItemList = new ArrayList(); if (docid.equals("/")) { contentItemList.add(connection.getRootItem()); jsonResponse = RepositoryUtils.

buildResultSetResponse

(callbacks, connection, contentItemList); } else { request.getParameterValues("docid"); String[] docids = 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

IBMDevCourseRepositoryActionMap

=

new

HashMap() {

private static final long

serialVersionUID

= 7212659949366854547L; { "com.ibm.ecm.extension.repositoryType.RepositoryLogon"); put("logon", 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 0  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

    JSONResponse jsonResponse =

new

JSONResponse(); jsonResponse.addErrorMessage( "${PluginClassName} does not support the action", "The repository action \""+action+"\" is not supported.", } } } } @Override

public

String getId() { @Override

null return

"IBMDevCourseRepository";

public

String getName(Locale locale) {

new

, JSONMessage(20005,

null

,

null return

"IBM Development Course Repository"; )); response.getWriter().write(jsonResponse.serialize()); ; @Override

public

String getConfigurationDijitClass() {

return

"iBMDevCoursePluginDojo.IBMDevCourseRepositoryGeneralConfigurationPane" }

public

String getConnectedConfigurationDijitClass() { } "iBMDevCoursePluginDojo.IBMDevCourseRepositoryConfigurationParametersPa

ne"; } @Override

public return

PluginRepositoryConnection logon(PluginServiceCallbacks callbacks, HttpServletRequest request, String userid, String password, RepositoryConfig repositoryConfig)

throws

Exception { PluginRepositoryConnection connection = RepositoryLogon.

class

password, repositoryConfig);

return

.newInstance().logon(callbacks, request, userid, 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:    

"propertyTable"

role=

"presentation"

> Name: 

_onParamChange"

"propertyRowLabel"

>

"required"

>*

"${id}_serverName"

>Server

"propertyRowValue"

>

"${id}_serverName"

data-dojo-attach-point= data-dojo-attach-event=

"serverNameField" "onKeyUp:

data-dojo type=

"ecm/widget/ValidationTextBox"

data-dojo-props=

"

required: true, trim: true, propercase: false, maxLength: 64

"

>

This  adds  a  required  field  to  the  widget  template,  prompting  the  administrator  for  a   “server  name”  parameter.  This  is  a  parameter  provided  by  default  in  the  standard   repository  configuration  object  in  Content  Navigator  and  a  typical  field  required  for    

3 2  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

    repositories.  Open  “

IBMDevCourseRepositoryGeneralConfigurationPane.js

”  and   make  the  following  changes:     define([ "dojo/_base/declare", "dijit/_TemplatedMixin", "dijit/_WidgetsInTemplateMixin",

"ecm/widget/ValidationTextBox",

"ecm/widget/admin/PluginRepositoryGeneralConfigurationPane", Pane.html" "dojo/text!./templates/IBMDevCourseRepositoryGeneralConfiguration ],

function

(declare, _TemplatedMixin, _WidgetsInTemplateMixin,

ValidationTextBox,

PluginRepositoryGeneralConfigurationPane, template) { /** *

@name

iBMDevCoursePluginDojo.IBMDevCourseRepositoryGeneralConfigurationPane repository configuration for the repository type. This panel appears configuration page in administration when creating or editing a repository *

@class

* * Provides a configuration panel for general on the general tab of the repository of the defined repository type. ecm.widget.admin.PluginRepositoryGeneralConfigurationPane rototype */ *

@augments

*/

return

/** declare("iBMDevCoursePluginDojo.IBMDevCourseRepositoryGeneralConfigurat

ionPane", [ PluginRepositoryGeneralConfigurationPane, _TemplatedMixin, _WidgetsInTemplateMixin ], {

@lends

iBMDevCoursePluginDojo.IBMDevCourseRepositoryGeneralConfigurationPane.p

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 4  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

     

"propertyTable"

role=

"presentation"

>

1: 

_onParamChange"

data-dojo type=

"ecm/widget/ValidationTextBox"

data-dojo-props=

"

required: true, trim: true, propercase: false, maxLength: 64

"

> 2: 

_onParamChange"

"propertyRowValue"

>

"propertyRowLabel"

>

"${id}_param2"

>Config Param

"propertyRowValue"

>

"propertyRowLabel" "required" "${id}_param1" "${id}_param1"

data-dojo-attach-point= data-dojo-attach-event=

"${id}_param2"

> >* >Config Param data-dojo-attach-point= data-dojo-attach-event=

"param1Field" "onKeyUp: "param2Field" "onKeyUp:

data-dojo type=

"ecm/widget/ValidationTextBox"

data-dojo-props=

"

required: true, trim: true, propercase: false, maxLength: 64

"

>

  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",

ersPane.html" "ecm/widget/admin/PluginRepositoryConfigurationParametersPane", "dojo/text!./templates/IBMDevCourseRepositoryConfigurationParamet ],

function

(declare,

dojojson,

_TemplatedMixin,

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT   3 5

 

_WidgetsInTemplateMixin, /** *

@name ValidationTextBox,

PluginRepositoryConfigurationParametersPane, template) { iBMDevCoursePluginDojo.IBMDevCourseRepository repository configuration for the repository type. This panel appears repository * * configuration page in administration when creating or editing a *

@class

Provides a configuration panel for general on the general tab of the repository of the defined repository type. ecm.widget.admin.PluginRepositoryGeneralConfigurationPane e.prototype */ *

@augments

*/

return

/** declare("iBMDevCoursePluginDojo.IBMDevCourseRepositoryConfigurationPara

metersPane", [ PluginRepositoryConfigurationParametersPane, _TemplatedMixin, _WidgetsInTemplateMixin], {

@lends

iBMDevCoursePluginDojo.IBMDevCourseRepositoryConfigurationParametersPan

templateString: template, widgetsInTemplate:

true

, load:

function

(repositoryConfig) {

repositoryConfig.getCustomProperties(); var customProperties = if (customProperties) { dojojson.fromJson(customProperties); var jsonConfig = this.param1Field.set('value',jsonConfig.configuration[0].value); this.param2Field.set('value',jsonConfig.configuration[1].value);

},

} _onParamChange: function() { },

validate:

function

() { },

this.onSaveNeeded(true); if(!this.param1Field.isValid()) return false; return true;

save:

function

(repositoryConfig) {

var configArray = []; var configString = { }; name: "param1Field", value: this.param1Field.get('value') configArray.push(configString); configString = { 3 6  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

 

;

});

name: "param2Field", value: this.param2Field.get('value') }; configArray.push(configString); var configJson = { "configuration" : configArray }; repositoryConfig.setCustomProperties(dojojson.toJson(configJson))

}); }     These  changes  load  and  save  the  custom  configuration  parameters  in  a  JSON  string   on  the  repository  configuration.  After  these  changes  you  should  now  find  the  new   prompts  on  the  repository  configuration  tab  after  you  log  in  to  your  repository  type.     Figure  1.14  

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 8  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

   

    The  Plug-­‐in  API  Java  class,  like  other  server  extensions,  is  a  simple  class  structure.  It   includes  only  an  identifier  and  an  execute  method.  Make  the  following  changes  to   the  execute  method  of  the  Plug-­‐in  API  Java  class:    

package

com.ibm.ecm.extension;

import

javax.servlet.http.HttpServletRequest;

import com.ibm.json.java.JSONObject;

/** * Provides an abstract class that is extended to create a class implementing an API provided by the plug-in. A plug-in * API can be invoked from other plug-ins, allowing one plug-in to provide services that another plug-in service can * use. A plug-in API is similar to a plug-in service, receiving an instance of PluginServiceCallbacks and the original * HttpServletRequest object, allowing access to any session state and underlying APIs.

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 PluginServiceCallbacks 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 HttpServletRequest from the current request being processed. The service can access * the invocation parameters from the request as well as session state. *

@param

arguments the API. The particular structure of these object is the objects used as arguments should be J2SE classes specific classes will be loaded by different * An object array containing the input arguments to * defined by the plug-in API writer. The classes of * or instances of com.ibm.ecm.json as any plug-in * 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

 

a J2SE class or instance of com.ibm.ecm.json as * plug-in API writer. The class of the object should be * any plug-in specific classes will be loaded by different classloaders causing class cast exceptions when * attempting to use the object by the invoker. If the logging level is high enough to log errors, Content Navigator. *

@throws

Exception * For exceptions that occur when the API is running. * information about the exception is logged by IBM */

public

Object execute(PluginServiceCallbacks callbacks, HttpServletRequest request, Object[] arguments)

throws

Exception {

String[] {"1"});

} }

JSONObject jsonObj = new JSONObject(); String[] configData = callbacks.loadConfigurations(new 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 0  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

    Figure  2.2     Since  this  plug-­‐in  will  leverage  a  Plug-­‐in  API  provided  by  our  first  plug-­‐in,  it  must   register  the  IBMDevCoursePlugin  as  a  dependency.  Make  the  following  changes  to   your  newly  created  plug-­‐ins  main  plug-­‐in  Java  class,  “

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];

null

;

private

PluginODAuthenticationService odAuthenticationService =

private

PluginViewerDef[] pluginViewerDefs =

new IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT   4 1

 

PluginViewerDef[0];

private

PluginLayout[] pluginLayouts =

new

PluginLayout[0];

private

PluginFeature[] pluginFeatures =

new

PluginFeature[0];

private private

PluginMenuType[] pluginMenuTypes =

new

PluginMenuType[0];

private

PluginMenu[] pluginMenus =

new

PluginMenu[0]; PluginRepositoryType[] pluginRepositoryTypes = PluginRepositoryType[0];

private

PluginAPI[] pluginAPIs =

new

PluginAPI[0];

private String[] pluginDependencies = new String[0]; new

/** * 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. *

* Important: This identifier is used in path names and * URLs so it must contain only alphanumeric characters. *

*/

public

String getId() {

return

"IBMDevCoursePlugin2"; } /** * Provides a descriptive name for this plug-in. The name identifies the * plug-in in the IBM Content Navigator administration tool * *

@parm

locale The locale currently in use. The name should be returned in * the language for this locale if it is translated. */

public

String getName(Locale locale) { }

return

"IBM Development Course Plugin 2"; /** * Provides a version indicator for this plug-in. The version information is * displayed in the administration tool to help the administrator

4 2  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

    validate * the version of the plug-in that is being used. The plug-in writer needs * to update this indicator when redistributing a modified version of the * plug-in. indicator for * the plug-in. * *

@return

A String representation of the version */

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 String representation of the license for the * plug-in. *

@since

2.0.2 when */

public

String getCopyright() { }

return

"Optionally add a Copyright statement here"; /** * Returns the name of a JavaScript file provided by this plug in. 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 * configuring desktops. */

public

} /** String getScript() {

return

"IBMDevCoursePlugin2.js"; provided by * Returns the name of a debug version of the JavaScript file * getScript(). The default implementation invokes getScript(). *

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT   4 3

 

* */

public

}

@since

2.0.2 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 * dojo.registerModulePath mapping to allow modules or widgets * with mapped path names to be loaded by using the the directory * dojo.require method. A specified module can be * 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 dijit class that provides a * configuration interface widget for this plug-in. The widget must extend * the ecm.widget.admin.PluginConfigurationPane widget. An * instance of the widget is created and displayed in the IBM Content * Navigator administration tool for configuration that is specific to the * plug-in.

4 4  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

    *

* Refer to the documentation on PluginConfigurationPane} * {@link ecm.widget.admin.PluginConfigurationPane * for more information on what is required for a plug-in configuration user * interface. *

*/

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 PluginAction} * {@link com.ibm.ecm.extension.PluginAction * 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 * {@link com.ibm.ecm.extension.PluginOpenAction PluginOpenAction} * 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. PluginRequestFilter} * {@link com.ibm.ecm.extension.PluginRequestFilter * objects. * *

@return

An array of */

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 * {@link com.ibm.ecm.extension.PluginResponseFilter PluginResponseFilter} * objects. */

public

} PluginResponseFilter[] getResponseFilters() {

return

pluginResponseFilters; /** * Provides a list of services that are provided by this plug-in. The browser * services run on the web server, and can be called by the web * logic component of the plug-in. * *

@return

An array of {@link com.ibm.ecm.extension.PluginService same set of * PluginService} objects. The plug-in should return the * 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 Manager * service will be the information passed through the Content * OnDemand Web Enablement Kit "passThru" API. * *

@since

2.0.2 com.ibm.ecm.extension.PluginODAuthenticationService *

@return

authentication A {@link * PluginODAuthenticationService} object used as an * exit for Content Manager OnDemand single sign-on. { */

public

PluginODAuthenticationService getODAuthenticationService()

return

odAuthenticationService;

4 6  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

    } /** * Provides a list of viewers that are provided by this plug-in. The viewers * become available in the viewer configuration area of the IBM Content * Navigator administration tool. The viewers can be mapped to be used to * view certain document types. *

* Note: Typically, a plug-in does not define multiple * viewers. However, this method can be used to provide multiple and an * configurations of the same viewer, such as a view-only version * editing mode version of the same viewer. *

* *

@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; } /** provided by this plug-in. * Provides a list of one or more custom repositories that are * *

@return

An array of plug-in repository types. */

public

} PluginRepositoryType[] getRepositoryTypes() {

return

pluginRepositoryTypes; /** by this plug-in. * Provides a list of one or more custom APIs that are provided * * */

public

}

@return

An array of plug-in APIs. PluginAPI[] getPluginAPIs() {

return

pluginAPIs;

/** plug-ins that this plug-in depends on. * Provides a list of identifiers (the plug-in Ids) of all the * * @return An array of plug-in identifier strings. * @since 2.0.3 */ public String[] getPluginDependencies() { if (pluginDependencies.length == 0) { "IBMDevCoursePlugin" }; } pluginDependencies = new String[] { return pluginDependencies; }

  }

4 8  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

    Now  that  you  have  registered  the  dependency  the  next  step  is  to  create  a   PluginService  that  will  leverage  the  Plugin  API  from  the  first  plug-­‐in.  Right-­‐click  on   the  “

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. *

* Important: This identifier is used in URLs so it must * contain only alphanumeric characters. *

* *

@return

A String 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 null. *  

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT   4 9

 

* */

public

}

@returns

The name of the service. String getOverriddenService() {

return null

; the /** * Performs the action of this service. * *

@param

callbacks * An instance of the PluginServiceCallbacks class * that contains several functions that can be used by * service. These functions provide access to the plug-in * configuration and content server APIs. provides the *

@param

request * The HttpServletRequest object that * request. The service can access the invocation parameters from * the request. generated stream and *

@param

response * The HttpServletResponse object that is * by the service. The service can get the output * write the response. The response must be in JSON format. *

@throws

Exception running. If the * For exceptions that occur when the service is * logging level is high enough to log errors, information about * the exception is logged by IBM Content Navigator. HttpServletRequest request, HttpServletResponse response) Exception {

PluginLogger logger = callbacks.getLogger();

*/

public void

execute(PluginServiceCallbacks callbacks,

logger.logEntry(this, "execute"); throws JSONResponse jsonResponse = new JSONResponse(); callbacks.executePluginAPI("IBMDevCoursePlugin", "DevCourseConfigAPI", null); try { Object obj = if (obj != null) { } jsonResponse.put("data", obj); } catch (Exception e) { logger.logError(this, "execute", e); jsonResponse.addErrorMessage(new JSONMessage(20002, "Error occurred attempting to process a request to the ConfigService in plug-in IBMDevCoursePlugin2", null, null, null, null)); } finally { 5 0  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

    } }

response, jsonResponse, callbacks, "ConfigurationService"); } logger.logExit(this, "execute"); // Send response to the client PluginResponseUtil.

writeJSONResponse

(request,

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:    

Parameters from IBM Development Course Plug-in

"propertyTable"

role=

"presentation"

>

"propertyRowLabel" "propertyRowValue"

>   data-dojo-attach point=

"param1Value"

>

"propertyRowLabel"

> IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT   5 1

 

 

"propertyRowValue"

data-dojo-attach point=

"param2Value"

>

    Now,  update  the  “

ConfigurationPane.js

”  file  under   “

com.ibm.ecm.extension.devcourse.WebContent.iBMDevCoursePlugin2Dojo

”:     define([ }); ],

function

(declare,

lang,

_TemplatedMixin, _WidgetsInTemplateMixin, Request,

PluginConfigurationPane,

template) { [ PluginConfigurationPane, _TemplatedMixin, _WidgetsInTemplateMixin], { }); "dojo/_base/declare",

"dojo/_base/lang",

"dijit/_TemplatedMixin", "dijit/_WidgetsInTemplateMixin",

"ecm/model/Request",

"ecm/widget/admin/PluginConfigurationPane", "dojo/text!./templates/ConfigurationPane.html"

return

load:

"ConfigService",

}, declare("IBMDevCoursePlugin2Dojo.ConfigurationPane", templateString: template, widgetsInTemplate:

true

,

function function(response) { response.data.param1) { response.data.param1; response.data.param2) { response.data.param2; ); }

(callback) {

Request.invokePluginService("IBMDevCoursePlugin2", { requestCompleteCallback: lang.hitch(this, // success if (response.data && })

validate:

function

() { }

return true

;

} if (response.data && } this.param1Value.innerHTML = this.param2Value.innerHTML = 5 2  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

      This  change  will  cause  the  configuration  to  call  your  newly  created  service   immediately  when  it  loads  and  display  the  configuration  parameters  saved  by  the   other  plug-­‐in.   Figure  2.4        

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT   5 3

 

 

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 4  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

   

  After  you  create  the  new  file  you  are  ready  to  enable  your  open  action.  Right-­‐click   on  the  “

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 6  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

   

    “DevCourseOpenAction.java”  file  defines  your  open  action  for  the  Content  Navigator   plug-­‐in  framework.  It  includes  both  the  typical  methods  to  return  the  identifier  and   name  of  your  open  action.  Additionally  it  contains  the  methods   “

getOpenActionFunction

one  entry,  “p8”.   ”,  “

getContentTypes

”  and  “

getServerTypes

action.  In  this  case,  you  specified  the  action  only  supports  the  “ ”.    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  

application/json

the  list  of  repository  types  supported  by  the  open  action.  Since  you  specified  the   ”   mime  type.  Finally,  the  “getServerTypes”  method  returns  a  String  array  containing   open  action  applies  only  to  the  FileNet  P8  repository  type,  the  array  contains  only     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", { parameterMap.widget.isInstanceOf(ecm.widget.listView.ContentList)) { ResultSet(response); parameterMap.widget.setResultSet(resultSet); ); } requestCompleteCallback:

function

(response) { }

if

} (parameterMap.widget && response.repository = repository;

var

resultSet =

new

}); 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 8  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

   

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) 3) Any  sweep-­‐like  jobs  that  are  CPU  intensive  and  may  take  hours  to   complete.   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) 3) Batch  and  parallel  processing  support  for  performance  and  scale  of  heavy   tasks   Clustered  deployment  for  fail-­‐over  safety   4) 5) Full  auditing  capabilities   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) 2) 3) 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.   Registering  the  custom  task  with  IBM  Content  Navigator  so  it  can  be  used   within  the  default  Navigator  Task  Pane.   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 0  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

          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) 3) 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.   LogDirectory  –  This  is  the  directory  where  any  logs  for  the  task  will  be  logged   into.    By  default,  this  log  directory  is  set  as  “taskManager”  and  located  as  a   child  of  the  log  directory  of  IBM  Content  Navigator.   4) Email  notifications  –  A  task  is  able  to  notify  users  when  a  task  completes,   fails,  or  errors  out.    There  are  methods  to  set  the  content  and  subject  of  how   the  email  will  be  presented  when  one  of  these  statuses  of  the  task  occur.         As  noted  above,  we  are  going  to  be  writing  a  simple  stimulated  task.    Change   IBMDevAsyncTask  to  look  like  this:      

package

com.ibm.ecm.extension;

import

java.io.File;

import

java.io.IOException;

import

com.ibm.ecm.task.Constants;

import

com.ibm.ecm.task.TaskLogger;

import

com.ibm.ecm.task.commonj.work.BaseTask;

import

com.ibm.ecm.task.entities.Task;

import

com.ibm.ecm.task.utils.Utils;

import

com.ibm.json.java.JSONObject;

public class

IBMDevAsyncTask

extends

BaseTask { IOException {

super

(task, logDirectory);

public

IBMDevAsyncTask(Task task, File logDirectory)

throws

}

public

//

TODO

Auto-generated constructor stub

void

performTask() {

try

{

6 2  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

    TaskLogger.

fine

(IBMDevAsyncTask.

class

.toString(), "performTask" , "Starting task." ); //grabbing information from the task info JSONObject taskInfo = JSONObject.

parse

( task .getTaskInfo()); Thread.

sleep

(5000); successfully!" ); JSONObject results = results.put(

new

JSONObject(); "default_label" , "This task completed } saveTaskInfo(results, taskInfo);

catch

(Exception exp){

this

.addError( Utils.

captureStackTrace

(exp)); }

new

Long(0), setTaskStatus(Constants.

TASK_STATUS_FAILED

);

public void

saveTaskInfo(JSONObject results, JSONObject taskInfo)

throws

Exception { .

} String resultsString = results.serialize();

if

(

this

.

taskExecutionRecord !=

null

&& (

this

.

task .getTaskMode() == Constants.

TASK_RECURRING

||

this

.

task .getTaskMode() == Constants.

TASK_CALENDAR_ACTION

)){ );

this

.

taskExecutionRecord .setTaskExecutionInfo(results.serialize()

this

.updateTaskExecutionRecord(); }

else this

.

taskInfo.put( task "results" , resultsString); .setTaskInfo(taskInfo.serialize()); } }

this

.updateTask();   What  this  task  is  doing  is  that  it’s  deserializing  the  task’s  info.    It  will  then  simulate   some  type  of  batched  operations  by  sleeping  for  5  seconds.    At  the  end,  it  will  save  a   json  value  back  into  the  task’s  info  to  indicate  that  it  has  successfully.    In  the  save,  it   checks  to  see  if  this  is  a  recurring  task,  if  it  is,  it  will  save  to  the  task  execution   record  instead.         Once  you  have  finished  creating  your  task,  you  are  now  required  to  add  this  class   into  the  IBM  ECM  Task  Manager  for  it  to  process.    Use  Windows  Explorer  and   navigate  to  where  your  IBMDevCoursePlugin  is  located.    Browse  into  and  make  a   copy  of  

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 4  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

    returning  a  custom  task  name  that  you  want  to  display  to  the  user.    For  the   TaskCreationDialogDijitClass,  you  are  presenting  a  custom  user  interface  that  will   be  displayed  to  the  user  when  scheduling  your  task.    

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.taskCreationPane,  0);   },   this.inherited(arguments);   this.taskCreationPane  =  new  TaskCreationPane();   this.taskSchedulerPane.addTitlePaneSection("General",                     });               });           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 6  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

            You  will  also  need  to  create  two  template  files  to  correspond  with  the  two  panes   under  

com/ibm/ecm/extension/WebContent/iBMDevCoursePluginDojo/templates  

1) 2) TaskCreationDialog.html    

 
    TaskCreationPane.html                                             property:                           data-­‐dojo-­‐props="value:'abc',  trim:      
     

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT   6 7

 

                 

                 
    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 8  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

    Launch  Navigator  in  your  browser.    Click  on  the  administration  feature  icon.    Click   on  Desktops  and  then  edit  your  default  desktop  (Demo)  .    Go  to  the  Layout  tab  and   in  the  feature  list,  enable  the  Asynchronous  Tasks  feature  and  then  Save  and  Close.             Refresh  your  browser.      Once  Navigator  has  loaded,  click  on  the  Asynchronous  Tasks   Feature  Pane  on  the  left-­‐hand  side.      

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT   6 9

 

      This  Asynchronous  Feature  Pane  displays  all  the  asynchronous  tasks  you  have  in   your  system.    This  is  also  the  location  where  you  can  schedule  new  async  tasks  as   well.    Since  you  have  your  AsyncTaskType  registered,  you  should  see  your  

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 0  

 

IBM  CONTENT  NAVIGATOR  PLUG-­‐IN  DEVELOPMENT

 

    Enter  a  name  and  description  for  your  task.    You  can  also  change  the  schedule  as   well.    Once  you  are  done,  click  on  the  Schedule  button.      Once  your  task  has  been   scheduled,  you  should  see  a  new  field  in  async  task  list.    The  new  task  should  be  in  a   temporary  

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