Last active
March 8, 2024 20:47
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%@ 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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
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.
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