Skip to content

Instantly share code, notes, and snippets.

@roanbester
Last active March 8, 2024 20:47
Show Gist options
  • Save roanbester/00c329ae5eb916fda723 to your computer and use it in GitHub Desktop.
Save roanbester/00c329ae5eb916fda723 to your computer and use it in GitHub Desktop.
Demo using WCM 8.0 Query API to search for and filter ContentItems with text, image and file.
package sample.wcm.query.portlet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.ResourceRequest;
import javax.portlet.ResourceResponse;
import org.apache.commons.lang.StringUtils;
import com.ibm.workplace.wcm.api.Category;
import com.ibm.workplace.wcm.api.Content;
import com.ibm.workplace.wcm.api.ContentComponent;
import com.ibm.workplace.wcm.api.DocumentId;
import com.ibm.workplace.wcm.api.DocumentLibrary;
import com.ibm.workplace.wcm.api.FileComponent;
import com.ibm.workplace.wcm.api.ImageComponent;
import com.ibm.workplace.wcm.api.Repository;
import com.ibm.workplace.wcm.api.TextComponent;
import com.ibm.workplace.wcm.api.WCM_API;
import com.ibm.workplace.wcm.api.Workspace;
import com.ibm.workplace.wcm.api.exceptions.ComponentNotFoundException;
import com.ibm.workplace.wcm.api.exceptions.OperationFailedException;
import com.ibm.workplace.wcm.api.exceptions.QueryServiceException;
import com.ibm.workplace.wcm.api.exceptions.ServiceNotAvailableException;
import com.ibm.workplace.wcm.api.query.Disjunction;
import com.ibm.workplace.wcm.api.query.ProfileSelectors;
import com.ibm.workplace.wcm.api.query.ResultIterator;
import com.ibm.workplace.wcm.api.query.Selectors;
/**
* A portlet that demos accessing content items with the Query API and doing filtering on Elements.
* @author RBester
*
*/
public class WcmQueryExamplePortlet extends GenericPortlet {
private static final String DOCUMENT_LIBRARY = "java-api-test-library";
private static final String NATIONAL_ID_ELEMENT_NAME = "nationalID";
@Override
public void serveResource(ResourceRequest request, ResourceResponse response) throws PortletException, IOException {
super.serveResource(request, response);
// comma-delimited list of content item names, category names, keywords and a custom element.
String contentNames = request.getParameter("contentNames");
String categoryNames = request.getParameter("categoryNames");
String keywords = request.getParameter("keywords");
String nationalIds = request.getParameter("nationalIds");
//Retrieve the workspace
Workspace workspace = null;
Repository repository = WCM_API.getRepository();
try {
// Use the logged-in user credentials to log in to the workspace
workspace = repository.getWorkspace(request.getUserPrincipal());
workspace.login();
// use a specific WCM library that is available on the Virtual portal
DocumentLibrary library = workspace.getDocumentLibrary(DOCUMENT_LIBRARY);
com.ibm.workplace.wcm.api.query.Query query = workspace.getQueryService().createQuery();
/*
* Select all content items for a specific library, with either categories, keywords or names as requested.
* Use a Disjunction (OR) compound selector to add the relevant Selectors to (categories OR keywords OR names)
*/
Disjunction or = addOrClauses(workspace, library, categoryNames, keywords, contentNames);
// limit to the library and Content, with the specified OR clause.
query.addSelector(Selectors.libraryEquals(library));
query.addSelector(Selectors.typeIn(Content.class));
query.addSelector(or);
// We're interested in the objects themselves (Document) and not the IDs (DocumentId)
query.returnObjects();
/*
* Filter the results on the provided custom element (TextElement) value.
* We need to filter, as there isn't a facility in the Query API to search on Document Elements.
*/
ResultIterator resultIterator = workspace.getQueryService().execute(query);
List<Content> content = packageContentInList(resultIterator);
// apply filter
content = filterByNationalId(content, nationalIds);
// print the results as HTML by traversing the Content Elements (previously known as ContentComponent and still that name in the API)
response.getWriter().write(generateDocumentList(content));
} catch (ServiceNotAvailableException e) {
e.printStackTrace();
} catch (OperationFailedException e) {
e.printStackTrace();
} catch (QueryServiceException e) {
e.printStackTrace();
} finally {
if (workspace != null) {
workspace.logout();
}
if (repository != null) {
repository.endWorkspace();
}
}
}
private Disjunction addOrClauses(Workspace workspace, DocumentLibrary library, String categoryNames, String keywords, String contentNames) {
Disjunction or = new Disjunction();
addCategoriesSelectors(workspace, library, or, categoryNames);
addKeywordSelectors(or, keywords);
addNamesSelectors(or, contentNames);
return or;
}
private void addCategoriesSelectors(Workspace workspace, DocumentLibrary library, Disjunction or, String categoryNames) {
if (StringUtils.isEmpty(categoryNames)) {
return;
}
try {
List<DocumentId> categories = findCategories(workspace, library, categoryNames);
or.add(ProfileSelectors.categoriesContains(categories));
} catch (QueryServiceException e) {
e.printStackTrace();
}
}
private void addKeywordSelectors(Disjunction or, String keywords) {
if (StringUtils.isEmpty(keywords)) {
return;
}
or.add(ProfileSelectors.keywordsContain(splitKeywords(keywords)));
}
private void addNamesSelectors(Disjunction or, String itemNames) {
if (StringUtils.isEmpty(itemNames)) {
return;
}
or.add(Selectors.nameIn(splitKeywords(itemNames)));
}
/*
* Loops through the content items and interrogates the ContentComponent (Element). If it's a TextElement (which it should be),
* the text should match our search criteria.
*/
private List<Content> filterByNationalId(List<Content> contentItems, String nationalIds) {
if (StringUtils.isEmpty(nationalIds)) {
return contentItems;
}
List<Content> toReturn = new ArrayList<Content>();
for (Content contentItem: contentItems) {
try {
ContentComponent contentComponent = contentItem.getComponent(NATIONAL_ID_ELEMENT_NAME);
if (containsContentText(nationalIds, contentComponent)) {
toReturn.add(contentItem);
}
} catch (ComponentNotFoundException e) {
System.out.println("component not found: " + e.getMessage());
}
}
return toReturn;
}
/*
* Match TextComponent's text with a comma-delimited list of search terms
*/
private boolean containsContentText(String textToScan, ContentComponent contentComponent) {
if (contentComponent instanceof com.ibm.workplace.wcm.api.TextComponent) {
String componentText = ((com.ibm.workplace.wcm.api.TextComponent) contentComponent).getText();
return textToScan.contains(componentText);
}
return false;
}
/*
* Casts and adds results to a List for convenience.
*/
private List<Content> packageContentInList(ResultIterator resultIterator) {
List<Content> toReturn = new ArrayList<Content>();
Object nextItem;
while(resultIterator.hasNext()) {
nextItem = resultIterator.next();
if (nextItem instanceof Content) {
toReturn.add((Content)resultIterator.next());
}
}
return toReturn;
}
/*
* Generate HTML to be printed
*/
private String generateDocumentList(List<Content> content) {
StringBuilder documentHtml = new StringBuilder();
for (Content contentItem : content) {
documentHtml.append(getContentDescription(contentItem));
}
return documentHtml.toString();
}
/*
* Tokenize the keywords/search terms
*/
private List<String> splitKeywords(String keywords) {
String [] splitKeywords = keywords.split(",");
return Arrays.asList(splitKeywords);
}
/*
* To search by categories, we need to first fetch all the categories' IDs, then use those in the query.
* Typically one could cache the categories so this lookup isn't needed all the time.
*/
private List<DocumentId> findCategories(Workspace workspace, DocumentLibrary library, String categories) throws QueryServiceException {
String[] categoriesSplit = categories.split(",");
com.ibm.workplace.wcm.api.query.Query query = workspace.getQueryService().createQuery();
query.addSelector(Selectors.typeIn(new Class[] {Category.class}));
query.addSelector(Selectors.libraryEquals(library));
query.addSelector(Selectors.nameIn(categoriesSplit));
query.returnIds();
ResultIterator resultIterator = workspace.getQueryService().execute(query);
List<DocumentId> toReturn = new ArrayList<DocumentId>();
while (resultIterator.hasNext()) {
toReturn.add((DocumentId) resultIterator.next());
}
return toReturn;
}
/*
* Returns html for ContentItem with text, an ImageComponent/Element and a FileComponent (e.g. pdf).
*/
private String getContentDescription(Content content){
String result = "";
try {
String[] componentNames = content.getComponentNames();
for(int i = 0; componentNames != null && i < componentNames.length; i++){
ContentComponent contentComponent = content.getComponent(componentNames[i]);
if(contentComponent instanceof TextComponent){
result += componentNames[i] + ": " + ((TextComponent)contentComponent).getText() + "<br>";
} else if(contentComponent instanceof ImageComponent){
result += componentNames[i] + ": <a href='" + ((ImageComponent)contentComponent).getResourceURL() + "'>Image</a><br>";
} else if(contentComponent instanceof FileComponent){
result += componentNames[i] + ": <a href='" + ((FileComponent)contentComponent).getResourceURL() + "'>File</a><br>";
}
}
result += "<br>";
} catch(ComponentNotFoundException e){
e.printStackTrace();
}
return result;
}
@Override
protected void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException {
PortletRequestDispatcher dispatcher = getPortletConfig().getPortletContext().getRequestDispatcher("/WEB-INF/jsp/WcmQueryUtilitiesPortlet.jsp");
dispatcher.forward(request, response);
}
}
<%@ page contentType="text/html" pageEncoding="UTF-8" language="java"%>
<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet_2_0"%>
<portlet:defineObjects/>
<form method="post" action='<portlet:resourceURL />'>
Content name(s): <input type="text" name="contentNames">
Category(ies): <input type="text" name="categoryNames">
Keyword(s): <input type="text" name="keywords">
National IDs(s): <input type="text" name="nationalIds">
<input type="submit" value="submit">
</form>
@roanbester
Copy link
Author

Using WCM 8.0 Query API

Project context

For our project we needed to make use of WCM as a 'lightweight' document store, or Library. For many reasons we couldn't at this stage use e.g. Filenet or some other document store. This meant we had to create WCM ContentItems (including Authoring Template, taxonomy, Workflow etc) to handle the requirements.
The result should be that you can create and maintain/workflow your documents (as ContentItems with file elements and metadata) in WCM using the WCM user interface, but search for and surface these items in any application with access to the Portal.

Ways to access the content

IBM Web Content Manager 8.0 has different means of accessing WCM Content.

  • Via the WCM REST API (out-of-the-box)
  • Using the Java API
  • Using a WCM Content Viewer Portlet with Presentation template.

We started off using the out-of-the-box WCM REST API. The problem was that (at least for Portal 8.0/WCM 8.0) the REST service was complex, chatty and very slow.
Next up, we tried out the 'basic' Java API using the Workspace. Unfortunately the more simplistic API couldn't do the combination search we wanted.

Query API

WCM comes with a more advanced API called the Query API. You can have a look at the javadocs here:
http://public.dhe.ibm.com/software/dw/lotus/portal_javadoc/80/wcm/api-javadoc/index.html

What this demo code does

The demo/POC code in this listing demonstrates a few concepts.

  • Use a WCM Workspace for a specific logged in user;
  • Limit search results to a given WCM Library;
  • Search for Content (Items) by category(ies), keyword(s) or name(s);
  • Filter the results on a custom Element (a text element) value (the Query API doesn't seem to support searching on Element in Documents directly).

As the WCM API is available only on Portal, and we wanted to have a visual representation of the results, including a simplistic search form, we decided to use a normal JSR286 Portlet.

Getting the project to work on your side

You will need to create a WAR with a JSR286 Portlet, with the appropriate Portal WCM jar:
ilwwcm-api.jar

which should come with your Portal installation and be available in RAD or eclipse with Portal 8 Development stubs.

You also need to have access to Portal 8.0 and the WCM 8.0 Application so you can create ContentItems.

Note on ContentItem in the sample and creating sample WCM content

The example code uses standard ContentItem fields (name, keywords) and categories (added to the ContentItem profile), FileElement and TextElement.
It is out of the scope of this sample to show you how to set this up in WCM, but I'm sure if you read/followed this code you will know where to go from here :)

Additional references

WCM Query API
http://www-10.lotus.com/ldd/portalwiki.nsf/dx/WCM_Query_API

Using the more base Workspace find facilities (code recipes)
http://saranshibmblog.blogspot.co.za/2012/12/wcm-api-document.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment