Overview
This page presents the simplest-possible example of a stand-alone Java application that sends a query to AIE from the command line and displays the search results in the console window. It demonstrates the fundamental features that all such search clients must possess.
View incoming links.
Create a Search Application in Java
The examples shown here presume that the AIE index contains documents from the Factbook (Quick Start Tutorial) demo. The search application is sufficiently generic to work with any AIE documents, however.
Create the Project
Create the plusjava project from the Using Java APIs page in AIE Designer. Then follow the procedures on this page.
Sample Data
The query application demonstrated here is sufficiently generic to use with any AIE project. For sake of demonstration, we started the FactBook demo and loaded the "city" feed. At the end of the example we'll search for "London" and AIE will return multiple matches.
Create a New Class
To create a new Java class in AIE Designer for this application:
- In Designer, right-click the project node in the Project Explorer view. In this example the project is plusjava.
- Select New > Class from the context menu. This exposes the New Java Class dialog.
- In the New Java Class dialog, set the Source Folder to plusjava/src.
- In the New Java Class dialog, enter a new package name such as com.acme.client. The object is to create a package that does not already exist in the project to prevent any possible name collisions.
- Continuing in the Java Class dialog, enter a name for the new class. In this example we used SearchQueryTest.
- Click the Finish button to create the class. This will open the new class in an editing view.
- For the purposes of this exercise, open the SearchQueryTest.java file (attached to this page) and paste the content into the editor. Save the file.
Import Required Classes
The SearchQueryTest.java file begins with a package statement and a list of imported classes. (The library files that support these classes are already part of the Eclipse project generated by createproject.)
import java.io.IOException; import java.util.ListIterator; import com.attivio.sdk.search.QueryRequest; import com.attivio.sdk.search.QueryResponse; import com.attivio.sdk.AttivioException; import com.attivio.sdk.search.QueryLanguages; import com.attivio.sdk.search.SearchField; import com.attivio.sdk.search.SearchDocument; import com.attivio.sdk.search.facet.FacetBucket; import com.attivio.sdk.search.facet.FacetRequest; import com.attivio.sdk.search.facet.FacetResponse; import com.attivio.sdk.search.facet.ScopeFacetRequest; import com.attivio.sdk.search.fields.StoredField; import com.attivio.sdk.client.AieClientFactory; import com.attivio.sdk.client.SearchClient; import com.attivio.sdk.service.Platform; import com.attivio.sdk.service.ServiceFactory;
Class Declaration
The SearchQueryTest class will connect to a "remote" AIE server. The host and port number are stored in variables in this example.
public class SearchQueryTest { public QueryRequest request; // Change these to point to your AIE server private static final String host = "localhost"; private static final int port = 17000;
Setting up Main
To run this class as an independent application, we must supply it with a main method.
public static void main(String[] args) throws IOException {
The main() method expects a string array of arguments, which will be fed in from the command line. It throws appropriate exceptions.
Create a Search Client
Use the ServiceFactory to instantiate a SearchClient object.
// create an attivio search client System.setProperty("AIE_ZOOKEEPER", "localhost:16980"); Platform.instance.setProjectName("remote"); // must be the name of the remote project try (SearchClient searchClient = ServiceFactory.getService(SearchClient.class)) {
Once we have the searchClient object, we can set its clientWorkFlow value. The default search workflow is called "search".
// Must set a workflow: default workflow is "search" searchClient.setClientWorkflow("search");
Create a Query Request
Now we can create a QueryRequest object and set some of its properties.
// The queryString comes from the command line QueryRequest request = new QueryRequest(args[0], QueryLanguages.ADVANCED);
It suits the current example to use the Advanced Query Language. Your application might find QueryLanguages.SIMPLE to be adequate.
Optional Extras
At this point in the process we can optionally experiment with some additional minor features, or just skip this section and go on to the next one.
Add Security Filter
The page Custom Index Security With ACLs points to this page for an example of how to use Access Control Lists to restrict the results of a query. Set up the query client as shown on this page, and then issue a query that is filtered by access-control tokens. We do this by adding a filter to the query. Filters are ANDed with the query, so only those documents that match the filter are returned.
Presuming that the documents have an acl field containing UserID and GroupID values (as demonstrated on the Custom Index Security With ACLs page), we can create a query filter to match a specific UserID or GroupID:
// Add a filter (filters are ANDed to the current query) which matches acls on UserID or GroupID // request.addFilter("OR(acl:" + UserID + ", acl:" + GroupID + ")", QueryLanguages.ADVANCED);
In practice, you would have to supply the UserID and GroupID values, which is beyond the scope of this example.
Relational Joins
The page that describes the Relational Querying JOIN feature points to this page to show how to do JOIN queries via the Java Client API.
This is very simple. Just set up the search application as shown on this page, and then plug in a string containing the text of the JOIN query, like this:
QueryRequest request = new QueryRequest("JOIN(table:country, INNER(table:city, on=country), INNER(table:medal, on=country), INNER(table:news, on=country))", QueryLanguages.ADVANCED);
This JOIN query is from the Quick Start Tutorial example. The query returns a list of all countries that have at least one city, have won at least one Olympic medal, and that are currently in the news.
Add Fields
If you don't tell AIE which fields to return, it helpfully returns them all. Alternately, you can demand them all, as shown here:
// Return all fields. request.addField("*");
If you would like AIE to return a specific set of fields or field expressions, you must request them one at a time, like this:
// Return specific fields. request.addField( new StoredField("title") ); request.addField( new StoredField("date") ); request.addField( new StoredField("text") );
Pagination
You can use the Search Application Example (on this page) to experiment with AIE's pagination commands. The setOffset() and setRows() methods control which results will be returned from the index. You can used these methods to page through a long list of result items in the user interface. Insert these lines of code just before the line that executes the search:
// Demonstrate pagination request.setOffset(5); // Skip the first 5 items. request.setRows(5); // Show 5 result items.
This example skips over the first five result items (the first page of results that has already been viewed. It then displays the next five items (the current page).
Note that pagination interacts with AIE's features for Sorting Results.
Static Named Facet
The default name of a facet is the name of the field that is being faceted. When that isn't desired, we can apply a name to the facet and retrieve it on that basis.
// Add a static facet request (a "named" facet) FacetRequest namedFacet = new FacetRequest("Feelings"); namedFacet.setField("sentiment"); request.addFacet(namedFacet);
In this example, "sentiment" is the field the facet is based on. "Feelings" is the arbitrary name we chose to use instead.
We'll retrieve the facet data by name later on this page.
Scope Facet
This optional feature is presented for people who want to issue a scope search (such as "text:scope(location)") and get a scope facet in the search results.
// Add a scope facet request. Requires that you issue a // scope query such as "text:scope(location)". //request.addFacet( new ScopeFacetRequest("text", "location") );
This scope search asks AIE for all documents where any location entity appears in the text field. For more information, see Scope Search and Scope Facets.
Business Center Profile
You can have your query use a published Attivio Business Center profile. All you need is the name of the profile.
// Set Search Profile. Use name of a published ABC Profile //request.setSearchProfile("newsonly");
Execute the Query
The request object is all ready to go. To execute the query, pass the request to the searchClient.search() method.
// Execute search request, get response QueryResponse response = searchClient.search(request);
This method returns a QueryResponse object, which we have bound to the variable response.
Display Returned Documents
The remainder of SearchQueryTest:
- Prints out the number of matching documents.
- Prints out the (optional) facets.
- Iterates over documents in the response object.
- Iterates over fields in each document.
- If necessary, iterates over multiple values found in a field.
- Prints document information to the command window.
Number of Matching Documents
First, let's display the number of matching documents in the result set.
// Display results System.out.println(""); System.out.println("Total documents matched: " + response.getDocuments().getNumberFound());
Display (Optional) Named Facet
Once we have the QueryResponse object, we can extract facets by name. Here we extract and print out the content of the "Feelings" facet.
// Print out named facet FacetResponse facet = response.getFacet("Feelings"); System.out.println("Facet: Feelings"); for (FacetBucket bucket : facet) { System.out.println(" " + bucket.getDisplayValue() + " " + bucket.getCount()); }
Display (Optional) Scope Facet
If you are following the optional scope facet demonstration, this is where we retrieve and display the facet buckets.
// Print out scope facet facet = response.getFacet("text:location"); System.out.println("Facet: text:location"); for (FacetBucket bucket : facet) { System.out.println(" " + bucket.getDisplayValue() + " " + bucket.getCount()); }
For more information, see Scope Search and Scope Facets.
Iterate over Response Documents
For each document, we need to iterate over the document's fields and field values.
// Iterate through documents for (SearchDocument document : response.getDocuments()) { System.out.println(""); // Print document ID System.out.println("document ID: " + document.getId());
Each block of document output begins with the document ID number, as shown above.
Next is the loop that processes the fields within the document:
// Iterate through fields for (Field<?> field : document) { // Print field name System.out.print(" " + field.getName());
This is followed by the nested loop that prints out the values of the field. Most fields are single-valued, but some are multi-valued. The logic has to check and branch to handle the two situations.
// Check field value count if (field.size() == 1) { // Print single value System.out.println(": " + field.getDisplayValue()); } else { // Print multiple values int numFieldValues = field.getValues().toArray().length; System.out.print(" {" + numFieldValues + " values}:"); int valNum = 0; for (Object value: field.getValues()) { valNum++; if (value instanceof String) { System.out.print(" {" + valNum + "} " + value); } else if (value instanceof Integer) { String valueString = "" + value; System.out.print(" {" + valNum + "} " + valueString); } } System.out.println(""); } } }
Catch Exceptions
We have to include some error-catching boilerplate at the end. This independent Java application might have to handle an AttivioException generated by the Java Client library. Note that this application is not a part of AIE, however, and therefore should not throw an error. AIE isn't there to catch it.
} catch (AttivioException e) { System.err.println("Category: " + e.getErrorCode().getCategory()); System.err.println("Code: " + e.getErrorCode().getCode()); e.printStackTrace(); } } }
Compile the Class
To compile SearchQueryTest in Designer, select the project in the Project Explorer. Click the Project menu, and select Clean. Then select Project > Build Project.
Run the Program
To run SearchQueryTest from Designer, we'll have to create a Run Configuration for it.
- In Designer, navigate from the Run menu to Run Configurations. This opens the Run Configurations dialog box.
- Right-click Java Applications and select New.
- Create a new SearchQueryTest run application in project plusjava.
On the Arguments Tab, paste in the query string. You can simply search for the keyword "London", or for the scope-facet experiment, enter a scope search such as "text:scope(location)".
Run Configuration Command-Line ArgumentsLondon
On the Arguments Tab, paste in the following VM arguments. (Edit the file path to suit your situation.)
Run Configuration VM Arguments-Dattivio.log.printStackTraces=true -Dattivio.log.level=INFO -Dattivio.log.directory="C:\attivio-projects\plusjava\build\logs"
- Apply the changes. Close the dialog box.
Example Search Run
AIE must be running!
It should go without saying that the AIE server should be up and running before you attempt to run SearchQueryTest.
To run the application, use the Eclipse Run menu. Select the Run command.
In the following example the query is "London". If the target AIE project is a FactBook demo with the cities feed loaded, our query should return multiple cities. This output appears in the Designer console view. Each city result looks like this one:
document ID: CITY-United Kingdom-London .score: 2.1944141 .id: CITY-United Kingdom-London .zone: default title: London teaser: Greater London language: English languages: English size: 6967500 table: city date: 2014-09-04T18:35:08 text: Greater London position: -0.0022,0.8990 latitude: 51.506325 longitude: -0.127144 sourcepath: C:\attivio42\conf\factbook\content\cities.csv filename: cities.csv location {2 values}: {1} London {2} Greater London conversionErrorCode: 0 country: United Kingdom newDate: 04 Sep 2014