WebSphere: How to Access Portal Theme Tags in JSP?

Add “<%@ taglib uri=”/WEB-INF/tld/portal.tld” prefix=”portal” %>” in order to access the portal theme tags like “<portal-logic/>” and “<portal-navigation/>“.

To call the tag function, simply call <portal:<function name>/>. For example in portal theme, we would call:


<portal-logic:if loggedIn="yes">...</portal-logic:if>

to


<portal:if loggedIn="yes">...</portal:if>

WebSphere Portal is Throwing “Could not load ‘dojo.nls.dojo_en'” error

We have logged a PMR with IBM recently about the browser is complaining that it could not load ‘dojo.nls.dojo_en’.

could not load dojo.nls.dojo_en

And IBM official response is to apply Deferred with Dojo (“profiles/profile_deferred.json”) theme profile directly to the page and this resolves the issue for me.

How to apply “Deferred with Dojo” profile:

  1. At the front-end, turn on the “Edit Mode“.
  2. Click on Menu (below Edit Mode) > Edit Page Properties.
  3. Click on the Advanced tab and select “Deferred with Dojo” Profile.
  4. Click on the “Save” button.

 

Note: A simple method (which I like) is to add in “resourceaggregation.profile” page property and set “profiles/profile_dojo_deferred.json” as its value.

 

How to Cook Pasta the Correct Way

How to Create WebSphere Portal Custom Workflow Action?

Custom Workflow Action comes in handy when you need the system to trigger a particular action in specific workflow’s stages, for example your clients want to

  • Insert/Remove a reference database record whenever a content get published/expired
  • Send email notification to subscribers whenever a news content get published for the first time

Disclaimer: This article follows the steps listed in Creating a custom workflow action class and we have replaced WebContentCustomWorkflowServicewith WcmCustomWorkflowService. At this point, we can’t confirm if WcmCustomWorkflowService is the correct class replacement.

Steps to create your custom workflow action:

  1. Open your Eclipse or IBM® Rational® Application Developer and make sure Java EE developer tools add-on is installed.
  2. Click on File > New > Web Project.
  3. Key in your custom workflow project’s name. Select “Simple” as Project Templates and “Java EE” as Programming Model.
  4. Under Deployment tab, select “2.4” as Web module version and checked “Add project to an EAR“.
    Click on the “Finish” button.
  5. Create a java class that implements the interface com.ibm.workplace.wcm.api.custom.CustomWorkflowAction. This class is responsible for executing your codes.
    Note: In this example, we will only be doing System.out.println to prove that the custom workflow action works

    package xxx.xxx.xxx.cwf;
    
    import java.util.Date;
    import javax.naming.InitialContext;
    import javax.naming.NamingException;
    import com.ibm.workplace.wcm.api.Document;
    import com.ibm.workplace.wcm.api.WcmCustomWorkflowService;
    import com.ibm.workplace.wcm.api.custom.CustomWorkflowAction;
    import com.ibm.workplace.wcm.api.custom.CustomWorkflowActionResult;
    import com.ibm.workplace.wcm.api.custom.Directive;
    import com.ibm.workplace.wcm.api.custom.Directives;
    
    public class NewsAlertSubscriptionAction implements CustomWorkflowAction {
    
     @Override
     public CustomWorkflowActionResult execute(Document doc) {
      // Put your customized trigger codes here
      System.out.println("Executing NewsAlertSubscriptionAction");
      String msg = "";
      InitialContext initCtx = null;
      WcmCustomWorkflowService customWorkflowService = null;
      CustomWorkflowActionResult result = null;
    
      try {
       initCtx = new InitialContext();
       customWorkflowService = (WcmCustomWorkflowService) initCtx.lookup(WcmCustomWorkflowService.JNDI_NAME);
      } catch (Exception e) {
        msg = " - System has encountered exception (do check logs).";
        e.printStackTrace();
      }
    
      // directive: indicate if the content should proceed to the next stage
      // Check out WCM Javadoc for more valid directives information
      Directive directive = Directives.CONTINUE;
      System.out.println(" - document:" + doc.getName());
      return customWorkflowService.createResult(directive, msg);
     }
    
     @Override
     public Date getExecuteDate(Document arg0) {
      return DATE_EXECUTE_NOW;
     }
    }
    
  6. Create a custom workflow action factory class that implements the interface com.ibm.workplace.wcm.api.custom.CustomWorkflowActionFactory. This is the controller class, it is use to call the respective custom workflow actions.
    package xxx.xxx.xxx.cwf;
    
    import java.util.Locale;
    import com.ibm.workplace.wcm.api.Document;
    import com.ibm.workplace.wcm.api.custom.CustomWorkflowAction;
    import com.ibm.workplace.wcm.api.custom.CustomWorkflowActionFactory;
    
    public class XXXCustomWorkflowActionFactory implements CustomWorkflowActionFactory {
     String NEWS_ALERT_SUBSCRIPTION = "News Alert Subscription";
    
     @Override
     public CustomWorkflowAction getAction(String arg0, Document arg1) {
      if (arg0.equals(NEWS_ALERT_SUBSCRIPTION)) {
       return new NewsAlertSubscriptionAction();
      }
      return null;
     }
    
     @Override
     public String getActionDescription(Locale arg0, String arg1) {
      if (arg0.equals(NEWS_ALERT_SUBSCRIPTION)) {
       return "Send Email Alert to Subscribers";
      }
      return null;
     }
    
     @Override
     public String[] getActionNames() {
      String names[] = { NEWS_ALERT_SUBSCRIPTION };
      return names;
     }
    
     @Override
     public String getActionTitle(Locale arg0, String arg1) {
      return arg1;
     }
    
     @Override
     public String getName() {
      return "XXX Custom Workflow Actions";
     }
    
     @Override
     public String getTitle(Locale arg0) {
      return "XXX Custom Workflow Actions";
     }
    }
    
    
  7. Create a plugin.xml file if you are deploying using WAR or EAR. Include the plugin.xml file in the application’s “WEB-INF” folder.
    <?xml version="1.0" encoding="UTF-8"?>
    <plugin id="XXXCustomWorkflowActionsPlugin" name="XXX Custom Workflow Actions Plugin" version="1.0.0" provider-name="IBM">
    <extension point="com.ibm.workplace.wcm.api.CustomWorkflowActionFactory" id="XXXCustomWorkflowActionFactory">
    <provider class="xxx.xxx.xxx.cwf.XXXCustomWorkflowActionFactory"/>
    </extension>
    </plugin>
    
  8. Follow the Deploying custom plug-in applications to deploy your plugin.
  9. Go to Web Content Authoring portlet and create Custom Workflow Action, you should be able to select your custom workflow action as shown below.

 

WebSphere Portal 8.5: Customize Ephox Editor

We have been encouraging our clients to use Ephox Editor as their default WCM rich text editor ever since IBM has acquired Ephox Editor OEM licence. Hence it is important for us to know how to:

  • Customize Ephox Editor default menu and toolbar items (for example change the default font size from “pt” to “px”)
  • Apply our custom theme css classes styles in Ephox Editor

Most of the customization will be taking place at the config file, it is located at  <wp_profile>/installedApps/<cell>/wcm.ear/editor-editlive-config.war/config/config.xml.jsp.

 

Steps to customize Ephox Editor default editor settings:

  1. Read Setting Menu and Toolbar Items tutorial guide first.
  2. Edit the config file accordingly.
  3. Restart the server.

Steps to include custom theme css in Ephox Editor:

  1. Read Using CSS in the Applet tutorial guide first.
  2. Edit the config file accordingly.
  3. Restart server.

 

Find Out Your Jar File Location

Below is the code to locate your class jar file location:

CodeSource src = MyClass.class.getProtectionDomain().getCodeSource();

Query Service in WebSphere Portal WCM API

QueryService is a good addition to WCM API. Other than helping to keep the API neat, it also provides a way for developers to do “OR” condition (Disjunction class).

Below is a simple example on how to do a “Like” search on content’s title
(Do take note that Selectors.titleLike method is case-sensitive (as of 8.5) <- hope they will add in extra argument in future to allow us to decide if the title search should be case-sensitive)


Repository repository = WCM_API.getRepository();
Workspace ws = repository.getWorkspace();
ws.login();

QueryService queryService = ws.getQueryService();
Query query = queryService.createQuery(Content.class);
query.addSelector(Selectors.titleLike("%a%")); //searching for contents' title containing "a"

query.addSort(Sorts.byPublishDate(SortDirection.DESCENDING)); // sort by publish date
ResultIterator resultIter = queryService.execute(query);
while (resultIter.hasNext()) {
 Content content = (Content) resultIter.next();
 // process...
}

 

But we noticed that there are performance deficits when we try to traverse > 400 contents (~11 sec to complete). The deficit occurs when we tried to cast the object into its respective object types (for example (Content) resultIter.next()). We believe that the API is trying to clone the content’s internal document (including its respective elements).

Hence if you just need to reference the contents, perhaps you can try to retrieve content using the traditional Workspace.getById(DocumentId<T> id, boolean asReference, boolean loadElements) method (as this effectively reduce our time from 11 sec to just 1 sec on our side). Do let us know if it works for you, cheers!


Repository repository = WCM_API.getRepository();
Workspace ws = repository.getWorkspace();
ws.login();

QueryService queryService = ws.getQueryService();
Query query = queryService.createQuery(Content.class);
query.returnIds(); // get the query services to return DocumentId instead of Document objects
query.addSelector(Selectors.titleLike("%a%")); //searching for contents' title containing "a"

query.addSort(Sorts.byPublishDate(SortDirection.DESCENDING)); // sort by publish date
ResultIterator resultIter = queryService.execute(query);
while (resultIter.hasNext()) {
 Content content = (Content) ws.getById(((DocumentId) resultIter.next()),true,false); // only retrieve a reference Content without loading its elements
 // process...
}

 

Disjunction (Our favourite “OR” condition)

Below is a code snippet on how to retrieve contents based on “OR” categories condition (Disjunction).


Disjunction orCondForCats = new Disjunction();
// tranverse contents if they contains one of the selected categories
for (int i = 0, len = selectedList.size(); i < len; i++) {
 orCondForCats.add(ProfileSelectors.categoriesContains(selectedList.get(i)));
}
query.addSelector(orCondForCats);

WebSphere Portal: Using WCM_API for Selected Virtual Portal

I was writing a live-feed migration portlet (AJAX) for my client when I realized that calling Workspace in Servlet is actually pointing to the base portal (which somehow makes sense). Hence I checked WCM API for updates and that was where I found Repository.generateVPContextFromHostname and Repository.executeInVP methods. The methods look promising but I have no idea how to call it until I found Philip Cheshire’s blog. Credits goes to him!

Do note that objects taken out from executeInVP method will not be able to access their repository and all actions will be performed as if the item belonged to the base portal workspace. There is one instance where I wrote a VirtualPortalScopedAction object to return only the Workspace and that is how I realized that the Workspace is “referencing” back to the default base repository.

Below illustrate a simple example on how to retrieve Authoring Template’s DocumentId from a specific Virtual Portal

In Our Servlet:


@WebServlet("/ProcessCircular")
public class ProcessCircular extends HttpServlet {
 public ProcessCircular() {
  super();
  try {
   Repository repository = WCM_API.getRepository();
   VirtualPortalContext vpc = repository.generateVPContextFromHostname(MigrationScriptPortlet.VIRUTAL_PORTAL_HOSTNAME);

   // retrieve AT document id
   VPScopedActionAT vpsAT = new VPScopedActionAT();
   repository.executeInVP(vpc, vpsAT);
   DocumentId atId = vpsAT.docId; // this is the part where we take object out from executeInVP
  } catch (WCMException e) {
   // ...
  }
 }
}

 

Then we proceed to create VPScopedActionAT that implements VirtualPortalScopedAction:

public class VPScopedActionAT implements VirtualPortalScopedAction {
 public DocumentId docId;

 @Override
 public void run() throws WCMException {
  Repository repository = WCM_API.getRepository();
  Workspace ws = repository.getSystemWorkspace();
  ws.setCurrentDocumentLibrary(ws.getDocumentLibrary(MigrationScriptPortlet.CONTENT_LIB));
  DocumentLibrary docLib = ws.getDocumentLibrary("Corporate");

  DocumentIdIterator atIter = ws.findByName(DocumentTypes.AuthoringTemplate, MigrationScriptPortlet.CIRCULAR_AT);
  if (atIter.hasNext()) {
   docId = atIter.next();
  }
  repository.endWorkspace();
 }
}

Where is WebSphere Portal Jars?

Below is the jars file location for WebSphere Portal 8.5 SPI.

ilwwcm-api.jar 
Reside at <PortalServer>/wcm/prereq.wcm/wcm/shared/app/ilwwcm-api.jar
It is responsible for the following classes:

  • com.ibm.workplace.wcm.api.*

wp.model.api.jar 
Reside at <PortalServer>/base/wp.model.api/shared/app/wp.model.api.jar
It is responsible for the following classes:

  • com.ibm.portal.state.accessors.pagemode.*

wp.resolver.friendly.api.jar
Reside at <PortalServer>/base/wp.resolver/wp.resolver.friendly.api/shared/app/wp.resolver.friendly.api.jar
It is responsible for the following classes:

  • com.ibm.portal.resolver.friendly.*

wp.services.api.jar
Reside at <PortalServer>/base/wp.services.api/shared/app/wp.services.api.jar
It is responsible for the following classes:

  • com.ibm.portal.services.project.*

Just drop us a comment if you need to find out the jar location of any WebSphere Portal classes.

WebSphere Portal EJPPD0015E: Portlet application manager failed

Scenario

You have created your portlet using RAD and the portlet has been deployed to WebSphere Portal. For some reasons, you need to remove the portlet (not update) and re-deployed the portlet again. Immediately you encountered “EJPPD0015E: Portlet application manager failed” exception in your SystemOut.log file.

Then you begin to trace and discover that the exception occurs at EMF2DOMAdapterImpl_ERROR_0:

Caused by: java.lang.IllegalStateException: EMF2DOMAdapterImpl_ERROR_0
at org.eclipse.wst.common.internal.emf.resource.EMF2DOMAdapterImpl.handleInvalidMultiNodes(EMF2DOMAdapterImpl.java:1402)
at org.eclipse.wst.common.internal.emf.resource.EMF2DOMAdapterImpl.findDOMNode(EMF2DOMAdapterImpl.java:1389)
at org.eclipse.wst.common.internal.emf.resource.EMF2DOMAdapterImpl.primUpdateMOFFeature(EMF2DOMAdapterImpl.java:1572)
at org.eclipse.wst.common.internal.emf.resource.EMF2DOMAdapterImpl.updateMOFFeature(EMF2DOMAdapterImpl.java:1992)
at org.eclipse.wst.common.internal.emf.resource.EMF2DOMAdapterImpl.primUpdateMOF(EMF2DOMAdapterImpl.java:1010)
at org.eclipse.wst.common.internal.emf.resource.EMF2DOMAdapterImpl.updateMOF(EMF2DOMAdapterImpl.java:986)
at org.eclipse.wst.common.internal.emf.resource.EMF2DOMAdapterImpl.primUpdateMOFMultiFeature(EMF2DOMAdapterImpl.java:489)
at org.eclipse.wst.common.internal.emf.resource.EMF2DOMAdapterImpl.updateMOFRootFeature(EMF2DOMAdapterImpl.java:1039)
at org.eclipse.wst.common.internal.emf.resource.EMF2DOMAdapterImpl.primUpdateMOF(EMF2DOMAdapterImpl.java:1006)
at org.eclipse.wst.common.internal.emf.resource.EMF2DOMAdapterImpl.updateMOF(EMF2DOMAdapterImpl.java:986)
at org.eclipse.wst.common.internal.emf.resource.EMF2DOMRenderer.doLoad(EMF2DOMRenderer.java:331)
at org.eclipse.wst.common.internal.emf.resource.TranslatorResourceImpl.basicDoLoad(TranslatorResourceImpl.java:633)
... 78 more

If that is the reason, then you might just be in luck. Go to your WEB-INF/web.xml and ensure that the web.xml file does not contains multiple nodes of the following:

  • session-config
  • welcome-file-list
  • jsp-config
  • login-config
  • locale-encoding-mapping-list