Package com.esri.gpt.catalog.lucene

Source Code of com.esri.gpt.catalog.lucene.LuceneIndexAdapter

/* 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.context.CatalogConfiguration;
import com.esri.gpt.catalog.context.CatalogIndexAdapter;
import com.esri.gpt.catalog.context.CatalogIndexException;
import com.esri.gpt.catalog.discovery.IStoreable;
import com.esri.gpt.catalog.discovery.PropertyMeanings;
import com.esri.gpt.catalog.management.CollectionDao;
import com.esri.gpt.catalog.schema.Meaning;
import com.esri.gpt.catalog.schema.MetadataDocument;
import com.esri.gpt.catalog.schema.Schema;
import com.esri.gpt.catalog.schema.indexable.Indexables;
import com.esri.gpt.framework.collection.StringAttributeMap;
import com.esri.gpt.framework.collection.StringSet;
import com.esri.gpt.framework.context.ApplicationContext;
import com.esri.gpt.framework.context.RequestContext;
import com.esri.gpt.framework.security.metadata.MetadataAcl;
import com.esri.gpt.framework.util.Val;

import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.MapFieldSelector;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermDocs;
import org.apache.lucene.index.TermEnum;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.store.AlreadyClosedException;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.Lock;
import org.apache.lucene.store.LockObtainFailedException;
import org.apache.lucene.store.NativeFSLockFactory;
import org.apache.lucene.store.SimpleFSLockFactory;

/**
* Maintains a Lucene index of approved metadata documents.
*/
public class LuceneIndexAdapter extends CatalogIndexAdapter {
 
  /** class variables ========================================================= */
 
  private static final String BACKGROUND_LOCKNAME = "geoportal-background.lock";
 
  public static final String BATCH_INDEXWRITER_KEY = "LuceneIndexAdapter.BatchIndexWriter";
 
  /** Logger */
  private static final Logger LOGGER = Logger.getLogger(LuceneIndexAdapter.class.getName());
 
  /** Hold NativeFSLockFactory objects statically within the JVM */
  private static final Map<String,NativeFSLockFactory> NATIVEFSLOCKFACTORIES =
    new HashMap<String,NativeFSLockFactory>();
 
  /** Single referenced searcher. */
  private static ReferencedSearcher REFERENCED_SEARCHER = null;

  /** Hold IndexSearcher objects statically within the JVM */
  private static final Map<String,IndexSearcher> SEARCHERS = new HashMap<String,IndexSearcher>();
 
  /** Single index writer. */
  private static IndexWriter SINGLE_WRITER = null;
  private static boolean SINGLE_WRITER_WASDESTROYED = false;
 
  /** instance variables ====================================================== */
  private boolean      autoCommitSingleWriter = true;
  private LuceneConfig luceneConfig;
  private String       remoteWriterUrl;
  private boolean      useLocalWriter = true;
  private boolean      useRemoteWriter = false;
  private boolean      useSingleSearcher = false;
  private boolean      useSingleWriter = false;
 
  /** constructors ============================================================ */
 
  /**
   * Constructs with an associated request context.
   * @param requestContext the request context
   */
  public LuceneIndexAdapter(RequestContext requestContext) {
    super(requestContext);
    if (requestContext == null) {
      throw new IllegalArgumentException("The requestContext can't be null.");
    }
    this.luceneConfig = requestContext.getCatalogConfiguration().getLuceneConfig();
    if (this.luceneConfig == null) {
      throw new IllegalArgumentException("The requestContext contains no LuceneConfig.");
    } else if (this.luceneConfig.getIndexLocation().length() == 0) {
      throw new IllegalArgumentException("The LuceneConfig indexLocation is empty.");
    }
   
    StringAttributeMap params = requestContext.getCatalogConfiguration().getParameters();
    String param = Val.chkStr(params.getValue("lucene.useSingleSearcher"));
    this.useSingleSearcher = param.equalsIgnoreCase("true");
    param = Val.chkStr(params.getValue("lucene.useSingleWriter"));
    this.useSingleWriter = param.equalsIgnoreCase("true");
    param = Val.chkStr(params.getValue("lucene.useLocalWriter"));
    this.useLocalWriter = !param.equalsIgnoreCase("false");
    param = Val.chkStr(params.getValue("lucene.useRemoteWriter"));
    this.useRemoteWriter = param.equalsIgnoreCase("true");
    this.remoteWriterUrl = Val.chkStr(params.getValue("lucene.remoteWriterUrl"));

 
    // check for a use remote writer override
    Boolean bUseRemoteWriter = (Boolean)requestContext.getObjectMap().get("lucene.useRemoteWriter");
    if (bUseRemoteWriter != null) {
      this.useRemoteWriter = bUseRemoteWriter.booleanValue();
    }   
 
  }
 
  /** properties ============================================================== */
   
 
  /**
   * Indicates if publish or delete operations should auto-commit when a
   * single IndexWriter approach is being used.
   * @return true if publish or delete operations should auto-commit
   */
  public boolean getAutoCommitSingleWriter() {
    return this.autoCommitSingleWriter;
  }
  /**
   * Indicates if publish or delete operations should auto-commit when a
   * single IndexWriter approach is being used.
   * @param autoCommit true if publish or delete operations should auto-commit
   */
  public void setAutoCommitSingleWriter(boolean autoCommit) {
    this.autoCommitSingleWriter = autoCommit;
  }
 
  /**
   * Gets the logger.
   * @return the logger
   */
  @Override
  protected Logger getLogger() {
    return LOGGER;
  }
 
  /**
   * Gets the Lucene configuration.
   * @return the Lucene configuration
   */
  protected LuceneConfig getLuceneConfig() {
    return this.luceneConfig;
  }
 
  /**
   * Gets configured observers.
   * @return observers
   */
  protected LuceneIndexObserverArray getObservers() {
    return this.luceneConfig.getObservers();
  }
 
  /**
   * Indicates whether or not a single IndexWriter approach is being used.
   * @return true if a single IndexWriter approach is being used
   */
  public boolean getUsingSingleWriter() {
    return this.useSingleWriter;
  }
 
  /** methods ================================================================= */
 
  /**
   * Closes an index searcher.
   * @param searcher the searcher to close.
   */
  public void closeSearcher(IndexSearcher searcher) {
    try {
      if ((searcher != null) && !this.useSingleSearcher) {
        getLogger().finer("Closing Lucene IndexSearcher...");
        searcher.getIndexReader().close();
        searcher.close();
      } else if ((searcher != null) && this.useSingleSearcher) {
        getLogger().finer("Releasing Lucene IndexSearcher...");
        REFERENCED_SEARCHER.release(searcher);
      }
    } catch (Throwable t) {
      getLogger().log(Level.WARNING,"IndexSearcher failed to close.",t);
    }
  }
 
  /**
   * Closes an index writer.
   * @param writer the writer to close.
   */
  protected void closeWriter(IndexWriter writer) {
   
    if (this.useSingleWriter) {
      try {
        if (writer != null) {
          getLogger().finer("Committing Lucene IndexWriter...");
          writer.commit();
        }
      } catch (CorruptIndexException e) {
        getLogger().log(Level.SEVERE,"Error on IndexWriter.commit",e);
      } catch (IOException e) {
        getLogger().log(Level.SEVERE,"Error on IndexWriter.commit",e);
      } catch (OutOfMemoryError e) {
        getLogger().log(Level.SEVERE,"Error on IndexWriter.commit",e);
        try {
          writer.close();
        } catch (CorruptIndexException e1) {
          getLogger().log(Level.SEVERE,"Error on IndexWriter.commit",e);
        } catch (IOException e1) {
          getLogger().log(Level.SEVERE,"Error on IndexWriter.commit",e);
        } finally {
          SINGLE_WRITER = null;
        }
      }
      return;
    }
   
    try {
      if (writer != null) {
        getLogger().finer("Closing Lucene IndexWriter...");
        writer.close();
      }
    } catch (Throwable t) {
      getLogger().log(Level.WARNING,"IndexWriter failed to close.",t);
         
      // There are times when closing the IndexWriter fails (typically FileNotFound while flushing).
      // This is a bit of a disaster, if it happens we're unsure if the index is corrupted, it
      // also leaves the write lock unreleased, which basically is terminal when a
      // NativeFSLockFactory is active (you need to stop/start the web server)
      // This is an attempt to forcibly release the lock.
      if ((writer != null) && this.luceneConfig.getUseNativeFSLockFactory()) {
        java.lang.reflect.Field wlFld = null;
        try {
          wlFld = writer.getClass().getDeclaredField("writeLock");
        } catch (Throwable t2) {}  
        if (wlFld != null) {
          boolean wasReleased = false;
          try {
            wlFld.setAccessible(true);
            Object wlObj = wlFld.get(writer);
            if ((wlObj != null) && (wlObj instanceof Lock)) {
              Lock wlLock = (Lock)wlObj;
              wasReleased = !wlLock.isLocked();
              if (!wasReleased) {
                wlLock.release();
                wasReleased = !wlLock.isLocked();
              }
            }
          } catch (Throwable t2) {
            getLogger().log(Level.WARNING,"Unable to forcibly release an abandoned write lock.",t2);
          } finally {
            String sMsg = "The IndexWriter failed to close, write-lock released: "+wasReleased;
            getLogger().warning(sMsg);
          }
        }
      }
     
    }
  }
 
  /**
   * Counts the documents within the index.
   * @throws CatalogIndexException if an exception occurs
   */
  @Override
  public int countDocuments() throws CatalogIndexException {
    IndexSearcher searcher = null;
    try {
      searcher = newSearcher();
      return searcher.maxDoc();
    } catch (Exception e) {
      String sMsg = "Error accessing index:\n "+Val.chkStr(e.getMessage());
      throw new CatalogIndexException(sMsg,e);
    } finally {
      closeSearcher(searcher);
    }
  }
 
  /**
   * Deletes a collection of documents from the index.
   * @param uuids the collection of document UUIDS to delete
   * @throws CatalogIndexException if an exception occurs 
   */
  @Override
  public void deleteDocuments(String[] uuids) throws CatalogIndexException {
   
    if (this.useRemoteWriter) {
      RemoteIndexer remoteIndexer = new RemoteIndexer();
      remoteIndexer.send(this.getRequestContext(),"delete",uuids);
    }
    if (!this.useLocalWriter) return;
   
    IndexWriter writer = null;
    try {
      if ((uuids != null) && (uuids.length > 0)) {
        StringBuilder sbMsg = new StringBuilder("Removing UUIDs from the catalog index:");
        ArrayList<Term> alTerms = new ArrayList<Term>();
        for (String uuid: uuids) {
          String sUuid = Val.chkStr(uuid);
          if (sUuid.length() > 0) {
            alTerms.add(new Term(Storeables.FIELD_UUID,sUuid));
            if (getLogger().isLoggable(Level.FINER)) {
              sbMsg.append("\n ").append(sUuid);
            }
          }
        }
        if (alTerms.size() > 0) {
          if (getLogger().isLoggable(Level.FINER)) {
            getLogger().finer(sbMsg.toString());
          }
          Term[] aTerms = alTerms.toArray(new Term[0]);
          writer = newWriter();
          this.getObservers().onDocumentDelete(uuids);
          writer.deleteDocuments(aTerms);
          this.getObservers().onDocumentDeleted(uuids);
        }
      }
    } catch (Exception e) {
      String sMsg = "Error deleting document(s):\n "+Val.chkStr(e.getMessage());
      throw new CatalogIndexException(sMsg,e);
    } finally {
      if (this.useSingleSearcher) {
        if (this.getAutoCommitSingleWriter()) {
          closeWriter(writer);
        }
      } else {
        closeWriter(writer);
      }
    }
  }
 
  /**
   * Gets the native lock factory if it has been configured for use.
   * @return the native lock factory (null if not configured for use)
   * @throws IOException if an exception occurs
   */
  private synchronized NativeFSLockFactory getNativeLockFactory() throws IOException {
    NativeFSLockFactory factory = null;
    if (this.luceneConfig.getUseNativeFSLockFactory()) {
      File dir = new File(this.luceneConfig.getIndexLocation());
      String path = dir.getCanonicalPath();
      synchronized (NATIVEFSLOCKFACTORIES) {
        factory = NATIVEFSLOCKFACTORIES.get(path);
        if (factory == null) {
          factory = new NativeFSLockFactory(dir);
          NATIVEFSLOCKFACTORIES.put(path,factory);
        }
      }
      //if (NATIVEFSLOCKFACTORY == null) {
      //  File lDir = new File(this.luceneConfig.getIndexLocation());
      //  NATIVEFSLOCKFACTORY = new NativeFSLockFactory(lDir);
      //}
      //return NATIVEFSLOCKFACTORY;
    }
    return factory;
  }
 
  /**
   * Makes an analyzer for catalog documents.
   * <br/>Currently returns a StandardAnalyzer.
   * @return the analyzer
   */
  public Analyzer newAnalyzer() {
    String className = Val.chkStr(this.luceneConfig.getAnalyzerClassName());
    try {
      if ((className.length() == 0) || className.equals("org.apache.lucene.analysis.standard.StandardAnalyzer")) {
        return new StandardAnalyzer(org.apache.lucene.util.Version.LUCENE_30);
      } else {
        Class<?> cls = Class.forName(className);
        Object obj = cls.newInstance();
        if (obj instanceof Analyzer) {
          return (Analyzer)obj;
        } else {
          String sMsg = "The configured Lucene analyzer class name is invalid: "+className;
          getLogger().severe(sMsg);
        }
      }
    } catch (Throwable t) {
      String sMsg = "Error instantiating Lucene analyzer: "+className;
      getLogger().log(Level.SEVERE,sMsg,t);
    }
    return new StandardAnalyzer(org.apache.lucene.util.Version.LUCENE_30);
  }
 
  /**
   * Gets the Lucene Directory the has been configured to store the index.
   * @return the directory
   * @throws IOException if an exception occurs
   */
  private Directory newDirectory() throws IOException {
    File fDir = new File(this.luceneConfig.getIndexLocation());
    NativeFSLockFactory nativeLockFactory = this.getNativeLockFactory();
    if (nativeLockFactory != null) {
      return FSDirectory.open(fDir,nativeLockFactory);
    } else {
      return FSDirectory.open(fDir);
    }
  }
 
  /**
   * Makes a searcher for catalog documents.
   * <p/>The searcher is created from the value returned by
   * getCatalogIndexPath().
   * @return the writer
   * @throws CorruptIndexException if the index is corrupt
   * @throws IOException if the directory cannot be read/written to,
   *         or if it does not exist and create is false or if there is any
   *         other low-level IO error
   */
  public synchronized IndexSearcher newSearcher()
    throws CorruptIndexException, IOException {

    IndexSearcher searcher = null;
    if (!this.useSingleSearcher) {
      getLogger().finer("Opening Lucene IndexSearcher...");
      IndexReader reader = IndexReader.open(this.newDirectory(),true);
      searcher = new IndexSearcher(reader);
    } else {
     
      if (REFERENCED_SEARCHER == null) {
        REFERENCED_SEARCHER = new ReferencedSearcher(this.newDirectory());
        return REFERENCED_SEARCHER.get();
      } else {
        try {
          REFERENCED_SEARCHER.checkForReopen();
        } catch (InterruptedException e) {
          throw new IOException("Interrupted while opening single searcher.",e);
        }
        return REFERENCED_SEARCHER.get();
      }
   
      /*
      File fDir = new File(this.luceneConfig.getIndexLocation());
      String path = fDir.getCanonicalPath();
      synchronized (SEARCHERS) {
        searcher = SEARCHERS.get(path);
        if (searcher != null) {
          try {
            if (!searcher.getIndexReader().isCurrent()) {
              SEARCHERS.remove(path);
              searcher.getIndexReader().close();
              searcher.close();
              searcher = null;
            }
          } catch (AlreadyClosedException e) {
            SEARCHERS.remove(path);
            searcher = null;
          }
        }
        if (searcher == null) {
          IndexReader reader = IndexReader.open(this.newDirectory(),true);
          searcher = new IndexSearcher(reader);
          SEARCHERS.put(path,searcher);
        }
      }
      */
     
     
     
    }
    return searcher;
  }
   
  /**
   * Makes a writer for catalog documents.
   * <p/>The writer is created from the values returned by
   * getCatalogIndexPath() and newCatalogAnalyzer().
   * <br/>The index will be creating it if it does not already exist.
   * <br/>
   * The writer must be closed after use.
   * @return the writer
   * @throws CorruptIndexException if the index is corrupt
   * @throws LockObtainFailedException if another writer has this index
   *         open (write.lock could not be obtained)
   * @throws IOException if the directory cannot be read/written to,
   *         or if it does not exist and create is false or if there is any
   *         other low-level IO error
   */
  protected IndexWriter newWriter()
    throws CorruptIndexException, LockObtainFailedException, IOException {
    return newWriter(false);
  }
 
  /**
   * Makes a writer for catalog documents.
   * <p/>The writer is created from the values returned by
   * getCatalogIndexPath() and newCatalogAnalyzer().
   * <br/>The index will be creating it if it does not already exist.
   * <br/>
   * The writer must be closed after use.
   * @param forCompleteRebuild true if index will be completely rebuilt
   * @return the writer
   * @throws CorruptIndexException if the index is corrupt
   * @throws LockObtainFailedException if another writer has this index
   *         open (write.lock could not be obtained)
   * @throws IOException if the directory cannot be read/written to,
   *         or if it does not exist and create is false or if there is any
   *         other low-level IO error
   */
  private IndexWriter newWriter(boolean forCompleteRebuild)
    throws CorruptIndexException, LockObtainFailedException, IOException {
   
    if (!this.useLocalWriter) {
      throw new IOException("This instance is not using a local writer.");
    }
    if (this.useSingleWriter) return this.getSingleWriter(forCompleteRebuild);
   
    File f = new File(this.luceneConfig.getIndexLocation());
    getLogger().log(Level.FINER, "Creating Lucene IndexWriter for: {0}", f.getAbsolutePath());
    IndexWriter.MaxFieldLength mfl = IndexWriter.MaxFieldLength.UNLIMITED;
    if (!forCompleteRebuild) {
      return new IndexWriter(this.newDirectory(),this.newAnalyzer(),mfl);
    } else {
      return new IndexWriter(this.newDirectory(),this.newAnalyzer(),true,mfl);
    }
  }
 
  /**
   * Gets the single writer instance.
   * @param forCompleteRebuild true if index will be completely rebuilt
   * @return the writer
   * @throws CorruptIndexException if the index is corrupt
   * @throws LockObtainFailedException if another writer has this index
   *         open (write.lock could not be obtained)
   * @throws IOException if the directory cannot be read/written to,
   *         or if it does not exist and create is false or if there is any
   *         other low-level IO error
   */
  private synchronized IndexWriter getSingleWriter(boolean forCompleteRebuild)
    throws CorruptIndexException, LockObtainFailedException, IOException {
    if (SINGLE_WRITER_WASDESTROYED) {
      throw new IOException("The single IndexWriter instance was destroyed.");
    }
    if (SINGLE_WRITER != null) return SINGLE_WRITER;
   
    File f = new File(this.luceneConfig.getIndexLocation());
    getLogger().log(Level.FINER, "Creating Lucene IndexWriter for: {0}", f.getAbsolutePath());
    IndexWriter.MaxFieldLength mfl = IndexWriter.MaxFieldLength.UNLIMITED;
    Directory directory = this.newDirectory();
   
    if (IndexWriter.isLocked(directory)) {
      IndexWriter.unlock(directory);
    }
   
    if (!forCompleteRebuild) {
      IndexWriter tmp = new IndexWriter(directory,this.newAnalyzer(),mfl);
      SINGLE_WRITER = tmp;
    } else {
      IndexWriter tmp =  new IndexWriter(directory,this.newAnalyzer(),true,mfl);
      SINGLE_WRITER = tmp;
    }
    return SINGLE_WRITER;
  }
 
  /**
   * Obtains a write lock intended for background synchronization and
   * optimization processes.
   * <br/>There will be no wait time for this type of lock, if not obtained
   * immediately an exception will be thrown.
   * <br/>It is recommended to use LuceneIndexAdapter.touck() prior to
   * obtaining the background lock to ensure a proper directory structure.
   * <br/>The lock must be closed when the background process is complete.
   * @return the obtained lock
   * @throws LockObtainFailedException if the lock wwas not obtained
   */
  public synchronized Lock obtainBackgroundLock() throws LockObtainFailedException {
    Lock lock = null;
    String pfx = "Unable to obtain background lock.";
    if (this.luceneConfig.getUseNativeFSLockFactory()) {
      try {
        NativeFSLockFactory nativeLockFactory = this.getNativeLockFactory();
        lock = nativeLockFactory.makeLock(BACKGROUND_LOCKNAME);
      } catch (IOException e) {
        String msg = pfx+" "+e.getMessage();
        getLogger().log(Level.WARNING,pfx,e);
        throw new LockObtainFailedException(msg);
      }
    } else {
      try {
        File fDir = new File(this.luceneConfig.getIndexLocation());
        SimpleFSLockFactory factory = new SimpleFSLockFactory(fDir);
        factory.setLockPrefix("lucene-simple");
        lock = factory.makeLock(BACKGROUND_LOCKNAME);
      } catch (IOException e) {
        String msg = pfx+" "+e.getMessage();
        getLogger().log(Level.WARNING,pfx,e);
        throw new LockObtainFailedException(msg);
      }
    }
    try {
      boolean wasObtained = lock.obtain();
      if (!wasObtained) {
        String msg = "Unable to obtain background lock for: "+lock.toString();
        throw new LockObtainFailedException(msg);
      }
      return lock;
    } catch (LockObtainFailedException e) {
      throw e;
    } catch (IOException e) {
      String msg = pfx+" "+e.getMessage();
      getLogger().log(Level.WARNING,pfx,e);
      throw new LockObtainFailedException(msg);
    }
  }

  /**
   * Fired when the servlet context is shutting down.
   * @param context the application context
   */
  public static synchronized void onContextDestroy(ApplicationContext context) {
    SINGLE_WRITER_WASDESTROYED = true;
   
    if (SINGLE_WRITER != null) {
      try {
        IndexWriter tmp = SINGLE_WRITER;
        SINGLE_WRITER = null;
        if (tmp != null) {
          tmp.close();
        }
      } catch (Exception e) {
        LOGGER.log(Level.SEVERE,"Error while closing single IndexWriter on destroy.",e);
      }
    }
   
    if (REFERENCED_SEARCHER != null) {
      try {
        REFERENCED_SEARCHER.close();
      } catch (Exception e) {
        LOGGER.log(Level.SEVERE,"Error while closing single IndexReader on destroy.",e);
      }
    }
   
  }
 
  /**
   * Fired when the servlet context is starting up.
   * @param context the application context
   */
  public static synchronized void onContextInit(ApplicationContext context) {
    StringAttributeMap params = context.getConfiguration().getCatalogConfiguration().getParameters();
    String param = Val.chkStr(params.getValue("lucene.useLocalWriter"));
    boolean bUseLocalWriter = !param.equalsIgnoreCase("false");
   
    if (bUseLocalWriter) {
      RequestContext reqCtx = null;
      try {
        reqCtx = RequestContext.extract(null);
        LuceneIndexAdapter adapter = new LuceneIndexAdapter(reqCtx);
        adapter.touch();
      } catch (Exception e) {
        LOGGER.log(Level.SEVERE,"Error while touching IndexWriter on init.",e);
      } finally {
        if (reqCtx != null) reqCtx.onExecutionPhaseCompleted();
      }
    }
  }
   
  @Override
  public void publishDocument(String uuid,
                              Timestamp updateDate,
                              Schema schema,
                              String acl)
    throws CatalogIndexException {
    uuid = Val.chkStr(uuid);
    if (uuid.length() == 0) {
      throw new IllegalArgumentException("The supplied document UUID was empty.");
    }
   
    if (this.useRemoteWriter) {
      RemoteIndexer remoteIndexer = new RemoteIndexer();
      remoteIndexer.send(this.getRequestContext(),"publish",uuid);
    }
    if (!this.useLocalWriter) return;
   
    IndexWriter writer = null;
    PreparedStatement st = null;
    PreparedStatement stCol = null;
    try {
     
      // determine if the XML should always be stored within the index
      StringAttributeMap params  = this.getRequestContext().getCatalogConfiguration().getParameters();
      String s = Val.chkStr(params.getValue("lucene.alwaysStoreXmlInIndex"));
      boolean alwaysStoreXmlInIndex = !s.equalsIgnoreCase("false");
     
      // determine if collections are being used
      List<String[]> collections = null;
      CollectionDao colDao = new CollectionDao(this.getRequestContext());
      boolean hasCollections = false;
      boolean useCollections = colDao.getUseCollections();
      String sColMemberTable = colDao.getCollectionMemberTableName();
      String sqlCol = "SELECT COLUUID FROM "+sColMemberTable+" WHERE DOCUUID=?";
      if (useCollections) {
        collections = colDao.queryCollections();
        hasCollections = (collections.size() > 0);
      }
     
      // determine the storeables
      Document document = new Document();   
      Storeables storeables = null;
      PropertyMeanings meanings = null;
      Indexables indexables = schema.getIndexables();
      if ((indexables != null) && (indexables.getIndexableContext() != null)) {
        meanings = indexables.getIndexableContext().getPropertyMeanings();
        storeables = (Storeables)indexables.getIndexableContext().getStoreables();
      }
      if (storeables == null) {
        useCollections = false;
        meanings= schema.getMeaning().getPropertyMeanings();
        storeables = (Storeables)schema.getMeaning().getStoreables();
      }
     
      // resolve the thumbnail URL         
      if (Val.chkStr(schema.getMeaning().getThumbnailUrl()).length() == 0) {
        String thumbBinary = Val.chkStr(schema.getMeaning().getThumbnailBinary());
        if ((thumbBinary != null) && (thumbBinary.length() > 0)) {
          String thumbUrl = "/thumbnail?uuid="+URLEncoder.encode(uuid,"UTF-8");
          //IStoreable storeable = schema.getMeaning().getStoreables().get(Meaning.MEANINGTYPE_THUMBNAIL_URL);
          IStoreable storeable = storeables.get(Meaning.MEANINGTYPE_THUMBNAIL_URL);
          if (storeable != null) {
            storeable.setValue(thumbUrl);
          } else {
            storeables.ensure(meanings,Meaning.MEANINGTYPE_THUMBNAIL_URL).setValue(thumbUrl);
          }
        }
      }
       
      // build the ACL property for the document
      acl = Val.chkStr(acl);
      MetadataAcl oAcl = new MetadataAcl(this.getRequestContext());
      String[] aclValues = oAcl.makeDocumentAcl(acl);
      AclProperty aclProp = new AclProperty(Storeables.FIELD_ACL);
      aclProp.setValues(aclValues);
     
      // build the document to store
      storeables.ensure(meanings,Storeables.FIELD_UUID).setValue(uuid);
      storeables.ensure(meanings,Storeables.FIELD_DATEMODIFIED).setValue(updateDate);
      storeables.add(aclProp);
     
      String fldName = null;
      Field fld = null;
     
      // document XML
      String xml = Val.chkStr(schema.getActiveDocumentXml());
      String testBrief = Val.chkStr(schema.getCswBriefXslt());
      if (alwaysStoreXmlInIndex || (testBrief.length() > 0)) {
        fldName = Storeables.FIELD_XML;
        LOGGER.log(Level.FINER, "Appending field: {0}", fldName);
        fld = new Field(fldName,xml,Field.Store.YES,Field.Index.NO,Field.TermVector.NO);
        document.add(fld);
      }
     
      // add additional indexable fields based upon the SQL database record
      boolean bReadDB = true;
      if (bReadDB) {
        CatalogConfiguration cfg = this.getRequestContext().getCatalogConfiguration();
        this.getRequestContext().getCatalogConfiguration().getResourceTableName();
        String sql = "SELECT SITEUUID, TITLE FROM "+cfg.getResourceTableName()+" WHERE DOCUUID=?";
        Connection con = this.returnConnection().getJdbcConnection();
        this.logExpression(sql);
        st = con.prepareStatement(sql);
        st.setString(1,uuid);
        ResultSet rs = st.executeQuery();
        if (rs.next()) {
         
          String dbVal = Val.chkStr(rs.getString("SITEUUID"));
          if (dbVal.length() > 0) {
            //storeables.ensure(meanings,Storeables.FIELD_SITEUUID).setValue(dbVal);
            fldName = Storeables.FIELD_SITEUUID;
            LOGGER.log(Level.FINER, "Appending field: {0} ={1}", new Object[]{fldName, dbVal});
            fld = new Field(fldName,dbVal,Field.Store.YES,Field.Index.NOT_ANALYZED,Field.TermVector.NO);
            document.add(fld);
          }

          dbVal = Val.chkStr(rs.getString("TITLE"));
          if (dbVal.length() > 0) {
            // if the title is found and is different than that in the database
            // it means that title from the database is typed by the user. In
            // that case make 'title.org' element based on the current title.
            IStoreable iTitle = storeables.get(Meaning.MEANINGTYPE_TITLE);
            if (iTitle!=null) {
              Object [] values = iTitle.getValues();
              if (values.length>0 && values[0] instanceof String) {
                String val = (String)values[0];
                if (!val.equals(dbVal)) {
                  storeables.ensure(meanings,Meaning.MEANINGTYPE_TITLE_ORG).setValue(val);
                }
              }
            }
            // ensure the title from the database
            storeables.ensure(meanings,Meaning.MEANINGTYPE_TITLE).setValue(dbVal);
          }
         
        }
        st.close();
        st = null;
       
        // determine collection membership
        if (useCollections && hasCollections) {
          ArrayList<String> alCol = new ArrayList<String>();
          stCol = con.prepareStatement(sqlCol);
          stCol.setString(1,uuid);
          ResultSet rsCol = stCol.executeQuery();
          while (rsCol.next()) {
            String sCUuid = rsCol.getString(1);
            for (String[] col: collections) {
              if (sCUuid.equals(col[0])) { 
                alCol.add(col[1]);
                break;
              }
            }
          }
          stCol.close();
          stCol = null;
          if (alCol.size() > 0) {
            fldName = "isPartOf";
            Storeable storeable = (Storeable)storeables.ensure(meanings,fldName);
            if (storeable == null) {
              // TODO: add a warning message to the log
            } else {
              indexables.getIndexableContext().addStorableValues(
                meanings.get(fldName),alCol.toArray(new String[0]));
            }
          }
        }
      }

      for (IStoreable ist: storeables.collection()) {
        Storeable storeable = (Storeable)ist;
        storeable.appendForWrite(document);
      }
     
      // schema key
      String schemaKey = Val.chkStr(schema.getKey());
      if (schemaKey.length() > 0) {
        fldName = Storeables.FIELD_SCHEMA_KEY;
        LOGGER.log(Level.FINER, "Appending field: {0} ={1}", new Object[]{fldName,schemaKey});
        fld = new Field(fldName,schemaKey,Field.Store.YES,Field.Index.NOT_ANALYZED,Field.TermVector.NO);
        document.add(fld);
      }
     
      // cswOutputSchema, cswBriefXml, cswSummaryXml
      String cswOutputSchema = Val.chkStr(schema.getCswOutputSchema());
      if (cswOutputSchema.length() > 0) {
        fldName = Storeables.FIELD_SCHEMA;
        LOGGER.log(Level.FINER, "Appending field: {0} ={1}", new Object[]{fldName, cswOutputSchema});
        fld = new Field(fldName,cswOutputSchema,Field.Store.YES,Field.Index.NOT_ANALYZED,Field.TermVector.NO);
        document.add(fld);
      }
      String briefXslt = Val.chkStr(schema.getCswBriefXslt());
      if (briefXslt.length() > 0) {
        MetadataDocument mdDoc = new MetadataDocument();
        String briefXml = mdDoc.transform(xml,briefXslt);
        fldName = Storeables.FIELD_XML_BRIEF;
        LOGGER.log(Level.FINER, "Appending field: {0}", fldName);
        fld = new Field(fldName,briefXml,Field.Store.YES,Field.Index.NO,Field.TermVector.NO);
        document.add(fld);
      }
      String summaryXslt = Val.chkStr(schema.getCswSummaryXslt());
      if (summaryXslt.length() > 0) {
        MetadataDocument mdDoc = new MetadataDocument();
        String summaryXml = mdDoc.transform(xml,summaryXslt);
        fldName = Storeables.FIELD_XML_SUMMARY;
        LOGGER.log(Level.FINER, "Appending field: {0}", fldName);
        fld = new Field(fldName,summaryXml,Field.Store.YES,Field.Index.NO,Field.TermVector.NO);
        document.add(fld);
      }
     
      // check for to see if a batch IndexWriter has been placed within the RequestContext objectMap,
      // this is useful for batch processes where open/close/optimize on the index is costly
      IndexWriter batchWriter = null;
      Object o = this.getRequestContext().getObjectMap().get(LuceneIndexAdapter.BATCH_INDEXWRITER_KEY);
      if (o != null && (o instanceof IndexWriter)) {
        batchWriter = (IndexWriter)o;
      }
     
      // write the document (use update to replace an existing document),
      Term term = new Term(Storeables.FIELD_UUID,uuid);
      this.getObservers().onDocumentUpdate(document,uuid);
      if (batchWriter != null) {
        batchWriter.updateDocument(term,document);
      } else {
        writer = newWriter();
        writer.updateDocument(term,document);
      }
      this.getObservers().onDocumentUpdated(document,uuid);
     
    } catch (Exception e) {
      String sMsg = "Error indexing document:\n "+Val.chkStr(e.getMessage());
      throw new CatalogIndexException(sMsg,e);
    } finally {
      try {if (st != null) st.close();} catch (Exception ef) {}
      try {if (stCol != null) stCol.close();} catch (Exception ef) {}
      if (this.useSingleSearcher) {
        if (this.getAutoCommitSingleWriter()) {
          closeWriter(writer);
        }
      } else {
        closeWriter(writer);
      }
    }
  }
 
  /**
   * Purges the entire catalog index.
   * @throws CatalogIndexException if an exception occurs
   */
  @Override
  public void purgeIndex() throws CatalogIndexException {
    if (!this.useLocalWriter) return;
    IndexWriter writer = null;
    try {
      getLogger().info("Emptying Lucene index...");
      writer = newWriter(true);
    } catch (Exception e) {
      String sMsg = "Error purging index:\n "+Val.chkStr(e.getMessage());
      throw new CatalogIndexException(sMsg,e);
    } finally {
      closeWriter(writer);
    }
  }
  
  /**
   * Queries the ACL values indexed for a document.
   * @param uuid the document UUID
   * @return the ACL values (can be null)
   * @throws CatalogIndexException if an exception occurs
   */
  @Override
  public String[] queryAcls(String uuidthrows CatalogIndexException {
    ArrayList<String> values = new ArrayList<String>();
    IndexSearcher searcher = null;
    TermDocs termDocs = null;
    try {
      uuid = Val.chkStr(uuid);
      if (uuid.length() > 0) {
        searcher = newSearcher();
        String[] aFields = new String[]{Storeables.FIELD_ACL};
        MapFieldSelector selector = new MapFieldSelector(aFields);
        searcher = newSearcher();
        IndexReader reader = searcher.getIndexReader();
        termDocs = reader.termDocs();
        termDocs.seek(new Term(Storeables.FIELD_UUID,uuid));
        if (termDocs.next()) {
          Document document = reader.document(termDocs.doc(),selector);
          Field[] fields = document.getFields(Storeables.FIELD_ACL);
          if ((fields != null) && (fields.length > 0)) {
            for (Field field: fields) {
              values.add(field.stringValue());
            }
          }
        }
      }
    } catch (IOException e) {
      String sMsg = "Error accessing index:\n "+Val.chkStr(e.getMessage());
      throw new CatalogIndexException(sMsg,e);
    } finally {
      try {if (termDocs != null) termDocs.close();} catch (Exception ef) {}
      closeSearcher(searcher);
    }
    return values.toArray(new String[0]);
  }
 
  /**
   * Queries the system modified date associated with an indexed document.
   * @param uuid the document UUID
   * @return the update date (null if none was found)
   * @throws CatalogIndexException if an exception occurs
   */
  @Override
  public Timestamp queryModifiedDate(String uuid) throws CatalogIndexException {
    Timestamp tsUpdate = null;
    IndexSearcher searcher = null;
    TermDocs termDocs = null;
    try {
      uuid = Val.chkStr(uuid);
      if (uuid.length() > 0) {
        String[] aFields = new String[]{Storeables.FIELD_DATEMODIFIED};
        MapFieldSelector selector = new MapFieldSelector(aFields);
        searcher = newSearcher();
        IndexReader reader = searcher.getIndexReader();
        termDocs = reader.termDocs();
        termDocs.seek(new Term(Storeables.FIELD_UUID,uuid));
        if (termDocs.next()) {
          Document document = reader.document(termDocs.doc(),selector);
          String sUpdate = document.get(Storeables.FIELD_DATEMODIFIED);
          tsUpdate = new Timestamp(Long.valueOf(sUpdate));
        }
      }
    } catch (IOException e) {
      String sMsg = "Error accessing index:\n "+Val.chkStr(e.getMessage());
      throw new CatalogIndexException(sMsg,e);
    } finally {
      try {if (termDocs != null) termDocs.close();} catch (Exception ef) {}
      closeSearcher(searcher);
    }
    return tsUpdate;
  }
 
  /**
   * Reads the document UUIDs within the index.
   * @param maxUuids the maximum number to read
   * @param startIndex the index to begin reading
   * @return the set of UUIDs
   * @throws CatalogIndexException if an exception occurs
   */
  private StringSet readUuids(int startIndex, int maxUuids) throws CatalogIndexException {
    StringSet ssUuids = new StringSet();
    IndexSearcher searcher = null;
    TermEnum terms = null;
    try {
      String sField = Storeables.FIELD_UUID;
      searcher = newSearcher();
      terms = searcher.getIndexReader().terms(new Term(sField,""));
      int nCount = 0;
      while (sField.equals(terms.term().field())) {       
        if(nCount >= startIndex){
         ssUuids.add(terms.term().text());
        }
        nCount++;
        if (nCount >= (startIndex + maxUuids)) break;
        if (!terms.next()) break;
      }
     
    } catch (Exception e) {
      String sMsg = "Error accessing index:\n "+Val.chkStr(e.getMessage());
      throw new CatalogIndexException(sMsg,e);
    } finally {
      try {if (terms != null) terms.close();} catch (Exception ef) {}
      closeSearcher(searcher);
    }
    return ssUuids;
  }
 
  /**
   * Opens and closes a writer.
   * <br/>This ensures that a directory folder structure exists.
   * @throws CatalogIndexException if an exception occurs
   */
  public void touch() throws CatalogIndexException {
    if (!this.useLocalWriter) return;
    IndexWriter writer = null;
    try {
      writer = newWriter();
    } catch (Exception e) {
      String sMsg = "Error accessing index:\n "+Val.chkStr(e.getMessage());
      throw new CatalogIndexException(sMsg,e);
    } finally {
      closeWriter(writer);
    }
  }

}
TOP

Related Classes of com.esri.gpt.catalog.lucene.LuceneIndexAdapter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.