Package ch.entwine.weblounge.contentrepository.impl.index

Source Code of ch.entwine.weblounge.contentrepository.impl.index.ContentRepositoryIndex

/*
*  Weblounge: Web Content Management System
*  Copyright (c) 2003 - 2011 The Weblounge Team
*  http://entwinemedia.com/weblounge
*
*  This program is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public License
*  as published by the Free Software Foundation; either version 2
*  of the License, or (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public License
*  along with this program; if not, write to the Free Software Foundation
*  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package ch.entwine.weblounge.contentrepository.impl.index;

import static ch.entwine.weblounge.search.impl.IndexSchema.PATH;
import static ch.entwine.weblounge.search.impl.IndexSchema.RESOURCE_ID;
import static ch.entwine.weblounge.search.impl.IndexSchema.TYPE;
import static ch.entwine.weblounge.search.impl.IndexSchema.VERSION;

import ch.entwine.weblounge.common.content.Resource;
import ch.entwine.weblounge.common.content.ResourceSearchResultItem;
import ch.entwine.weblounge.common.content.ResourceURI;
import ch.entwine.weblounge.common.content.SearchQuery;
import ch.entwine.weblounge.common.content.SearchResultItem;
import ch.entwine.weblounge.common.impl.content.ResourceURIImpl;
import ch.entwine.weblounge.common.impl.content.SearchQueryImpl;
import ch.entwine.weblounge.common.repository.ContentRepositoryException;
import ch.entwine.weblounge.common.search.SearchIndex;
import ch.entwine.weblounge.common.site.Site;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Iterator;
import java.util.UUID;

/**
* This index into the repository is used to map resource and resource urls into
* repository indices and vice versa. In addition, it will facilitate listing
* url hierarchies.
*/
public class ContentRepositoryIndex {

  /** Logging facility */
  private static final Logger logger = LoggerFactory.getLogger(ContentRepositoryIndex.class);

  /** The search index */
  protected SearchIndex searchIdx = null;

  /** The site */
  protected Site site = null;

  /**
   * Creates a new index that is located in the indicated folder.
   *
   * @param site
   *          the site
   * @param searchIndex
   *          the search index
   * @throws IOException
   *           if creating the indices fails
   * @throws IllegalArgumentException
   *           if one of the parameters is null
   */
  public ContentRepositoryIndex(Site site, SearchIndex searchIndex)
      throws IOException, IllegalArgumentException {
    if (site == null)
      throw new IllegalArgumentException("Site must not be null");
    if (searchIndex == null)
      throw new IllegalArgumentException("Search index must not be null");
   
    this.site = site;
    this.searchIdx = searchIndex;
  }

  /**
   * Returns the index version or <code>-1</code> if the versions differ.
   *
   * @return the index version
   */
  public int getIndexVersion() {
    int version = searchIdx.getIndexVersion();
    return version;
  }

  /**
   * Closes the index files. No more read and write operations are allowed.
   *
   * @throws IOException
   *           if closing the index fails
   */
  public void close() throws IOException {
    if (searchIdx != null)
      searchIdx = null;
  }

  /**
   * Returns the number of resources in this index.
   *
   * @return the number of resources
   * @throws ContentRepositoryException
   *           if querying for the resource count fails
   */
  public long getResourceCount() throws ContentRepositoryException {
    SearchQuery q = new SearchQueryImpl(site).withPreferredVersion(Resource.LIVE).withField(RESOURCE_ID);
    return searchIdx.getByQuery(q).getHitCount();
  }

  /**
   * Returns the number of revisions in this index.
   *
   * @return the number of revisions
   * @throws ContentRepositoryException
   *           if querying for the resource count fails
   */
  public long getRevisionCount() throws ContentRepositoryException {
    SearchQuery q = new SearchQueryImpl(site).withField(RESOURCE_ID);
    return searchIdx.getByQuery(q).getHitCount();
  }

  /**
   * Adds all relevant entries for the given resource uri to the index and
   * returns it, probably providing a newly created uri identifier.
   *
   * @param url
   *          the resource uri
   * @return the uri
   * @throws IOException
   *           if writing to the index fails
   * @throws ContentRepositoryException
   *           if adding to the index fails
   */
  public synchronized ResourceURI add(Resource<?> resource) throws IOException,
      ContentRepositoryException {

    ResourceURI uri = resource.getURI();
    String id = uri.getIdentifier();
    String path = StringUtils.trimToNull(uri.getPath());
    long version = uri.getVersion();

    // Make sure we are not asked to add a resource to the index that has the
    // same id as an existing one
    if (id != null) {
      SearchQuery q = new SearchQueryImpl(site).withIdentifier(id).withPreferredVersion(version).withLimit(1).withField(PATH);
      SearchResultItem[] items = searchIdx.getByQuery(q).getItems();
      if (items.length > 0) {
        long versionInIndex = (Long) ((ResourceSearchResultItem) items[0]).getMetadataByKey(VERSION).getValue();
        if (items.length == 1 && versionInIndex == version)
          throw new ContentRepositoryException("Resource '" + id + "' already exists in version " + version);
        if (path == null) {
          path = (String) ((ResourceSearchResultItem) items[0]).getMetadataByKey(PATH).getValue();
          resource.getURI().setPath(path);
        }
      }
    }

    // Make sure we are not asked to add a resource to the index that has the
    // same path as an existing one
    if (path != null) {
      SearchQuery q = new SearchQueryImpl(site).withPath(path).withPreferredVersion(version).withLimit(1).withField(RESOURCE_ID);
      SearchResultItem[] items = searchIdx.getByQuery(q).getItems();
      if (items.length > 0) {
        long versionInIndex = (Long) ((ResourceSearchResultItem) items[0]).getMetadataByKey(VERSION).getValue();
        if (items.length == 1 && versionInIndex == version)
          throw new ContentRepositoryException("Resource '" + id + "' already exists in version " + version);
        if (id == null) {
          id = (String) ((ResourceSearchResultItem) items[0]).getMetadataByKey(RESOURCE_ID).getValue();
          resource.getURI().setIdentifier(id);
        }
      }
    }

    // Create an id if necessary. A missing id indicates that the resource
    // has never been added to the index before
    if (id == null) {
      id = UUID.randomUUID().toString();
      resource.setIdentifier(id);
      uri.setIdentifier(id);
    }

    try {
      searchIdx.add(resource);
    } catch (ContentRepositoryException e) {
      throw e;
    } catch (Throwable t) {
      throw new ContentRepositoryException("Error adding " + resource + " to index", t);
    }

    return uri;
  }

  /**
   * Removes all entries for the given resource uri from the index and returns
   * <code>true</code>. If the resource is not part of the index,
   * <code>false</code> is returned.
   *
   * @param uri
   *          the resource uri
   * @return <code>true</code> if the resource was deleted
   * @throws IOException
   *           if updating the index fails
   * @throws ContentRepositoryException
   *           if deleting the resource fails
   */
  public synchronized boolean delete(ResourceURI uri) throws IOException,
      ContentRepositoryException, IllegalArgumentException {
    getIdentifier(uri);
    StringUtils.trimToNull(uri.getPath());
    uri.getVersion();

    // Finally, delete the entry
    return searchIdx.delete(uri);
  }

  /**
   * Returns all revisions for the specified resource or an empty array if the
   * resource doesn't exist.
   *
   * @param uri
   *          the resource uri
   * @return the revisions
   */
  public long[] getRevisions(ResourceURI uri) throws ContentRepositoryException {
    String id = getIdentifier(uri);
    if (id == null)
      return new long[] {};
    SearchQuery q = new SearchQueryImpl(site).withIdentifier(id).withField(VERSION);
    SearchResultItem[] items = searchIdx.getByQuery(q).getItems();
    long[] versions = new long[items.length];
    for (int i = 0; i < items.length; i++) {
      versions[i] = (Long) ((ResourceSearchResultItem) items[i]).getMetadataByKey(VERSION).getValue();
    }
    return versions;
  }

  /**
   * Returns the identifier of the resource with uri <code>uri</code> or
   * <code>null</code> if the uri is not part of the index.
   *
   * @param uri
   *          the uri
   * @return the id
   * @throws IllegalArgumentException
   *           if the uri does not contain a path
   * @throws ContentRepositoryException
   *           if accessing the index fails
   */
  public String getIdentifier(ResourceURI uri)
      throws ContentRepositoryException, IllegalArgumentException {

    if (uri.getIdentifier() != null)
      return uri.getIdentifier();

    String path = StringUtils.trimToNull(uri.getPath());
    if (path == null)
      throw new IllegalArgumentException("ResourceURI must contain a path");

    // Load the identifier from the index
    SearchQuery q = new SearchQueryImpl(site).withPath(path);
    if (uri.getType() != null)
      q.withTypes(uri.getType());
    SearchResultItem[] items = searchIdx.getByQuery(q).getItems();
    if (items.length == 0) {
      logger.debug("Attempt to locate id for non-existing path {}", path);
      return null;
    }

    String id = (String) ((ResourceSearchResultItem) items[0]).getMetadataByKey(RESOURCE_ID).getValue();
    uri.setIdentifier(id);
    return id;
  }

  /**
   * Returns the path of the resource with uri <code>uri</code> by looking it up
   * using the uri's identifier or <code>null</code> if the uri is not part of
   * the index.
   *
   * @param uri
   *          the uri
   * @return the path
   * @throws IllegalArgumentException
   *           if the uri does not contain an identifier
   * @throws ContentRepositoryException
   *           if accessing the index fails
   */
  public String getPath(ResourceURI uri) throws ContentRepositoryException,
      IllegalArgumentException {
    if (uri.getPath() != null)
      return uri.getPath();

    String id = uri.getIdentifier();
    if (id == null)
      throw new IllegalArgumentException("ResourceURI must contain an identifier");

    // Load the path from the index
    SearchQuery q = new SearchQueryImpl(site).withIdentifier(id);
    if (uri.getType() != null)
      q.withTypes(uri.getType());
    SearchResultItem[] items = searchIdx.getByQuery(q).getItems();
    if (items.length == 0) {
      logger.debug("Attempt to locate path for non existing resource '{}'", id);
      return null;
    }

    String path = (String) ((ResourceSearchResultItem) items[0]).getMetadataByKey(RESOURCE_ID).getValue();
    uri.setPath(path);
    return path;
  }

  /**
   * Returns the resource type or <code>null</code> the resource could not be
   * found.
   *
   * @param uri
   *          the resource uri
   * @return the resource type
   * @throws ContentRepositoryException
   *           if loading the type from the index fails
   */
  public String getType(ResourceURI uri) throws ContentRepositoryException {
    if (uri.getType() != null)
      return uri.getType();

    String id = uri.getIdentifier();
    String path = uri.getPath();
    SearchQuery q = null;

    if (id != null)
      q = new SearchQueryImpl(site).withIdentifier(id).withLimit(1);
    else if (path != null)
      q = new SearchQueryImpl(site).withPath(path).withLimit(1);
    else
      throw new IllegalArgumentException("URI must have either id or path");

    // Load the path from the index
    SearchResultItem[] items = searchIdx.getByQuery(q).getItems();
    if (items.length == 0) {
      logger.debug("Attempt to locate path for non existing resource '{}'", id);
      return null;
    }

    String type = (String) ((ResourceSearchResultItem) items[0]).getMetadataByKey(TYPE).getValue();
    uri.setType(type);
    return type;
  }

  /**
   * Updates the resource in the search index.
   *
   * @param resource
   *          the resource to update
   * @throws IOException
   *           if writing to the index fails
   * @throws ContentRepositoryException
   *           if updating the index fails
   */
  public synchronized void update(Resource<?> resource) throws IOException,
      ContentRepositoryException {
    ResourceURI uri = resource.getURI();

    // Make sure the uri has an identifier
    if (uri.getIdentifier() == null) {
      uri.setIdentifier(getIdentifier(uri));
    }

    searchIdx.update(resource);
  }

  /**
   * Updates the path of the given resource.
   *
   * @param uir
   *          the resource uri
   * @param path
   *          the new path
   * @throws IOException
   *           if writing to the index fails
   * @throws ContentRepositoryException
   *           if moving the resource fails
   * @throws IllegalStateException
   *           if the resource to be moved could not be found in the index
   */
  public synchronized void move(ResourceURI uri, String path)
      throws IOException, ContentRepositoryException, IllegalStateException {

    // Do it this way to make sure we have identical path trimming
    ResourceURI newURI = new ResourceURIImpl(uri.getType(), uri.getSite(), StringUtils.trimToNull(path), uri.getIdentifier(), uri.getVersion());
    path = newURI.getPath();

    searchIdx.move(uri, path);
  }

  /**
   * Removes all entries from the index.
   *
   * @throws IOException
   *           if clearing the index fails
   */
  public synchronized void clear() throws IOException {
    searchIdx.clear();
  }

  /**
   * Returns <code>true</code> if the given uri exists in the given version.
   *
   * @param uri
   *          the uri
   * @return <code>true</code> if the uri exists
   * @throws ContentRepositoryException
   *           if looking up the uri fails
   */
  public boolean exists(ResourceURI uri) throws ContentRepositoryException {
    String id = getIdentifier(uri);
    if (id == null)
      return false;
    SearchQuery q = new SearchQueryImpl(site).withIdentifier(id).withVersion(uri.getVersion()).withField(RESOURCE_ID);
    if (uri.getType() != null)
      q.withTypes(uri.getType());
    return searchIdx.getByQuery(q).getDocumentCount() > 0;
  }

  /**
   * Returns <code>true</code> if the given uri exists in any version.
   *
   * @param uri
   *          the uri
   * @return <code>true</code> if the uri exists in any version
   * @throws ContentRepositoryException
   *           if looking up the uri fails
   */
  public boolean existsInAnyVersion(ResourceURI uri)
      throws ContentRepositoryException {
    String id = getIdentifier(uri);
    if (id == null)
      return false;
    SearchQuery q = new SearchQueryImpl(site).withIdentifier(id).withLimit(1).withField(RESOURCE_ID);
    if (uri.getType() != null)
      q.withTypes(uri.getType());
    return searchIdx.getByQuery(q).getDocumentCount() > 0;
  }

  /**
   * Returns all URIs that share the common root <code>uri</code>, are no more
   * than <code>level</code> levels deep nested and feature the indicated
   * version.
   * <p>
   *
   * @param uri
   *          the root uri
   * @param level
   *          the maximum nesting, <code>0</code> to return direct children only
   * @return an iteration of the resulting uris
   */
  public Iterator<ResourceURI> list(ResourceURI uri, int level) {
    return list(uri, level, -1);
  }

  /**
   * Returns all uris that share the common root <code>uri</code>, are no more
   * than <code>level</code> levels deep nested and feature the indicated
   * version.
   * <p>
   *
   * @param uri
   *          the root uri
   * @param level
   *          the maximum nesting, <code>0</code> to return direct children only
   * @param version
   *          the requested version, <code>-1</code> for any version
   * @return an iteration of the resulting uris
   */
  public Iterator<ResourceURI> list(ResourceURI uri, int level, long version) {
    throw new UnsupportedOperationException("Not yet implemented");
  }

}
TOP

Related Classes of ch.entwine.weblounge.contentrepository.impl.index.ContentRepositoryIndex

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.