/* See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * Esri Inc. licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.esri.gpt.catalog.lucene; import com.esri.gpt.catalog.discovery.DiscoveryException; import com.esri.gpt.framework.util.Val; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.Fieldable; import org.apache.lucene.search.Query; import org.apache.lucene.search.SortField; import org.apache.lucene.search.TermRangeQuery; /** * Represents a field that is held within the underlying data store. */ public class DatastoreField { /** class variables ========================================================= */ /** The Logger */ private static final Logger LOGGER = Logger.getLogger(DatastoreField.class.getName()); /** instance variables ====================================================== */ private Field.Index indexingOption = Field.Index.ANALYZED; private String name = ""; private Field.Store storeageOption = Field.Store.YES; private Field.TermVector termVectorOption = Field.TermVector.NO; /** constructors ============================================================ */ /** * Constructs with fully supplied parameters. * @param name the field name * @param storageOption the storage option * @param indexingOption the indexing option * @param termVectorOption the term vector option */ protected DatastoreField(String name, Field.Store storageOption, Field.Index indexingOption, Field.TermVector termVectorOption) { setName(name); setStorageOption(storageOption); setIndexingOption(indexingOption); setTermVectorOption(termVectorOption); } /** properties ============================================================== */ /** * Gets the indexing option. * @return the indexing option */ protected Field.Index getIndexingOption() { return indexingOption; } /** * Sets the indexing option. * @param indexingOption the indexing option */ protected void setIndexingOption(Field.Index indexingOption) { this.indexingOption = indexingOption; if (this.indexingOption == null) this.indexingOption = Field.Index.ANALYZED; } /** * Gets the field name. * @return the field name */ protected String getName() { return name; } /** * Sets the field name. * <br/>The name will be trimmed, a null name is treated as an empty string. * @param name the field name */ protected void setName(String name) { this.name = Val.chkStr(name); } /** * Gets the storage option. * @return the storage option */ protected Field.Store getStorageOption() { return storeageOption; } /** * Sets the storage option. * @param storageOption the storage option */ protected void setStorageOption(Field.Store storageOption) { this.storeageOption = storageOption; if (this.storeageOption == null) this.storeageOption = Field.Store.YES; } /** * Gets the term vector option. * @return the term vector option */ protected Field.TermVector getTermVectorOption() { return termVectorOption; } /** * Sets the term vector option. * @param termVectorOption the term vector option */ protected void setTermVectorOption(Field.TermVector termVectorOption) { this.termVectorOption = termVectorOption; if (this.termVectorOption == null) this.termVectorOption = Field.TermVector.NO; } /** methods ================================================================= */ /** * Appends the field to a document prior to writing the document to the index. * <p/> * The field will not be appended if it's name or value is empty. * @param document the Lucene document * @param value the input value to write */ protected void appendForWrite(Document document, Object value) { String sName = getName(); Fieldable fld = null; String sValueInput = null; if (value != null) { sValueInput = value.toString(); fld = this.makeFieldable(value); } if (sName.length() == 0) { LOGGER.fine("The field has not been named and will not be stored."); } else if (fld == null) { LOGGER.log(Level.FINER, "{0} has an empty value and will not be stored.", sName); } else { if (fld != null) { if (LOGGER.isLoggable(Level.FINER)) { String sTmp = fld.stringValue(); if (sTmp.length() > 101) sTmp = sTmp.substring(0,101)+" ..."; StringBuilder sb = new StringBuilder(); sb.append("Appending field:\n "); sb.append(" name=\"").append(fld.name()).append("\""); sb.append(" stored=\"").append(fld.isStored()).append("\""); sb.append(" indexed=\"").append(fld.isIndexed()).append("\""); sb.append(" analyzed=\"").append(fld.isTokenized()).append("\""); sb.append(" termVector=\"").append(fld.isTermVectorStored()).append("\""); sb.append("\n storeValue=\"").append(sTmp).append("\""); if ((sValueInput != null) && !sValueInput.equals(fld.stringValue())) { sTmp = sValueInput; if (sTmp.length() > 101) sTmp = sTmp.substring(0,101)+" ..."; sb.append("\n inputValue=\"").append(sValueInput).append("\""); } LOGGER.finer(sb.toString()); } document.add(fld); } } } /** * Makes the fieldable to index. * <p/> * Sub-classes should override this method if converted values need to * be written for subsequent search (ex. Doubles, Longs, Timestamps ...). * <p/> * The default behavior is to return the supplied value. * <br/>Null or empty values will not be stored. * @param value the value * @return the fieldable */ protected Fieldable makeFieldable(Object value) { if ((value != null) && (value instanceof String)) { String sValue = (String)value; if (sValue.length() > 0) { Field.Store storage = getStorageOption(); Field.Index indexing = getIndexingOption(); Field.TermVector termVector = getTermVectorOption(); return new Field(this.getName(),sValue,storage,indexing,termVector); } } return null; } /** * Makes a range query. * <p/> * Sub-classes should override this method if values need to be converted * for a search (ex. Doubles, Longs, Timestamps, ...). * <p/> * The default behavior is to return a new TermRangeQuery. * @param literalLowerValue the literal lower boundary value * @param literalUpperValue the literal upper boundary value * @param lowerBoundaryIsInclusive (>= versus >) * @param upperBoundaryIsInclusive (<= versus <) * @throws DiscoveryException if the supplied value cannot be converted */ protected Query makeRangeQuery(String literalLowerValue, String literalUpperValue, boolean lowerBoundaryIsInclusive, boolean upperBoundaryIsInclusive) throws DiscoveryException { return new TermRangeQuery(this.getName(), literalLowerValue,literalUpperValue,lowerBoundaryIsInclusive,upperBoundaryIsInclusive); } /** * Makes the value to query. * <p/> * Sub-classes should override this method if values need to be converted * for a search (ex. Doubles, Longs, Timestamps, ...). * <p/> * The default behavior is to return the supplied value. * @param value to input query value * @param isLowerBoundary true if this is a lower boundary of a range query * @param isUpperBoundary true if this is a upper boundary of a range query * @return the value to query * @throws DiscoveryException if the supplied value cannot be converted */ protected String makeValueToQuery(String value, boolean isLowerBoundary, boolean isUpperBoundary) throws DiscoveryException { return value; } /** * Makes the value to return in the query result. * <p/> * The value should be converted if required (example: from a double stored * as an indexable string within the document back to it's original form). * <p/> * The default behavior is to return the supplied value of the field. Sub-classes * should override this method if the value needs to be converted back to a * Double, Long, Timestamp, ... * @param storedValue the value stored within the Lucene document * @return the associated object value */ protected Object makeValueToReturn(String storedValue) { return storedValue; } /** * Returns the sort field type. * <p/> * Sub-classes should override this method if values are numeric * (ex. Doubles, Longs, Timestamps, ...). * @return the sort field type (by default SortField.STRING) */ protected int sortFieldType() { return SortField.STRING; } }