diff --git a/build.xml b/build.xml index 62d10d6..74514cc 100644 --- a/build.xml +++ b/build.xml @@ -36,6 +36,7 @@ + @@ -64,6 +65,7 @@ + diff --git a/pom.xml b/pom.xml index 3f82685..95ff89a 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ indextank-java jar Indextank Java Client - 1.0.8-SNAPSHOT + 1.0.11-SNAPSHOT Java client for Indextank search engine http://www.indextank.com diff --git a/src/main/java/com/flaptor/indextank/apiclient/ApiClient.java b/src/main/java/com/flaptor/indextank/apiclient/ApiClient.java index 2a29a26..2397636 100644 --- a/src/main/java/com/flaptor/indextank/apiclient/ApiClient.java +++ b/src/main/java/com/flaptor/indextank/apiclient/ApiClient.java @@ -4,6 +4,7 @@ import java.util.List; import com.flaptor.indextank.apiclient.IndexTankClient.Index; +import com.flaptor.indextank.apiclient.IndexTankClient.IndexConfiguration; public interface ApiClient { @@ -13,7 +14,7 @@ public interface ApiClient { Index createIndex(String indexName) throws IOException, IndexAlreadyExistsException, MaximumIndexesExceededException; - Index createIndex(String indexName, boolean publicSearch) throws IOException, + Index createIndex(String indexName, IndexConfiguration conf) throws IOException, IndexAlreadyExistsException, MaximumIndexesExceededException; void deleteIndex(String indexName) throws IOException, diff --git a/src/main/java/com/flaptor/indextank/apiclient/Index.java b/src/main/java/com/flaptor/indextank/apiclient/Index.java index f32c554..b559a61 100644 --- a/src/main/java/com/flaptor/indextank/apiclient/Index.java +++ b/src/main/java/com/flaptor/indextank/apiclient/Index.java @@ -4,6 +4,8 @@ import java.util.Date; import java.util.Map; +import com.flaptor.indextank.apiclient.IndexTankClient.IndexConfiguration; + public interface Index { @@ -23,15 +25,15 @@ void deleteBySearch(IndexTankClient.Query query) throws IOException, * Creates this index. * * @param publicSearch - * enable public search for this index + * enable public search for this index. if null, public search will be disabled. * @throws IndexAlreadyExistsException * If it already existed * @throws MaximumIndexesExceededException * If the account has reached the limit */ - void create(boolean publicSearch) throws IOException, IndexAlreadyExistsException, - MaximumIndexesExceededException; - + void create(IndexConfiguration conf) throws IOException, IndexAlreadyExistsException, + MaximumIndexesExceededException; + /** * Creates this index. * @@ -45,6 +47,15 @@ void create(boolean publicSearch) throws IOException, IndexAlreadyExistsExceptio void create() throws IOException, IndexAlreadyExistsException, MaximumIndexesExceededException; + + /** + * Update this index. + * + * @throws IndexDoesNotExistException* + * if the index does not exist + */ + void update(IndexConfiguration conf) throws IOException, IndexDoesNotExistException; + /** * Delete this index * @@ -218,14 +229,19 @@ Map listFunctions() throws IndexDoesNotExistException, * @throws IOException */ boolean hasStarted() throws IOException, IndexDoesNotExistException; + + String getStatus() throws IOException, IndexDoesNotExistException; String getCode() throws IOException, IndexDoesNotExistException; Date getCreationTime() throws IOException, IndexDoesNotExistException; + + boolean isPublicSearchEnabled() throws IOException, IndexDoesNotExistException; void refreshMetadata() throws IOException, IndexDoesNotExistException; Map getMetadata() throws IOException, IndexDoesNotExistException; + } diff --git a/src/main/java/com/flaptor/indextank/apiclient/IndexAlreadyExistsException.java b/src/main/java/com/flaptor/indextank/apiclient/IndexAlreadyExistsException.java index ba3b492..a6b9ffb 100644 --- a/src/main/java/com/flaptor/indextank/apiclient/IndexAlreadyExistsException.java +++ b/src/main/java/com/flaptor/indextank/apiclient/IndexAlreadyExistsException.java @@ -4,6 +4,11 @@ public class IndexAlreadyExistsException extends Exception { + + public IndexAlreadyExistsException(String message) { + super(message); + } + public IndexAlreadyExistsException(HttpCodeException source) { super(source.getMessage()); } diff --git a/src/main/java/com/flaptor/indextank/apiclient/IndexDoesNotExistException.java b/src/main/java/com/flaptor/indextank/apiclient/IndexDoesNotExistException.java index bdf316b..5dddb46 100644 --- a/src/main/java/com/flaptor/indextank/apiclient/IndexDoesNotExistException.java +++ b/src/main/java/com/flaptor/indextank/apiclient/IndexDoesNotExistException.java @@ -4,7 +4,12 @@ public class IndexDoesNotExistException extends Exception { + public IndexDoesNotExistException(HttpCodeException source) { super(source.getMessage()); } + + public IndexDoesNotExistException(String message) { + super(message); + } } diff --git a/src/main/java/com/flaptor/indextank/apiclient/IndexTankClient.java b/src/main/java/com/flaptor/indextank/apiclient/IndexTankClient.java index 610f8d1..aef3c89 100644 --- a/src/main/java/com/flaptor/indextank/apiclient/IndexTankClient.java +++ b/src/main/java/com/flaptor/indextank/apiclient/IndexTankClient.java @@ -273,12 +273,15 @@ public static class SearchResults { public final float searchTime; public final List> results; public final Map> facets; + public final String didYouMean; public SearchResults(Map response) { matches = (Long) response.get("matches"); searchTime = Float.valueOf((String) response.get("search_time")); results = (List>) response.get("results"); facets = (Map>) response.get("facets"); + Object didYouMean = response.get("didyoumean"); + this.didYouMean = (didYouMean != null)? didYouMean.toString() : null; } @Override @@ -328,6 +331,8 @@ public String getValue() { protected List documentVariableFilters; protected Map queryVariables; protected String queryString; + protected String fetchCategories; + protected String fetchVariables; public static Query forString(String query) { return new Query(query); @@ -369,7 +374,17 @@ public Query withSnippetFields(List snippetFields) { public Query withSnippetFields(String... snippetFields) { return withSnippetFields(Arrays.asList(snippetFields)); } - + + public Query withFetchCategories() { + this.fetchCategories = "*"; + return this; + } + + public Query withFetchVariables() { + this.fetchVariables = "*"; + return this; + } + public Query withFetchFields(List fetchFields) { if (fetchFields == null) { throw new NullPointerException("fetchFields must be non-null"); @@ -468,6 +483,10 @@ ParameterMap toParameterMap() { params.put("snippet", join(snippetFields, ",")); if (fetchFields != null) params.put("fetch", join(fetchFields, ",")); + if (fetchCategories != null) + params.put("fetch_categories", fetchCategories); + if (fetchVariables != null) + params.put("fetch_variables", fetchVariables); if (categoryFilters != null) params.put("category_filters", JSONObject.toJSONString(categoryFilters)); @@ -532,6 +551,21 @@ public static String join(Iterable s, String delimiter) { } + public static class IndexConfiguration { + protected Boolean publicSearch; + + public IndexConfiguration enablePublicSearch(Boolean publicSearchEnabled) { + this.publicSearch = publicSearchEnabled; + return this; + } + + protected Map toConfigurationMap() { + HashMap conf = new HashMap(); + if (this.publicSearch != null) conf.put("public_search", this.publicSearch); + return conf; + } + } + private static final String GET_METHOD = "GET"; private static final String PUT_METHOD = "PUT"; private static final String DELETE_METHOD = "DELETE"; @@ -587,8 +621,10 @@ private static Object callAPI(String method, String urlString, // http://code.google.com/p/googleappengine/issues/detail?id=1454 urlConnection.setInstanceFollowRedirects(false); urlConnection.setDoOutput(true); - urlConnection.setRequestProperty("Authorization", - "Basic " + Base64.encodeBytes(privatePass.getBytes())); + if (privatePass != null && !privatePass.isEmpty()) { + urlConnection.setRequestProperty("Authorization", + "Basic " + Base64.encodeBytes(privatePass.getBytes())); + } urlConnection.setRequestMethod(method); if (method.equals(PUT_METHOD) && data != null) { @@ -730,14 +766,20 @@ public void deleteBySearch(Query query) throws IOException, @Override public void create() throws IOException, IndexAlreadyExistsException, MaximumIndexesExceededException { - this.create(false); + this.create(null); } @Override - public void create(boolean publicSearch) throws IOException, IndexAlreadyExistsException, + public void create(IndexConfiguration conf) throws IOException, IndexAlreadyExistsException, MaximumIndexesExceededException { - Map data = new HashMap(); - data.put("public_search", publicSearch); + + if (this.exists()) + throw new IndexAlreadyExistsException("Index already exists"); + + Map data = null; + if (conf != null) + data = conf.toConfigurationMap(); + try { callAPI(PUT_METHOD, indexUrl, null, data, privatePass); } catch (HttpCodeException e) { @@ -751,6 +793,33 @@ public void create(boolean publicSearch) throws IOException, IndexAlreadyExistsE } } + @Override + public void update(IndexConfiguration conf) throws IndexDoesNotExistException, IOException { + + if (conf == null) + throw new IllegalArgumentException("Index configuration must not be null"); + + Map data = conf.toConfigurationMap(); + + if (data.size() == 0) + throw new IllegalArgumentException("Index configuration is empty."); + + if (!this.exists()) + throw new IndexDoesNotExistException("Index does not exist"); + + try { + callAPI(PUT_METHOD, indexUrl, null, data, privatePass); + } catch (HttpCodeException e) { + if (e.getHttpCode() == 204) { + this.refreshMetadata(); + return; + } else { + throw new UnexpectedCodeException(e); + } + } + } + + @Override public void delete() throws IOException, IndexDoesNotExistException { try { @@ -788,10 +857,11 @@ public BatchResults addDocuments(Iterable documents) Boolean added = (Boolean) result.get("added"); addeds.add(i, added); + errors.add(i, null); // populate every index position to avoid IndexOutOfBoundsException below if (!added) { hasErrors = true; - errors.add(i, (String) result.get("error")); + errors.set(i, (String) result.get("error")); } } @@ -1065,13 +1135,17 @@ public boolean exists() throws IOException { } @Override - public boolean hasStarted() throws IOException, - IndexDoesNotExistException { + public boolean hasStarted() throws IOException, IndexDoesNotExistException { refreshMetadata(); return (Boolean) getMetadata().get("started"); } + @Override + public String getStatus() throws IOException, IndexDoesNotExistException { + return (String) getMetadata().get("status"); + } + @Override public String getCode() throws IOException, IndexDoesNotExistException { return (String) getMetadata().get("code"); @@ -1088,6 +1162,11 @@ public Date getCreationTime() throws IOException, } } + @Override + public boolean isPublicSearchEnabled() throws IOException, IndexDoesNotExistException { + return (Boolean)getMetadata().get("public_search"); + } + @Override public void refreshMetadata() throws IOException, IndexDoesNotExistException { @@ -1154,14 +1233,14 @@ public Index getIndex(String indexName) { @Override public Index createIndex(String indexName) throws IOException, IndexAlreadyExistsException, MaximumIndexesExceededException { - return this.createIndex(indexName, false); + return this.createIndex(indexName, null); } @Override - public Index createIndex(String indexName, boolean publicSearch) throws IOException, + public Index createIndex(String indexName, IndexConfiguration conf) throws IOException, IndexAlreadyExistsException, MaximumIndexesExceededException { Index index = getIndex(indexName); - index.create(publicSearch); + index.create(conf); return index; } @@ -1263,4 +1342,14 @@ List get(String key){ } } + + public static void main(String[] args) throws IOException, InvalidSyntaxException { + IndexTankClient client = new IndexTankClient("http://localhost:20220"); + + Index index = client.getIndex("idx"); + + Query query = new Query("the"); + + System.out.println(index.search(query)); + } } diff --git a/src/main/java/com/flaptor/indextank/apiclient/IndexTankMain.java b/src/main/java/com/flaptor/indextank/apiclient/IndexTankMain.java new file mode 100644 index 0000000..ec36c9c --- /dev/null +++ b/src/main/java/com/flaptor/indextank/apiclient/IndexTankMain.java @@ -0,0 +1,220 @@ +package com.flaptor.indextank.apiclient; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.util.Map; +import java.util.Properties; + +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import com.flaptor.indextank.apiclient.IndexTankClient.Index; +import com.flaptor.indextank.apiclient.IndexTankClient.Query; +import com.flaptor.indextank.apiclient.IndexTankClient.SearchResults; + +public class IndexTankMain { + + private static final String INDEX_NAME = "index"; + private static final String APIURL = "apiurl"; + private static final String INDEXTANK_CONFIG_FILENAME = "indextank.config"; + + /** + * @param args + */ + public static void main(String[] args) { + if (args.length > 0) { + if ("config".equals(args[0])) { + if (args.length != 3){ + printUsage(); + System.exit(1); + } + config(args[1], args[2]); + } else if ("search".equals(args[0])) { + if (args.length < 2 || args.length > 4){ + printUsage(); + System.exit(1); + } + String query = args[1]; + int start = args.length > 2 ? Integer.parseInt(args[2]) : 0; + int length = args.length > 3 ? Integer.parseInt(args[3]) : 10; + + search(query, start, length); + + } else if ("index".equals(args[0])) { + try { + if (args.length != 3) { + printUsage(); + System.exit(1); + } + JSONParser parser = new JSONParser(); + Map fields = (Map) parser.parse(args[2]); + index(args[1], fields); + } catch (ParseException e) { + System.err.println("Invalid json map."); + e.printStackTrace(); + System.exit(1); + } + + } else { + printUsage(); + System.exit(1); + } + } else { + printUsage(); + System.exit(1); + } + } + + private static void printUsage() { + System.out.println("Usage options:\n"); + System.out.println("java -jar indextank.jar [config|index|search] options"); + System.out.println("config options: "); + System.out.println("index options: "); + System.out.println("search options: [start] [length]"); + System.out.println(); + System.out.println("fields: a json map with fields for this document"); + System.out.println("start,length: optional parameters"); + } + + private static void config(String apiUrl, String indexName) { + IndexTankClient client = new IndexTankClient(apiUrl); + Index index = client.getIndex(indexName); + + try { + System.out.println("Creating index " + indexName); + if (index.exists()) { + System.out.println("You already have an index called " + indexName + "." + + "\nThis process will delete this index and all your documents will be lost." + + "\nDo you want to continue (Y/n)?"); + char response = (char)System.in.read(); + if (response != 'y' && response != 'Y' && response != '\n') { + System.out.println("Aborting"); + System.exit(1); + } + + index.delete(); + } + + index.create(); + System.out.println("Index created."); + System.out.println("Saving configuration to indextank.config file"); + + File configFile = new File(INDEXTANK_CONFIG_FILENAME); + if (configFile.exists()) + configFile.delete(); + + boolean created = configFile.createNewFile(); + if (!created) { + System.err.println("Couldn't create config file"); + System.exit(1); + } + + Properties prop = new Properties(); + prop.setProperty(APIURL, apiUrl); + prop.setProperty(INDEX_NAME, indexName); + Writer writer = new FileWriter(configFile); + prop.store(writer, "IndexTank configuration. Do not remove this file if you want to try search and index options"); + writer.close(); + + System.out.println("Configuration successfully finished."); + + } catch (IOException e) { + System.err.println("Unexpected exception"); + e.printStackTrace(); + } catch (IndexDoesNotExistException e) { + System.err.println("Unexpected exception"); + e.printStackTrace(); + } catch (IndexAlreadyExistsException e) { + System.err.println("Unexpected exception"); + e.printStackTrace(); + } catch (MaximumIndexesExceededException e) { + System.err.println("Max quantity of index reached. Please delete any index in your account to continue with this configuration."); + } + } + + private static void search(String query, int start, int length) { + + System.out.println("Reading configuration file"); + File configFile = new File(INDEXTANK_CONFIG_FILENAME); + if (!configFile.exists() || !configFile.canRead()) { + System.err.println("File " + INDEXTANK_CONFIG_FILENAME + "doesn't exist or can't be read."); + System.err.println("Aborting"); + System.exit(1); + } + + try { + + Properties prop = new Properties(); + Reader reader = new FileReader(configFile); + prop.load(reader); + reader.close(); + + IndexTankClient client = new IndexTankClient(prop.getProperty(APIURL)); + Index index = client.getIndex(prop.getProperty(INDEX_NAME)); + + if (!index.exists()) { + System.err.println("Index " + prop.getProperty(INDEX_NAME) + " doesn't exist. Please run 'config' option first"); + System.exit(1); + } + + System.out.println("Searching for:\n query: " + query + "\n start:" + start + "\n length:" + length); + SearchResults results = index.search(Query.forString(query).withStart(start).withLength(length)); + System.out.println(results); + + } catch (FileNotFoundException e) { + System.err.println("Unexpected exception"); + e.printStackTrace(); + } catch (IOException e) { + System.err.println("Unexpected exception"); + e.printStackTrace(); + } catch (InvalidSyntaxException e) { + System.err.println("Unexpected exception"); + e.printStackTrace(); + } + } + + private static void index(String docid, Map fields) { + System.out.println("Reading configuration file"); + File configFile = new File(INDEXTANK_CONFIG_FILENAME); + if (!configFile.exists() || !configFile.canRead()) { + System.err.println("File " + INDEXTANK_CONFIG_FILENAME + "doesn't exist or can't be read."); + System.err.println("Aborting"); + System.exit(1); + } + + try { + + Properties prop = new Properties(); + Reader reader = new FileReader(configFile); + prop.load(reader); + reader.close(); + + IndexTankClient client = new IndexTankClient(prop.getProperty(APIURL)); + Index index = client.getIndex(prop.getProperty(INDEX_NAME)); + + if (!index.exists()) { + System.err.println("Index " + prop.getProperty(INDEX_NAME) + " doesn't exist. Please run 'config' option first"); + System.exit(1); + } + + System.out.println("Indexing document:\n id: " + docid + "\n fields:" + fields); + index.addDocument(docid, fields); + System.out.println("Document indexed successfully."); + + } catch (FileNotFoundException e) { + System.err.println("Unexpected exception"); + e.printStackTrace(); + } catch (IOException e) { + System.err.println("Unexpected exception"); + e.printStackTrace(); + } catch (IndexDoesNotExistException e) { + System.err.println("Unexpected exception"); + e.printStackTrace(); + } + } +}