Write for OTN
Earn money and promote your technical skills by writing a technical article for Oracle Technology Network.
Learn more
Stay Connected
OTN Architect Community
OTN ArchBeat Blog Facebook Twitter YouTube Podcast Icon

Building an Oracle WebCenter Default Search Adapter

By Daniel Merchán Garcia

Step-by-step instructions for building a Twitter search adapter for Oracle WebCenter Portal.

November 2013

Downloads
download-icon13-1Oracle WebCenter
download-icon13-1DefaultSearchAdapter.rar (sample code)

Overview

This guide complements the official documentation of Oracle WebCenter Portal by adding a new source of data for WebCenter Search Service. It offers step-by-step instructions for building a new search adapter, using an actual sample developed using the framework (Figure 1).

merchan-webcenter-search-fig01
Figure 1

The sample contains a default search adapter with the following functionality:

  • Adds new service oracle.webcenter.twitter for tweet resources, enabling search capabilities
  • Uses twitter4j API to search tweets
  • Shows how pagination works
  • Filters by default WebCenter search filters:
    • Dates
    • Creator
  • Shows the detail of an item in two ways (configurable):
    • Using a resource-viewer task flow with a detail fragment page
    • Using a URI linking directly to Twitter

Building a New Search Adapter

Constructing a new search adapter involves three main steps:

  1. Registering a new service in WebCenter Portal enabling search capabilities
  2. Implementing and extending some classes of oracle.webcenter.search
  3. (Optional) Creating a bounded task flow as resource-viewer for a custom detail page for the item.

Register a New Service Enabling Search Capabilities

Register the new service in the service-definition.xml configuration file (Figure 2).

merchan-webcenter-search-fig02
Figure 2
<service-definition id="oracle.webcenter.twitter" version="11.1.1.0.0">
   <resource-view taskFlowId="/WEB-INF/flows/search/twitter/twitter-viewer.xml#twitter-viewer"/>
   <!-- <resource-bundle-class>com.merchan.sample.portal.search.adapter.twitter.resource.
     TwitterMessageBundle</resource-bundle-class> -->
   <name>Twitter</name>
   <!-- <name-key>TWITTER_NAME_KEY</name-key> -->
   <description>Adapter to show twitter entries in the results</description>
   <!-- <description-key>TWITTER_DESCRIPTION_KEY</description-key> -->
   <icon>/oracle/webcenter/portalapp/shared/twitter_logo.png</icon>
   <search-definition xmlns="http://xmlns.oracle.com/webcenter/search"
     id="com.merchan.sample.portal.search.adapter.twitter" version="11.1.1.0.0">
   <query-manager-class>com.merchan.sample.portal.search.adapter.twitter.
     TwitterQueryManager</query-manager-class>
   </search-definition>
</service-definition>

Configuration of a service-definition:

  • <service-definition>: Start tag includes the service's ID and version
    • id: service ID (e.g., oracle.webcenter.doclib, oracle.webcenter.page, etc.)
    • version: to maintain versioning for the service
  • <resource-view>: Registers a task flow that displays the detail of a resource for the defined service
  • (Optional) <resource-bundle-class>: If we are building a multi-language service, register a Java Bundle Class to provide the translation for things like <name>, <description> and information relative to the new service
  • <name> or <name-key>: The name of the service; when contributing a resource-bundle-class, use name-key instead of name
  • <description> or <description-key>: Summary of the service
  • <icon>: Representative icon of the service
  • <search-definition>: Responsible for adding our new service to the search framework, this tag includes the following configuration:
    • <query-manager-class>: Registers the QueryManager class that will be the entry point of the custom search adapter

Implementation

The Search Framework requires implementing three interfaces (Figure 3):

  • oracle.webcenter.search.QueryManager: Initial class responsible for initiating search parameters. Prepares the initialization of the parameters before execution of the search service.
  • oracle.webcenter.search.QueryExecutor: Main class that integrates with the source to search the results. Generates result rows, and manages filters (author and dates) and pagination.
  • oracle.webcenter.search.Row: Wraps the information for an item row in the result list.

And also extends the following class:

  • oracle.webcenter.search.util.WrapperQueryResult: Wraps the result rows in a format expected by the Search Framework.
merchan-webcenter-search-fig03
Figure 3

The following steps describe how Twitter classes were created by extending and implementing the Framework classes described earlier:

Step 1: TwitterQueryManager implementing oracle.webcenter.search.QueryManager

This class is the adapter's entry point; it can be used for the configuration to initialise the adapter.

The following method must be implemented:

Method Description
createRowQueryHandler Initialize adapter values. Responsible for creation of the executor.

 

public QueryExecutor<Row> createRowQueryHandler(Query query,
                                                    List<QName>
columns) {
        final String METHOD_NAME = "createRowQueryHandler";
        if (_logger.isFine()) {
            _logger.fine(CLASS_NAME,METHOD_NAME,"Query: " + query.toString());
        }
        return new TwitterRowQueryExecutor(query, columns);
}

Important: Every time a user clicks or removes the filters, a new search is created and this method is executed at the start.

Step 2: Create a class implementing oracle.webcenter.search.QueryExecutor

This class is responsible for integrating with the source API and building/filtering the rows of the result list.

TwitterRowQueryExecutor is created by TwitterQueryManager and is dependent on two classes: TwitterRow and TwitterQueryResult.

Attribute Description
m_resultStart Keeps the current index for pagination.
m_resultLimit Keeps the number of items per page. At the first search (no filters) has MAX_INT value.
m_query Has the current oracle.webcenter.search.Query object. Includes Keywords and Predicates storing filters applied, like Author or Dates.
m_columns List of QNames specifying which columns to return by the service.
m_properties Stores the configuration of the QueryHandler, like Timeout or Estimated Rows to Show.


Here is more information about the class attributes:

m_resultStart and m_resultLimit values are automatically filled by WebCenter Framework when the pagination link is pressed.


Example:
Using 5 items per page in Search Task Flows:

Operation m_resultStart m_resultLimit
First search 0 MAX_INT
Click page 1 0 5
Click page 2 5 5
Click page 4 15 5


m_query
and m_columns are values passed from TwitterQueryManager.

It is mandatory to implement the following interface methods:

Method Description
Execute The main method responsible for calling the source API and building/filtering result rows. Returns an object of type oracle.webcenter.search.QueryResult<Row>.
setResultStart Setter of current result start flag.
setResultLimit Setter of current result limit flag.


The execute() method returns the collection to be shown in the result list.

public QueryResult<Row> execute() {
        // List of resutl rows
        List<Row> rows = new ArrayList<Row>();
        try {
            this.searchTwitterRows(rows);
        } catch (TwitterException e) {
            _logger.warning("Twitter Adapter not working: " + e.getMessage(),e);
        }
        // Create the QueryResult based on the rows to show
        QueryResult<Row> ret = new 
TwitterRowQueryResult(rows.iterator());
        // Estimated Row Count comes from the full list searched in our
        // service and stored in PageFlow
        String estimatedResultCount =
PageFlowScopeUtils.get(TwitterAdapterConstants.PF_ROWS_ESTIMATED_COUNT);
        ret.getProperties().put(QueryResult.ESTIMATED_RESULT_COUNT, estimatedResultCount);
        return ret;
    }

Important: Fill the m_properties field when calling QueryResult.ESTIMATED_RESULT_COUNT to show the pagination according to the number of rows returned. This doesn't match with the size of the returned rows list because the returned rows list can contain only the current page rows.

To perform the search, we must first know how the m_query of type oracle.webcenter.search.Query object is built.

Examples:

A default query containing only keywords looks like:
Query - Scope: null Predicates: ComplexPredicate(TextPredicate(webcenter))

A query containing author filter:
Query - Scope: null Predicates:
ComplexPredicate(TextPredicate(webcenter),ComplexPredicate(AttributePredicate(http://purl.org/dc/elements/1.1/creator equals Daniel Merchan)))

A query containing author and dates:
Query - Scope: null Predicates:
ComplexPredicate(TextPredicate(webcenter),ComplexPredicate(AttributePredicate(http://purl.org/dc/terms/1.1/modified greater.than.or.equals 2013-06-19T00:00:00.00 +0100),AttributePredicate(http://purl.org/dc/terms/1.1/modified less.than 2013-06-20T00:00:00.00 +0100)),AttributePredicate(http://purl.org/dc/elements/1.1/creator equals Daniel Merchan))

Algorithms to parse and retrieve the information from the Query object must be implemented and passed to the source API to get the desired results.

In TwitterRowQueryExecutor there are auxiliary methods that retrieve the information from the oracle.webcenter.search.Query object to call and use twitter4j to return desired content.

Example:

Retrieving the Author given the associated Query Predicate.

private String getAuthor(Predicate predicate) {
        String author = null;
        if (predicate instanceof NamedPredicate) {
            QName predQName = ((NamedPredicate)predicate).getName();
            if (predQName.equals(AttributeConstants.DCMI_CREATOR)) {
                author = (String)((AttributePredicate)predicate).getValue();
            }
        } else if (predicate instanceof ComplexPredicate){
            List<Predicate> predicateList =
                ((ComplexPredicate)predicate).getChildren();
            for (Predicate pred : predicateList) {
                author = this.getAuthor(pred);
            }
        }
        return author;
    }

Step 3: Create a class implementing oracle.webcenter.search.Row

This class represents a Row in the result list. It has a specific format to populate the information (Figure 4).

merchan-webcenter-search-fig04
Figure 4

Attributes of the class:

Variable Value
m_storage Auxiliary map used to create a Row response storing all the necessary QName columns with the associated values to print the Row in the result page.


Methods implemented by TwitterRow for oracle.webcenter.search.Row interface:

Method Description
getObject Get a value from a given QName
getColumns Get QName's Iterator for the result page rendering


The m_storage attribute is a map to fill the QName columns and to easily retrieve the Iterator required by both getColumns and the Framework, simply by calling the keySet().iterator() method.

The most important QName columns to fill when creating a Row are:

QName Description
oracle.webcenter.search.AttributeConstants.DCMI_TITLE Title that should appear in the Row.
oracle.webcenter.search.AttributeConstants.DCMI_IDENTIFIER Unique identifier of the Row; used to pass the resourceId parameter to a resource-viewer when using one to print the detail of a Row when title is clicked.
oracle.webcenter.search.AttributeConstants.DCMI_MODIFIED Last modification date. If this is not provided, filters for Last Modified will not appear in the result page
oracle.webcenter.search.AttributeConstants.DCMI_CREATOR Creator of the Row. Used by the filters. If not provided, Creator filters will not appear.
oracle.webcenter.search.AttributeConstants.WPSAPI_ICON_PATH Icon to show in the Row of the result list.
oracle.webcenter.search.AttributeConstants.DCMI_URI Type of the Row. This value is propagated as an input parameter calling resourceType to the resource-viewer Task Flow.
oracle.webcenter.search.AttributeConstants.DCMI_TYPE Type of the Row. This value is propagated as an input parameter calling resourceType to the resource-viewer Task Flow.


In addition, there are more QName columns that can be populated to provide further functionality or information.

QName Description
oracle.webcenter.search.AttributeConstants.WPSAPI_TAG_WORDS String separated by whitespaces containing the tag words. When tags and clicked, Tag Services for WebCenter Services are executed.
oracle.webcenter.search.AttributeConstants.DCMI_DESCRIPTION Adds more information relative to the Row.
oracle.webcenter.search.AttributeConstants.WPSAPI_MODIFIER User that last modified the item. Cannot be the Creator.
oracle.webcenter.search.AttributeConstants.WPSAPI_ICON_PATH Icon to show in the Row of the result list.


More attributes are available in the oracle.webcenter.search.AttributeConstants class in the WebCenter Portal API.

Example:

Figure 5 illustrates how some of the QName column values correspond to the result page of Search Task Flow.

merchan-webcenter-search-fig05
Figure 5

Step 4: Create a class extending oracle.webcenter.search.WrapperQueryResult

Represent the collection of oracle.webcenter.search.Row to be displayed in the result page.

TwitterQueryResult must store only an Iterator<Row> generated in TwitterQueryExecutor.

public final class TwitterRowQueryResult extends WrapperQueryResult<Row> {
    public TwitterRowQueryResult(Iterator<Row> rows) {
        super(rows);
    }
}

Detail of the Item

There are two ways to show the detail of an item in the result list:

  • Open an External URI with the detail link.
  • Open the detail in a Custom Bounded Task Flow registered as resource-viewer.

External URI

Open an item's detail using an external page by filling in the oracle.webcenter.search.AttributeConstants.DCMI_URI of the Row.

Clicking the result title will open the external URI provided (Figure 6).

merchan-webcenter-search-fig06
Figure 6

In Resource Viewer Task Flow

merchan-webcenter-search-fig07
Figure 7

To show the detail using the resource viewer Task Flow, configure as described below:

  • Do not provide oracle.webcenter.search.AttributeConstants.DCIM_URI QName column value.
  • Register a resource viewer in the service-definition.xml configuration file for the service pointing to the Task Flow (Figure 8).
merchan-webcenter-search-fig08
Figure 8

A resource viewer Task Flow should have the configuration described below (Figure 9):

merchan-webcenter-search-fig09
Figure 9
  • Necessary Input Parameters:
    • resourceId: Retrieves the ID of the resource to be displayed, for when Twitter Adapter is the Tweet ID.
    • resourceType: Retrieves the type of the content. In addition, could be useful to pass more information that will help retrieve the details of the resource.
  • Optional input parameters:
    • resourceTitle: Title associated with the resource.
    • resourceScope: Indicates if the resource is associated with a specific space or the general one.

Where do these input parameters come from?

WebCenter's default ResourceHandler is responsible for passing these parameters when the detail of an item in the service is clicked.

For example, for a document service, the document resource viewer receives the dDocName through the content's resourceId when the Twitter Viewer receives the tweet Id.

Once the resource viewer Task Flow is registered and the correct permissions assigned to roles in jazn-data.xml, the Task Flow will open when a user clicks an item.

Configure Search Task Flow

After configuring the new service with search capabilities, the Search Task Flow should be configured by adding the new service ID (Figure 10).

merchan-webcenter-search-fig10
Figure 10

Common Issues and Tips

No Oracle WebCenter Framework Information in adf-config File

When items are clicked, the following stack trace appears in the log file:

<ResourceActionUtils> <getHandlerClass> No Oracle WebCenter Framework information in adf-config file.

To fix this situation, register a handler only in the adf-config.xml. You can select:

  • PopUpResourceActionViewHandler to show the resource in a popup window when a search result is clicked.
  • NavigationResourceActionHandler to show the resource in the navigation node of the resource when a search result is clicked.

Sample of the config in adf-config.xml:

<wpsC:adf-service-config xmlns="http://xmlns.oracle.com/webcenter/framework/service">
     <resource-handler class="oracle.webcenter.portalframework.sitestructure.handler.
NavigationResourceActionHandler"/>
</wpsC:adf-service-config>

Create Reusable Adapters in ADF JAR Libraries

When creating new Default Live Adapters in real projects, best practice is to create reusable ADF JAR Libraries instead of working directly inside the Portal Framework Application. They can then be deployed as a shared lib across Portal and Spaces.

Required files like service-definitions.xml must be created manually in the project.

Run the Sample

The sample (DefaultSearchAdapter.rar) was implemented using Oracle JDeveloper 11.1.1.6 (PS5 version of Oracle WebCenter Portal) and using twitter4j as the Twitter Search API.

The file twitter4j.properties must be configured with Twitter OAuth keys (Figure 11).

merchan-webcenter-search-fig11
Figure 11

Find the keys here (Figure 12): https://dev.twitter.com/docs/auth/obtaining-access-tokens

merchan-webcenter-search-fig12
Figure 12

References

  1. Official documentation about Integrating the Search Service
  2. Configuring Search with WebCenter Portal's Search Adapters
  3. Building Adapters for the Search Service
  4. Extending your Framework Application with Custom Components

About the Author

Daniel Merchán Garcia is an Oracle WebCenter Portal/Content Solutions Architect with Vass, based in London, UK.
Twitter Blog LinkedIn