Package org.exoplatform.services.jcr.impl.core.query

Source Code of org.exoplatform.services.jcr.impl.core.query.SearchManager

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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 org.exoplatform.services.jcr.impl.core.query;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.StringTokenizer;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.query.InvalidQueryException;
import javax.jcr.query.Query;

import org.picocontainer.Startable;

import org.exoplatform.services.log.Log;

import org.exoplatform.container.configuration.ConfigurationManager;
import org.exoplatform.services.document.DocumentReaderService;
import org.exoplatform.services.jcr.config.QueryHandlerEntry;
import org.exoplatform.services.jcr.config.QueryHandlerEntryWrapper;
import org.exoplatform.services.jcr.config.RepositoryConfigurationException;
import org.exoplatform.services.jcr.core.nodetype.NodeTypeDataManager;
import org.exoplatform.services.jcr.dataflow.ItemDataConsumer;
import org.exoplatform.services.jcr.dataflow.ItemState;
import org.exoplatform.services.jcr.dataflow.ItemStateChangesLog;
import org.exoplatform.services.jcr.dataflow.persistent.MandatoryItemsPersistenceListener;
import org.exoplatform.services.jcr.datamodel.ItemData;
import org.exoplatform.services.jcr.datamodel.NodeData;
import org.exoplatform.services.jcr.datamodel.QPath;
import org.exoplatform.services.jcr.impl.Constants;
import org.exoplatform.services.jcr.impl.core.NamespaceRegistryImpl;
import org.exoplatform.services.jcr.impl.core.SessionDataManager;
import org.exoplatform.services.jcr.impl.core.SessionImpl;
import org.exoplatform.services.jcr.impl.dataflow.persistent.WorkspacePersistentDataManager;
import org.exoplatform.services.log.ExoLogger;

/**
* Acts as a global entry point to execute queries and index nodes.
*/
public class SearchManager implements Startable, MandatoryItemsPersistenceListener {

  /**
   * Logger instance for this class
   */
  private static final Log              log           = ExoLogger.getLogger(SearchManager.class);

  protected final QueryHandlerEntryWrapper     config;

  /**
   * Text extractor for extracting text content of binary properties.
   */
  protected final DocumentReaderService extractor;

  /**
   * QueryHandler where query execution is delegated to
   */
  protected QueryHandler                handler;

  /**
   * The shared item state manager instance for the workspace.
   */
  protected final ItemDataConsumer      itemMgr;

  /**
   * The namespace registry of the repository.
   */
  protected final NamespaceRegistryImpl nsReg;

  /**
   * The node type registry.
   */
  protected final NodeTypeDataManager   nodeTypeDataManager;

  /**
   * QueryHandler of the parent search manager or <code>null</code> if there is
   * none.
   */
  protected final SearchManager         parentSearchManager;

  protected QPath                       indexingRoot;

  protected List<QPath>                 excludedPaths = new ArrayList<QPath>();

  private final ConfigurationManager    cfm;

  /**
   * Creates a new <code>SearchManager</code>.
   *
   * @param config the search configuration.
   * @param nsReg the namespace registry.
   * @param ntReg the node type registry.
   * @param itemMgr the shared item state manager.
   * @param rootNodeId the id of the root node.
   * @param parentMgr the parent search manager or <code>null</code> if there is
   *          no parent search manager.
   * @param excludedNodeId id of the node that should be excluded from indexing.
   *          Any descendant of that node will also be excluded from indexing.
   * @throws RepositoryException if the search manager cannot be initialized
   * @throws RepositoryConfigurationException
   */
  public SearchManager(QueryHandlerEntry config,
                       NamespaceRegistryImpl nsReg,
                       NodeTypeDataManager ntReg,
                       WorkspacePersistentDataManager itemMgr,
                       SystemSearchManagerHolder parentSearchManager,
                       DocumentReaderService extractor,
                       ConfigurationManager cfm) throws RepositoryException,
      RepositoryConfigurationException {

    this.extractor = extractor;

    this.config = new QueryHandlerEntryWrapper(config);
    this.nodeTypeDataManager = ntReg;
    this.nsReg = nsReg;
    this.itemMgr = itemMgr;
    this.cfm = cfm;

    this.parentSearchManager = parentSearchManager != null ? parentSearchManager.get() : null;
    itemMgr.addItemPersistenceListener(this);
   
    initializeQueryHandler();
  }

  /**
   * Creates a query object from a node that can be executed on the workspace.
   *
   * @param session the session of the user executing the query.
   * @param itemMgr the item manager of the user executing the query. Needed to
   *          return <code>Node</code> instances in the result set.
   * @param node a node of type nt:query.
   * @return a <code>Query</code> instance to execute.
   * @throws InvalidQueryException if <code>absPath</code> is not a valid
   *           persisted query (that is, a node of type nt:query)
   * @throws RepositoryException if any other error occurs.
   */
  public Query createQuery(SessionImpl session, SessionDataManager sessionDataManager, Node node) throws InvalidQueryException,
                                                                                                 RepositoryException {
    AbstractQueryImpl query = handler.createQueryInstance();
    query.init(session, sessionDataManager, handler, node);
    return query;
  }

  /**
   * Creates a query object that can be executed on the workspace.
   *
   * @param session the session of the user executing the query.
   * @param itemMgr the item manager of the user executing the query. Needed to
   *          return <code>Node</code> instances in the result set.
   * @param statement the actual query statement.
   * @param language the syntax of the query statement.
   * @return a <code>Query</code> instance to execute.
   * @throws InvalidQueryException if the query is malformed or the
   *           <code>language</code> is unknown.
   * @throws RepositoryException if any other error occurs.
   */
  public Query createQuery(SessionImpl session,
                           SessionDataManager sessionDataManager,
                           String statement,
                           String language) throws InvalidQueryException, RepositoryException {
    AbstractQueryImpl query = handler.createQueryInstance();
    query.init(session, sessionDataManager, handler, statement, language);
    return query;
  }

  /**
   * just for test use only
   */
  public QueryHandler getHandler() {

    return handler;
  }

  public void onSaveItems(ItemStateChangesLog changesLog) {
    if (handler == null)
      return;

    long time = System.currentTimeMillis();

    // nodes that need to be removed from the index.
    final Set<String> removedNodes = new HashSet<String>();
    // nodes that need to be added to the index.
    final Set<String> addedNodes = new HashSet<String>();

    final Map<String, List<ItemState>> updatedNodes = new HashMap<String, List<ItemState>>();

    for (Iterator<ItemState> iter = changesLog.getAllStates().iterator(); iter.hasNext();) {
      ItemState itemState = iter.next();

      if (!isExcluded(itemState)) {
        String uuid = itemState.isNode() ? itemState.getData().getIdentifier()
                                        : itemState.getData().getParentIdentifier();

        if (itemState.isAdded()) {
          if (itemState.isNode()) {
            addedNodes.add(uuid);
          } else {
            if (!addedNodes.contains(uuid)) {
              createNewOrAdd(uuid, itemState, updatedNodes);
            }
          }
        } else if (itemState.isRenamed()) {
          if (itemState.isNode()) {
            addedNodes.add(uuid);
          } else {
            createNewOrAdd(uuid, itemState, updatedNodes);
          }
        } else if (itemState.isUpdated()) {
          createNewOrAdd(uuid, itemState, updatedNodes);
        } else if (itemState.isMixinChanged()) {
          createNewOrAdd(uuid, itemState, updatedNodes);
        } else if (itemState.isDeleted()) {
          if (itemState.isNode()) {
            if (addedNodes.contains(uuid)) {
              addedNodes.remove(uuid);
              removedNodes.remove(uuid);
            } else {
              removedNodes.add(uuid);
            }
            // remove all changes after node remove
            updatedNodes.remove(uuid);
          } else {
            if (!removedNodes.contains(uuid) && !addedNodes.contains(uuid)) {
              createNewOrAdd(uuid, itemState, updatedNodes);
            }
          }
        }
      }
    }
    // TODO make quick changes
    for (String uuid : updatedNodes.keySet()) {
      removedNodes.add(uuid);
      addedNodes.add(uuid);
    }

    // // property events
    // List<ItemState> propEvents = new ArrayList<ItemState>();
    // List<ItemState> itemStates = changesLog.getAllStates();
    //
    // final Set<String> allRemovedNodesId = new HashSet<String>();
    // final Set<String> allAddedNodesId = new HashSet<String>();
    // for (ItemState itemState : itemStates) {
    // if (!isExcluded(itemState)) {
    // if (itemState.isNode()) {
    // if (itemState.isAdded() || itemState.isRenamed()) {
    // addedNodes.add(itemState.getData().getIdentifier());
    // allAddedNodesId.add(itemState.getData().getIdentifier());
    // } else if (itemState.isDeleted()) {
    // // remove node from add list, and if node not in add list add it to
    // // removed list
    // if (!addedNodes.remove(itemState.getData().getIdentifier()))
    // removedNodes.add(itemState.getData().getIdentifier());
    // allRemovedNodesId.add(itemState.getData().getIdentifier());
    // } else if (itemState.isMixinChanged()) {
    // removedNodes.add(itemState.getData().getIdentifier());
    // addedNodes.add(itemState.getData().getIdentifier());
    // }
    // } else {
    // propEvents.add(itemState);
    // }
    // }
    // }
    //
    // // sort out property events
    // for (int i = 0; i < propEvents.size(); i++) {
    // ItemState event = propEvents.get(i);
    // String nodeId = event.getData().getParentIdentifier();
    // if (event.isAdded()) {
    // if (!addedNodes.contains(nodeId) && !allAddedNodesId.contains(nodeId)) {
    // // only property added
    // // need to re-index
    // addedNodes.add(nodeId);
    // removedNodes.add(nodeId);
    // } else {
    // // the node where this prop belongs to is also new
    // }
    // } else if (event.isRenamed() || event.isUpdated()) {
    // // need to re-index
    // addedNodes.add(nodeId);
    // removedNodes.add(nodeId);
    // } else if (event.isDeleted()) {
    // if (!allRemovedNodesId.contains(nodeId)) {
    // addedNodes.add(nodeId);
    // removedNodes.add(nodeId);
    // }
    // }
    // }

    Iterator<NodeData> addedStates = new Iterator<NodeData>() {
      private final Iterator<String> iter = addedNodes.iterator();

      public boolean hasNext() {
        return iter.hasNext();
      }

      public NodeData next() {

        // cycle till find a next or meet the end of set
        do {
          String id = iter.next();
          try {
            ItemData item = itemMgr.getItemData(id);
            if (item != null) {
              if (item.isNode())
                return (NodeData) item; // return node
              else
                log.warn("Node not found, but property " + id + ", "
                    + item.getQPath().getAsString() + " found. ");
            } else
              log.warn("Unable to index node with id " + id + ", node does not exist.");

          } catch (RepositoryException e) {
            log.error("Can't read next node data " + id, e);
          }
        } while (iter.hasNext()); // get next if error or node not found

        return null; // we met the end of iterator set
      }

      public void remove() {
        throw new UnsupportedOperationException();
      }
    };

    Iterator<String> removedIds = new Iterator<String>() {
      private final Iterator<String> iter = removedNodes.iterator();

      public boolean hasNext() {
        return iter.hasNext();
      }

      public String next() {
        return nextNodeId();
      }

      public String nextNodeId() throws NoSuchElementException {
        return iter.next();
      }

      public void remove() {
        throw new UnsupportedOperationException();

      }
    };

    if (removedNodes.size() > 0 || addedNodes.size() > 0) {
      try {
        handler.updateNodes(removedIds, addedStates);
      } catch (RepositoryException e) {
        log.error("Error indexing changes " + e, e);
      } catch (IOException e) {
        log.error("Error indexing changes " + e, e);
        try {
          handler.logErrorChanges(removedNodes, addedNodes);
        } catch (IOException ioe) {
          log.warn("Exception occure when errorLog writed. Error log is not complete. " + ioe, ioe);
        }
      }
    }

    if (log.isDebugEnabled()) {
      log.debug("onEvent: indexing finished in "
          + String.valueOf(System.currentTimeMillis() - time) + " ms.");
    }
  }

  public void createNewOrAdd(String key, ItemState state, Map<String, List<ItemState>> updatedNodes) {
    List<ItemState> list = updatedNodes.get(key);
    if (list == null) {
      list = new ArrayList<ItemState>();
      updatedNodes.put(key, list);
    }
    list.add(state);

  }

  public void start() {

    if (log.isDebugEnabled())
      log.debug("start");

    // Calculating excluded node identifiers
    excludedPaths.add(Constants.JCR_SYSTEM_PATH);

    if (config.getExcludedNodeIdentifers() != null) {
      StringTokenizer stringTokenizer = new StringTokenizer(config.getExcludedNodeIdentifers());
      while (stringTokenizer.hasMoreTokens()) {

        try {
          ItemData excludeData = itemMgr.getItemData(stringTokenizer.nextToken());
          if (excludeData != null)
            excludedPaths.add(excludeData.getQPath());
        } catch (RepositoryException e) {
          log.warn(e.getLocalizedMessage());
        }
      }
    }

    indexingRoot = Constants.ROOT_PATH;
    if (config.getRootNodeIdentifer() != null) {
      try {
        ItemData indexingRootData = itemMgr.getItemData(config.getRootNodeIdentifer());
        if (indexingRootData != null && indexingRootData.isNode())
          indexingRoot = indexingRootData.getQPath();
      } catch (RepositoryException e) {
        log.warn(e.getLocalizedMessage() + " Indexing root set to " + indexingRoot.getAsString());
      }

    }
    handler.init();
  }

  public void stop() {
    handler.close();
    log.info("Search manager stopped");
  }

  /**
   * Checks if the given event should be excluded based on the
   * {@link #excludePath} setting.
   *
   * @param event observation event
   * @return <code>true</code> if the event should be excluded,
   *         <code>false</code> otherwise
   */
  protected boolean isExcluded(ItemState event) {

    for (QPath excludedPath : excludedPaths) {
      if (event.getData().getQPath().isDescendantOf(excludedPath)
          || event.getData().getQPath().equals(excludedPath))
        return true;
    }

    return !event.getData().getQPath().isDescendantOf(indexingRoot)
        && !event.getData().getQPath().equals(indexingRoot);
  }

  protected QueryHandlerContext createQueryHandlerContext(QueryHandler parentHandler) throws RepositoryConfigurationException {

    QueryHandlerContext context = new QueryHandlerContext(itemMgr,
                                                          config.getRootNodeIdentifer() != null ? config.getRootNodeIdentifer()
                                                                                               : Constants.ROOT_UUID,
                                                          nodeTypeDataManager,
                                                          nsReg,
                                                          parentHandler,
                                                          config.getIndexDir(),
                                                          extractor);
    return context;
  }

  /**
   * Initializes the query handler.
   *
   * @throws RepositoryException if the query handler cannot be initialized.
   * @throws RepositoryConfigurationException
   * @throws ClassNotFoundException
   */
  private void initializeQueryHandler() throws RepositoryException,
                                       RepositoryConfigurationException {
    // initialize query handler
    String className = config.getType();
    if (className == null)
      throw new RepositoryConfigurationException("Content hanler       configuration fail");

    try {
      Class qHandlerClass = Class.forName(className, true, this.getClass().getClassLoader());
      Constructor constuctor = qHandlerClass.getConstructor(QueryHandlerEntry.class, ConfigurationManager.class);
      handler = (QueryHandler) constuctor.newInstance(config.getQueryHandlerEntry(), cfm);
      QueryHandler parentHandler = (this.parentSearchManager != null) ? parentSearchManager.getHandler()
                                                                     : null;
      QueryHandlerContext context = createQueryHandlerContext(parentHandler);
      handler.setContext(context);
    } catch (SecurityException e) {
      throw new RepositoryException(e.getMessage(), e);
    } catch (IllegalArgumentException e) {
      throw new RepositoryException(e.getMessage(), e);
    } catch (ClassNotFoundException e) {
      throw new RepositoryException(e.getMessage(), e);
    } catch (NoSuchMethodException e) {
      throw new RepositoryException(e.getMessage(), e);
    } catch (InstantiationException e) {
      throw new RepositoryException(e.getMessage(), e);
    } catch (IllegalAccessException e) {
      throw new RepositoryException(e.getMessage(), e);
    } catch (InvocationTargetException e) {
      throw new RepositoryException(e.getMessage(), e);
    } catch (IOException e) {
      throw new RepositoryException(e.getMessage(), e);
    }
  }

}
TOP

Related Classes of org.exoplatform.services.jcr.impl.core.query.SearchManager

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.