-
Notifications
You must be signed in to change notification settings - Fork 149
Add Custom Search Criteria
In the geoportal, it is possible to filter search results based on certain metadata elements. For example, you can retrieve documents with specific terms in the title by prefixing the search query in the Search field with title:searchTerm. This is enabled by the Lucene index, which classifies information in a metadata element with a heading by which a user would search. Search syntax is further discussed in Using Lucene Search Text Queries.
It is also possible to add this customized element search as a search filter in the list of available search options shown on the Geoportal Search page. This topic will first describe how to designate specific elements for indexing and search, how to them as custom search filters to the Search page, and how to add them to the Additional Options for advanced search.
Note: When you add the customized search criteria, you can also search the custom field using lucene syntax in the CS-W interface. However, adding the custom field does not alter the geoportal's GetCapabilities operation; the only properties explicitly listed in the geoportal's GetCapabilities are the spatial predicates (e.g., BBOX, Intersects, Within).
Steps for how to add a custom search criteria are discussed in this topic.
This section assumes that you have an initial understanding of the geoportal's property-meanings.xml file as described in Details of Lucene Indexing in the Geoportal. In this example, you will configure the geoportal to index an element from the ISO metadata schema, as defined by the \\geoportal\WEB-INF\classes\gpt\metadata\iso\apiso-indexables.xml file.
Tip: This customization does not require the Geoportal Server source code; however, you will be creating new java classes, so basic java programming knowledge is highly recommended. If you use an IDE to compile the new class, remember to import the geoportal.war file into your project as you develop.
Likely you already have in mind a metadata element that your organization would like to be searchable from the Geoportal Search page. It is important to be able to locate this element in one or more metadata profiles your geoportal supports. Verify that you can find the metadata element in one of the indexables files (e.g., \\geoporta\WEB-INF\classes\gpt\metadata\iso\apiso-indexables.xml). In this example, we will add two search filters from the ISO 19115 metadata standard: Lineage and Hierarchy Level. To see these fields in the geoportal metadata editor, launch the geoportal and log in as a publisher user or administrator. Then, click the Administration tab, and then the Add link. Select the Use dedicated editor to create metadata manually option, and then choose the ISO 19115 profile. In the resulting form, you should see the ‘Metadata’ tab. On that Metadata tab there is an Identification tab that contains the “Hierarchy Level” dropdown. The “Lineage” element can be found on the Quality tab.
If the mappings for your chosen metadata elements already appear in one of the geoportal's indexables.xml files, then they’re indexed by default. However, if you have created a custom metadata profile or added new metadata elements to the default profiles, then you may need to define the indexing for those elements. Follow guidance in the "Determine if the chosen element is already indexed by default" section in the Details of Lucene Indexing in the Geoportal topic. After carrying out recommendations for how the chosen elements are indexed, you should be able to input a lucene query for each element on the Geoportal Search page and retrieve relevant results – assuming content in your geoportal matches your query.
It is necessary to ensure that your indexed elements are available through CS-W. To do this, you will need to verify that the elements have a Dublin Core mapping in the property-meanings.xml file. If they don't already have one, you will need to add it in. In our example, we give the lineage and hierarchy elements a dublin core name and an alias, as shown below:
<property-meaning name="apiso.Lineage" valueType="String" comparisonType="keyword">
<apiso name="apiso:Lineage"/>
<dc name="dc:lineage" aliases="lineage"/>
</property-meaning>
...
<property-meaning name="apiso.Type" valueType="String" comparisonType="keyword">
<apiso name="apiso:Type"/>
<dc name="dc:type" aliases="hierarchy,type,format,dc:format"/>
</property-meaning>
Now you will prepare custom search filters for the Geoportal Search page. Note that in this example, our new searchable elements - lineage and hierarchy - are text fields. If your new elements are a date type or a multiselect field, the steps below should be adapted to the type of the field. A sample for customizing the search UI with checkboxes can be found here.
The geoportal compiled code already includes classes for the default search options seen on the Search page. To include your additional filter, you will need to create new classes that define your search filters. The new classes must implement the ISearchFilter interface, or extend/implement one of the children of ISearchFilter. It is recommended to follow the naming convention of the other search filter classes within the geoportal, so the name of your classes will be SearchFilter.name_of_your_indexing_field.java. Important: Make a separate class for each search filter. The code shown below is an example of a class named SearchFilterLineage.java that could be used for adding our example lineage element; if you want to include the hierarchy filter as well, you would create another class similar to this called SearchFilterHierarchy.java. In the example below, note that wherever you see ‘Lineage’ you’ll need to update the example to match your own search element. After you author and compile the new classes, put the resulting class files into the \\geoportal\WEB-INF\classes directory.
import com.esri.gpt.catalog.search.ISearchFilter;
import com.esri.gpt.catalog.search.SearchException;
import com.esri.gpt.catalog.search.SearchParameterMap;
import com.esri.gpt.catalog.search.SearchParameterMap.Value;
import com.esri.gpt.framework.util.Val;
@SuppressWarnings("serial")
public class SearchFilterLineage implements ISearchFilter {
// key to be used to serialize class to a map
private static String KEY_LINEAGE = "apiso.Lineage";
// instance variable
private String lineage;
// property (Can be used by jsf(advanced search page)
public String getLineage() {
return Val.chkStr(lineage);
}
// property (Can be used by jsf(advanced search page)
public void setLineage(String lineage) {
this.lineage = lineage;
}
// Serialize class instance into a map
public SearchParameterMap getParams() {
SearchParameterMap map = new SearchParameterMap();
map.put(KEY_LINEAGE, map.new Value(this.getLineage(), ""));
return map;
}
// The class may receive a new map for deserialization (e.g. saved searches
// can trigger this
public void setParams(SearchParameterMap parameterMap) throws SearchException {
Value val = parameterMap.get(KEY_LINEAGE);
this.setLineage(val.getParamValue());
}
// Deep comparison of filters
public boolean isEquals(Object obj) {
if (obj instanceof SearchFilterLineage) {
return ((SearchFilterLineage) obj).getLineage().equals(this.getLineage());
}
return false;
}
// This will be called by the clear button
public void reset() {
this.setLineage("");
}
// Before search, validate will be called. An exception can be thrown
// that will stop the search and the error is displayed on the search page
public void validate() throws SearchException {
if (this.getLineage().equals("this should throw an exception")) {
throw new SearchException("this should throw an exception");
}
}
}
Search parameters and their values are stored in session variables. These variables are created when a user loads the first web page of the site, and the variables then persist till the user closes the browser or does not create any web requests for a certain amount of time. The JavaServer Faces framework, upon which the Geoportal Server is built, has a configuration file where session variables are stored. This file is located in the \\geoportal\WEB-INF directory, and is called gpt-faces-config.xml. You will need to update this file in two places.
- Under the commented section titled "Search Beans", add a new managed bean for each of your new search filters to store your new variables in the session. Note that in the example below, we reference our example Lineage and Hierarchy elements; you will need to edit this to match the elements for which you are customizing:
<!--managed bean for lineage search-->
<managed-bean>
<description>Search Filter with lineage properties</description>
<managed-bean-name>SearchFilterLineage</managed-bean-name>
<managed-bean-class>SearchFilterLineage</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
<!-- managed bean for Hierarchy search -->
<managed-bean>
<description>Search Filter with hierarchy properties</description>
<managed-bean-name>SearchFilterHierarchy</managed-bean-name>
<managed-bean-class>SearchFilterHierarchy</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
- In the managedProperty called miscelleniousFilters, you will need to make edits. First, verify that the value-class is set to com.esri.gpt.catalog.search.ISearchFilter. In the list of values, add a value that references your new managed bean. In the example below, we add the lines <value>#{SearchFilterLineage}</value> and <value>#{SearchFilterHierarchy}</value>, which are the names of the two Search Filter classes we just created:
<managed-property>
<property-name>miscelleniousFilters</property-name>
<property-class>com.esri.gpt.catalog.search.SearchFiltersList</property-class>
<list-entries>
<value-class>com.esri.gpt.catalog.search.ISearchFilter</value-class>
<value>#{SearchFilterHarvestSites}</value>
<value>#{SearchFilterLineage}</value>
<value>#{SearchFilterHierarchy}</value>
</list-entries>
</managed-property>
For your custom search to work from the search page, it must also work from the REST API. The RestQueryServlet class is the controller for REST API searches and should be overridden. The code below is shown for creating the class that will override the RestQueryServlet class that can be named CustomRestQueryServlet.java. After you author and compile the new class, put the resulting class file into the \\geoportal\WEB-INF\classes directory. Note: In this example, you see that there is a REST_PARAM_KEY for each filter, lineage and hierarchy. You will need to update these references for the elements you want to search. Also, you will notice that in the "parser.parsePropertyIsLike" method, we refer to the elements as apiso.Lineage and apiso.Type. This corresponds to the way the elements are named in the \\geoportal\WEB-INF\classes\gpt\metadata\property-meanings.xml file.
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import com.esri.gpt.catalog.discovery.rest.RestQuery;
import com.esri.gpt.catalog.discovery.rest.RestQueryParser;
import com.esri.gpt.catalog.search.SearchCriteria;
import com.esri.gpt.control.georss.RestQueryServlet;
import com.esri.gpt.framework.context.RequestContext;
import com.esri.gpt.framework.util.Val;
public class CustomRestQueryServlet extends RestQueryServlet {
private static String REST_PARAM_KEY1 = "lineage";
private static String REST_PARAM_KEY2 = "hierarchy";
//Relate the rest queryable to the CSW queryables
protected RestQuery parseRequest(HttpServletRequest request, RequestContext context) {
Logger LOG = Logger.getLogger(RestQuery.class.getCanonicalName());
RestQuery query = super.parseRequest(request, context);
RestQueryParser parser = new RestQueryParser(request,context,query);
// "lineage" will be the name of the rest queryable
parser.parsePropertyIsLike(REST_PARAM_KEY1, "apiso.Lineage");
parser.parsePropertyIsLike(REST_PARAM_KEY2, "apiso.Type");
/** The below is shown as an example
parser.parseRepositoryId("rid");
parser.parseResponseFormat("f");
parser.parseResponseGeometry("geometryType");
parser.parseResponseStyle("style");
parser.parseResponseTarget("target");
parser.parseStartRecord("start",1);
parser.parseMaxRecords("max",10);
parser.parsePropertyIsEqualTo("uuid","uuid");
parser.parsePropertyIsLike("searchText","anytext");
parser.parsePropertyList("contentType","dc:type",",",true);
parser.parsePropertyList("dataCategory","dc:subject",",",true);
parser.parsePropertyRange("after","before","dct:modified");
parser.parseSpatialClause("bbox","spatialRel","geometry");
parser.parseSortables("orderBy");
**/
LOG.log(Level.FINER, "In Custom Rest Query Servlet");
return query;
}
//Populate the searchCriteria with the rest queryable lineage
protected SearchCriteria toSearchCriteria(HttpServletRequest request,
RequestContext context, RestQuery query) {
SearchCriteria criteria = super.toSearchCriteria(request, context, query);
RestQueryParser parser = new RestQueryParser(request,context, query);
String sLineage = Val.chkStr(parser.getRequestParameter(REST_PARAM_KEY1));
if (sLineage.length() > 0) {
SearchFilterLineage filterLineage = new SearchFilterLineage();
filterLineage.setLineage(sLineage);
criteria.getMiscelleniousFilters().add(filterLineage);
}
String sHierarchy = Val.chkStr(parser.getRequestParameter(REST_PARAM_KEY2));
if (sHierarchy.length() > 0) {
SearchFilterHierarchy filterHierarchy = new SearchFilterHierarchy();
filterHierarchy.setHierarchy(sHierarchy);
criteria.getMiscelleniousFilters().add(filterHierarchy);
}
return criteria;
}
}
Open the \\geoportal\WEB-INF\web.xml file. Find the "servlet" element that has the "servlet-name" set to RestQueryServlet. Update its "servlet-class" element from com.esri.gpt.control.georss.RestQueryServlet to CustomRestQueryServlet, as shown below. Then, save the file.
<servlet>
<servlet-name>RestQueryServlet</servlet-name>
<servlet-class>CustomRestQueryServlet</servlet-class>
<init-param>
<param-name>bundleBaseName</param-name>
<param-value>gpt.resources.gpt</param-value>
</init-param>
<load-on-startup>6</load-on-startup>
</servlet>
The criteria.jsp file defines the search criteria fields on the Search page, including the pop-up interface for Additional Options. Now that you've created the filters and done the underlying work to reference them in the geoportal, it is important to add them to this search interface. The steps below first show how to add the lineage filter to the main search page. Then we show how to add the hierarchy filter to the Additional Options dialog.
- Open the \\geoportal\catalog\search\criteria.jsp file. Find the section that defines the function scReadRestUrlParams():
function scReadRestUrlParams() {
var restParams = "";
var bIsRemoteCatalog = scIsRemoteCatalog();
...
- Update this function with new variables that will add your custom filter REST parameters (e.g., lineage and hierarchy in our example) to be appended to the generated REST URLs, such that the function now looks like this:
function scReadRestUrlParams() {
var restParams = "";
var bIsRemoteCatalog = scIsRemoteCatalog();
var scLineage = GptUtils.valChkStr(
dojo.byId('frmSearchCriteria:scLineage').value);
if(scLineage != "") {
restParams += "&lineage=" + encodeURIComponent(scLineage)+"&";
}
var scHierarchy = GptUtils.valChkStr(
dojo.byId('frmSearchCriteria:scHierarchyHidden').value);
if(scHierarchy != "") {
restParams += "&hierarchy=" + encodeURIComponent(scHierarchy)+"&";
}
- Notice that in the scHierarchy variable, “scHierarchyHidden” is used, whereas ‘Hidden’ is left off of the scLineage variable. This is because we intend to put the Hierarchy in the Additional Options popup, and so using this convention makes it clear what is visible on the Search page and what is seen in the Additional Options.
- After this function, additional functions and their variables that manage the Search page functionality are defined in the criteria.jsp file. Then the User Interface elements are defined, starting with the "search text and submit button" section. Now, scroll down to find the “h:outputText” section with the name “catalog.search.additionalOptions”. All user interface elements that are within this "h:output text" element will be included in the Additional Options dialog. In our example, we will have the Lineage element display just before the Map search on the Search page and not in the Additional Options, and so we make sure to include it before the Additional Options h:outputText section. We add the following just above the "spatial filter" section:
<% // lineage (added) %>
<h:outputText escape="false" value="<h3>"/>
<h:outputText id="scLblLineage" value="#{gptMsg['catalog.search.filterLineage.title']}" />
<h:outputText escape="false" value="</h3>"/>
<h:inputText id="scLineage" onchange="javascript:updateHiddenValue(this)" value="#{SearchFilterLineage.lineage}" maxlength="4000" styleClass="searchBox" />
- Note that the value defined for the outputText id=scLbl will be a string that will need to be referenced in the gpt.properties file, the file that manages all of the text in the geoportal user interface through key-value references. Save the criteria.jsp file, and open the \\geoportal\WEB-INF\classes\gpt\resources\gpt.properties file.
- Search for the section in gpt.properties where search filters are defined. Keys for search filters begin with the string catalog.search.filter.
- Add a new value. This value should match the scLbl string you defined in your h:outputText id=scLbl element from the criteria.jsp file. In our example we add the following, also adding a string for the hierarchy search label, since we will be configuring its filter next:
catalog.search.filterLineage.title = Lineage
catalog.search.filterHierarchy.title = Hierarchy Level
- Save the gpt.properties file.
Remember that you’ve already created a Search Filter class for the Hierarchy filter, and you’ve already added a reference to it in the gpt-faces-config.xml file. You’ve also referenced your Hierarchy Search Filter class in the CustomRestQueryServlet class, and added a line for its label in the gpt.properties file. All that remains is to add it to the Additional Options user interface by referencing it correctly in the criteria.jsp file.
- Again, open the \\geoportal\catalog\search\criteria.jsp file, Recall that you’ve already added a variable to construct the REST URL, in the scReadRestUrlParams function.
- Now you will add the User Interface elements. Scroll down to the modification date section. You will insert your hierarchy filter just after the final h:panelGroup tag in the modification date section. Find this section and insert the following (substituting your element for the hierarchy one here):
<% //Hierarchy Filter %>
<h:outputText escape="false" value="<h3>"/>
<h:outputText id="scLblHierarchy" value="#{gptMsg['catalog.search.filterHierarchy.title']}" />
<h:outputText escape="false" value="</h3>"/>
<h:inputText id="scHierarchy" onchange="javascript:updateHiddenValue(this)" value="#{SearchFilterHierarchy.hierarchy}" maxlength="4000" styleClass="searchBox" />
- Again, note that the value defined for the outputText id=scLbl should match the string you added to the gpt.properties file.
- Now scroll near the end of the criteria.jsp file. Here there is a section that further defines the search options on the Additional Options dialog. Add a value for your newly added hidden search option, beneath the h:inputHidden id="scSelThemeHidden" tag in the list, as shown below. Note that the id for your inputHidden tag should be similar to the id in the h:inputText element in the piece of code you just added to this file. In our example for the inputText element, the id was scHierarchy. In this h:inputHidden element, the id will be scHierarchyHidden:
<h:outputText escape="false" value="</div>"/>
<h:inputHidden id="scSelSortHidden" value="#{SearchFilterSort.selectedSort}"/>
<h:inputHidden id="scDateToHidden" value="#{SearchFilterTemporal.dateModifiedTo}"/>
<h:inputHidden id="scDateFromHidden" value="#{SearchFilterTemporal.dateModifiedFrom}"/>
<h:inputHidden id="scSelContentHidden" value="#{SearchFilterContentTypes.selectedContentType}"/>
<h:inputHidden id="scSelThemeHidden" value="#{SearchController.searchCriteria.searchFilterThemes.selectedThemes}">
<f:converter converterId="gpt.ListToString" />
</h:inputHidden>
<h:inputHidden id="scHierarchyHidden" value="#{SearchFilterLineage.lineage}"/>
- Save the criteria.jsp file.