Search Portlet

By means of the search portlet, search forms and results lists can be integrated into a website.

Both the function and the layout of the search can be adapted to meet individual needs without having to modify Java code. The portlet supports sending requests to the Content Management Server as well as to the Search Engine Server. It uses the search engine configured in the searchEngine entry of the pm.xml configuration file.

Each search is represented by an individual directory below the instance-specific web application directory WEB-INF/templates/search.

Usage

The search portlet can be included in layout files in the following way:

<npspm includeportlet="/PM-PL/search" instance="dealer" language="de" />

The instance name, dealer, corresponds to the name of the directory containing the search definition. language optionally lets you define the language to be used by the portlet. If it is not specified, the language set in the browser is used.

Configuration

Definition of a Search

Any number of searches can be defined. Each of them is defined by means of two Velocity templates located in the web application directory mentioned above. The view.vm file determines the layout of the search form and the results list while config.vm determines the search query to be executed by the portlet.

Since the portlet is capable of displaying itself in several languages, a localization file, localizer_xy.properties, is required for each locale of the Verity search engine. In the file name mentioned, xy stands for the language code concerned. In these files, named strings are defined in the following form:

title: Dealer
buttonSearch: Search
errorNoResults: Your search query did not return any results.

Please note that the localizer files need to be UTF-8-encoded.

Structure of the config.vm Configuration File

After the search form generated by the portlet has been sent, the portlet loads and evaluates the config.vm Velocity template to generate the search query from the data given in the input elements of the form. The parameters from the search form are available in the template under their names.

The evaluation of the template yields an XML document the Search Server is able to process. The portlet sends this document to the Search Server and receives as response an XML document containing the search results to be displayed.

The XML document generated by the Velocity template is required to have the following structure:

<query>
  <condition>...</condition>
  <result-fields>...</result-fields>
  <sort>...</sort>
  <collections>...</collections>
  <page-size>...</page-size>
  <max-hits>...</max-hits>
  <min-score>...</min-score>
</query>

All elements except condition are optional. In the following config.vm example file, a search for documents containing a search term is defined. Only documents without any access restrictions are returned:

<query>
  <condition>
    <and>
      <vql-statement>
        &lt;#MANY&gt;&lt;#STEM&gt;${searchText}
      </vql-statement>
      <vql-statement>
        &lt;#MANY&gt;&lt;#STEM&gt;free &lt;#IN&gt;
        noPermissionLiveServerRead
      </vql-statement>
    </and>
  </condition>
  <result-fields>
    <field>title</field>
    <field>name</field>
    <field>visiblePath</field>
    <field>ip_abstract</field>
  </result-fields>
  <sort>
    <criterion>
      <field>pl_PLZ</field>
      <ascending/>
    </criterion>
    <criterion>
      <field>name</field>
      <ascending/>
    </criterion>
  </sort>
  <page-size>5</page-size>
  <collections>
    <collection>cm-contents-${language}</collection>
  </collections>
  <log>
    <context>search</context>
#if ($user.isLoggedIn())
    <login>${user.login}</login>
#end
    <query>${searchText}</query>
 </log>
</query>

In the following, the meaning of the elements of which the search query consists is explained:

  • condition: This defines the search query. The condition may contain the following operators:
    • and: this element combines the elements contained in it with a logical and.
    • contains: searches files, whose field with name field contains the string value. Example:
      <contains>
        <field>keywords</field>
        <value>service</value>
      </contains>
    • contains-match: searches files, where the wildcard pattern value matches the content of the field named field. Valid wildcards are asterisk (*) and question mark (?).
    • equals: searches files, where the field named field equals exactly the string value. Example:
      <equals>
        <field>name</field>
        <value>mastertemplate</value>
      </equals>
    • or: combines the elements contained in it using or.
    • starts-with: searches files, where the field with name field begins with the string value.
    • vql-statement: contains the search query in the explicit Verity query language. For a detailed explanation of the syntax, please refer to the Search Server manual. Note that special characters need to be specified as HTML entities, for example < as &lt;.
  • result-fields: This element specifies in its field subelements the fields of the resulting documents to be added to the search results. This makes it possible to display the values of these fields on the result pages.
  • sort: Here, one or more criteria for sorting the search results can be specified. Each criterion consists of a field name, field, whose contents is used for sorting, as well as optionally either ascending or descending to define the sort order.
  • page-size: By means of this element, the number of hits to be displayed on each result page can be specified.
  • collections: Specify here the collections to be searched. If this information is omitted, all collecions are searched.
  • log: Specify here the elements to be tracked. Permitted elements are context, login, and query. context must be specified, otherwise tracking is disabled.

Please note that the fields to be searched and also to be returned by the search engine (see result-fields, sort above) must have been defined in the Verity configuration prior to creating the collection concerned (see the Search Cartridge documentation).

Structure of the view.vm Configuration File

This Velocity template serves to generate the HTML text of the search form and the results list.

In the template, you can use the following keywords to access objects in the Velocity context:

  • $action: The action to use in the search form.
  • $params: The list of search parameters used so far. Internal parameters (whose name begins with a underscore character) do not show up in this list.
  • $text: offers access to localized texts (originating from the localization files in the definition directory).
  • $locale: the current locale.
  • $search: Tool with which URLs can be generated. The following methods are available:
    • String getPageUrl(Page page): returns an URL for jumping to the specified page of the results list.
    • String getSearchUrl(String parameter, String value): returns an URL for performing a search using the given parameter.
    • String highlight(String word, String text, String prefix, String suffix): encloses each occurrence of the specified word in text into the strings prefix and suffix ein.
  • $result: allows accessing the results list by means of the following methods:
    • int getHitCount(): returns the number of hits.
    • List getPages(): returns the list of Page objects in the search result.
    • Page getCurrentPage(): returns the Page object which represents the current page of the results list.

The properties of the the objects listed above are described in the portlet's javadoc documentation. Example (starting with the form, then comes the Velocity template):

<form method="get" action="$action">
  <input type="text" name="searchText" value="$!params.searchText"/>
  <input type="submit" name="_buttonSearch"
    value="$text.buttonSearch"/>
</form>

#if ($result)
  #if ($result.hitCount == 0)
    $text.errorNoResults
  #else
    <ul>
    #foreach ($item in $result.currentPage.hits)
      #set($path = $document.getUrl($item.visiblePath))
      <li><a href="$path">$item.title</a></li>
    #end
    </ul>
  #end
#end

Configuring Access to the Search Engine

The search engine is configured in the pm.xml file as a the searchEngine bean. Two implementations are available, direct access via the Search Server, and access via the Content Management Server.

SesSearchEngine: For the live system search the search queries are sent directly to the Search Server running on the live system:

<bean id="searchEngine"
    class="com.infopark.libs.search.ses.SesSearchEngine">
  <property name="host"><value>localhost</value></property>
  <property name="port"><value>3011</value></property>
</bean>

AdvancedCmSearchEngine: When searching via the Content Management Server, the editorial system is searched. Der Content Management Server redirects the queries to the Search Server associated with the editorial system. However, it adds to each hit the path of the CMS file found:

<bean id="searchEngine"
    class="com.infopark.libs.search.cm.AdvancedCmSearchEngine">
  <property name="host"><value>localhost</value></property>
  <property name="port"><value>3002</value></property>
  <property name="user"><value>root</value></property>
  <property name="tokenManager">
    <ref bean="tokenManager"/>
  </property>
</bean>

Example

The following example illustrates how a new search can be created in the portal included in the demo content. The search term that was entered is to be found in the main content, the title, or the abstract, i.e. in the fields named blob, title, and ip_abstract. On the result pages, the fields title and ip_abstract are to be output. The abstract is to be displayed only if it contains text, i.e. if it is not empty.

Preparing the Search Engine Indexes

The fields mentioned above can only be used after the configuration of the search engine on the live server side has been extended so that the additional fields are available and filled-in when documents are indexed.

For this, open the file belonging to the collection which is to be searched, located in the instance concerned.

  instance/instanceName/config/vdk/styles/collectionName/style.ufl

Add the following fields:

# -----------------------------------------------------------------
# Specify additional application-specific fields here in their own
# data-table[s].

data-table: nps
{
  **** Existing fields  
  ****
  **** New fields

  varwidth:   ip_abstract dxa
  autoval:    collection DBNAME
}

Stop the Search Engine Server:

./rc.npsd stop SES

For the changes to become effective, the collection needs to be created again and the documents need to be reindexed. To do this, start the Search Server in single mode:

./SES -single

Now, perform the following steps for the collection concerned:

SES>deleteCollection collectionName
SES>createCollection collectionName
SES>exit

Start the Search Engine Server:

./rc.npsd start SES

Now, connect to the Content Management Server:

./client localhost 3001 root demo

Reindex all NPS files using the following command:

CM>indexAllObjects
CM>exit

Create the new Search

Change to the directory

/instance/instanceName/webapps/PM-PL/WEB-INF/templates/search

and make a copy of the existing directory nameTitle and name it bodySubjects. Then change to this directory and edit the files named config.vm and view.vm.

In the file config.vm the search query is configured. For this purpose change the contents of the condition element so that it contains only the following vql-statement element:

<vql-statement>
  (&quot;${suche}&quot; &lt;#IN&gt; blob) &lt;#OR&gt;
  (title &lt;#SUBSTRING&gt; &quot;${suche}&quot;) &lt;#OR&gt;
  (ip_abstract &lt;#SUBSTRING&gt; &quot;${suche}&quot;)
</vql-statement>

Please note that the serach query consists of several parts (one per field), all of which are combined with OR. Since the main content (blob) is a document zone and not a field (like title and ip_abstract), the operator IN, which searches document zones, is used for searching it. The SUBSTRING operator, on the other hand, searches the contents of fields.

Since the search results are to include the contents of the ip_abstract field, it needs to be added to the result-fields element:

<result-fields>
  <field>objId</field>
  <field>name</field>
  <field>title</field>
  <field>score</field>
  <field>visiblePath</field>
  <field>ip_abstract</field>
</result-fields>

Creating the Search and Results Page

The search and results page are created in the file view.vm. Its upper section defines the search form:

<form action="$action" method="post">
  <div>
    Ihre Suche <input type="text" name="suche"
        value="$!params.suche" />
  </div>
  <input type="submit" name="dialog.buttonOk"
      value="$text.buttonOk" />
</form>

The lower section of the file view.vm makes it possible to display the results after the user has submitted the search form. All results are to be displayed as a list. For each hit, the (linked) title and the abstract of the document is to be displayed:

<ul>
  <li>Verlinkter Titel<br />
  ip_abstract</li>
  <li>...</li>
  <li>...</li>
</ul>

To achieve this display format, the following code is used:

#if ($result)
  #if ($result.hitCount == 0)
    <div>$text.noHits</div>
  #else
    <!-- Number of results -->
    <div>$text.hitCount $result.hitCount</div>
    <!-- Begin of results list generation -->
    <ul>
      #foreach ($item in $result.currentPage.hits)
        <li>
          #set($path = $document.getUrl($item.visiblePath))
          #if ($path)<a href="$path">#end
          $item.title
          #if ($path)</a>#end
          #if ($item.ip_abstract)<br />$item.ip_abstract#end
        </li>
     #end
    </ul>
    <!-- End of results list generation -->
    <!-- Create links to other results pages -->
    <div>
    #foreach ($page in $result.pages)
      #if ($page.isCurrent())
        $page.number
      #else
        <a href="$search.getPageUrl($page)">$page.number</a>
      #end
    #end
    </div>
    <div>$text.page $result.currentPage.number $text.pageOf
        $result.pages.size()
    </div>
    <!-- End of results pages overview -->
  #end
#end

In order to remove the first directory from the paths in the results list (so that the root folder is not displayed), use the following code in the foreach loop:

## Search for directory delimiter  (slash)
#set( prefix $path.indexOf("/", 1) )
## Do not use $path to access the path, instead use:
$path.substring($prefix + 1)

Including the Portlet in a Web Page

To include the portlet in a web page, add the following line to a layout file used for generating the pages to be displayed:

<npspm includePortlet="/PM-PL/search" instance="bodySubjects" />

The portlet is now displayed in the separate preview and can be used.

Clearing the Input Fields of the Search Form

The search portlet stores the values contained in its input fields. If, after a search, a website visitor reenters the search page (the page containing the search portlet), the form contains the values that were entered before.

Since this behavior is normally unwanted, you can have the portlet clear the input fields. For this, up to version 6.5.0, add the reset=true parameter to the URL that links to the search page. Example:

<a href="/suchen.html?reset=true">Search</a>

From version 6.5.1, add a link to the remote control portlet instead. Example.

<npspm includePortlet="/PM-PL/remoteControl" instance="search">
targetUrl=/search.html
emptySearch=true
emptySearchLinkText=Search
</npspm>

The linked text is configured in the portlet itself and reads "New Search". If you wish to be able to specify the linked text in each call to the portlet, using the parameter emptySearchLinkText like in the example above, replace in the webapps/PM-PL/WEB-INF/templates/remote/search/view.vm template file the line

  <a href="javascript:document.emptySearch.submit();">$text.emptySearch</a>

with the following code:

#if ($params.emptySearchLinkText)
    #set ($linkText = $params.emptySearchLinkText)
#else
    #set ($linkText = $text.emptySearch)
#end
    <a href="javascript:document.emptySearch.submit();">$linkText</a>