Package ch.entwine.weblounge.contentrepository.impl.bundle

Source Code of ch.entwine.weblounge.contentrepository.impl.bundle.WritableBundleContentRepository

/*
*  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.bundle;

import ch.entwine.weblounge.common.content.Resource;
import ch.entwine.weblounge.common.content.ResourceContent;
import ch.entwine.weblounge.common.content.ResourceReader;
import ch.entwine.weblounge.common.content.ResourceURI;
import ch.entwine.weblounge.common.content.ResourceUtils;
import ch.entwine.weblounge.common.impl.content.ResourceURIImpl;
import ch.entwine.weblounge.common.repository.ContentRepositoryException;
import ch.entwine.weblounge.common.repository.ResourceSerializer;
import ch.entwine.weblounge.common.site.Site;
import ch.entwine.weblounge.common.url.PathUtils;
import ch.entwine.weblounge.common.url.UrlUtils;
import ch.entwine.weblounge.contentrepository.impl.fs.FileSystemContentRepository;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.osgi.framework.Bundle;
import org.osgi.service.cm.ConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.xml.parsers.ParserConfigurationException;

/**
* Represents a bundle repository that is writable. OSGi bundles are not
* writable, so this content repository behaves like a
* <code>FileSystemContentRepository</code>, while the initial content is copied
* from the respective bundle.
*/
public class WritableBundleContentRepository extends FileSystemContentRepository {

  /** The logging facility */
  private static final Logger logger = LoggerFactory.getLogger(WritableBundleContentRepository.class);

  /** The repository type */
  public static final String TYPE = "ch.entwine.weblounge.contentrepository.bundle";

  /** Option specifying the root directory */
  public static final String OPT_ROOT_DIR = BundleContentRepository.CONF_PREFIX + "root";

  /** Prefix into the bundle */
  protected String bundlePathPrefix = "/repository";

  /** The site's bundle */
  protected Bundle bundle = null;

  @Override
  public String getType() {
    return TYPE;
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.contentrepository.impl.fs.FileSystemContentRepository#connect(ch.entwine.weblounge.common.site.Site)
   */
  @Override
  public void connect(Site site) throws ContentRepositoryException {

    // Don't have the super implementation automatically create a home page
    // for us. Otherwise, the creation of the index from the bundle won't work.
    createHomepage = false;

    // Initialize the repository and the repository index
    super.connect(site);

    try {
      initializing = true;

      // Find the site's bundle
      bundle = loadBundle(site);
      if (bundle == null)
        throw new ContentRepositoryException("Unable to locate bundle for site '" + site + "'");

      // Add the bundle contents to the index if needed
      File rootDirecotry = getRootDirectory();
      if (getResourceCount() == 0 || !rootDirecotry.exists() || rootDirecotry.list().length == 0)
        indexBundleContents();

      // If there was no homepage as part of the bundle, create it
      createHomepage();

    } finally {
      initializing = false;
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.osgi.service.cm.ManagedService#updated(java.util.Dictionary)
   */
  @Override
  @SuppressWarnings({ "rawtypes", "unchecked" })
  public void updated(Dictionary properties) throws ConfigurationException {

    // Since the bundle content repository is based on the file system content
    // repository, the configuration properties need to be adjusted
    String rootDirPath = (String) properties.get(OPT_ROOT_DIR);
    if (StringUtils.isNotBlank(rootDirPath)) {
      properties.put(FileSystemContentRepository.OPT_ROOT_DIR, PathUtils.trim(rootDirPath));
      logger.info("Bundle content repository data will be stored at {}", rootDirPath);
    }

    super.updated(properties);
  }

  /**
   * Initializes the content repository by loading the resources from the bundle
   * and adding it to the repository index.
   */
  protected void indexBundleContents() throws ContentRepositoryException {
    logger.info("Indexing bundle content repository {}", this);

    try {
      logger.info("Clearing index of bundle content repository {}", this);
      index.clear();
    } catch (IOException e) {
      logger.error("Error indexing bundle content repository: {}", e.getMessage());
    }

    // See if there are any resources. If that's the case, then we don't need to
    // do anything. If not, we need to copy everything that's currently in the
    // bundle.
    for (ResourceSerializer<?, ?> serializer : getSerializers()) {
      String resourceDirectoryPath = UrlUtils.concat(repositorySiteRoot.getAbsolutePath(), serializer.getType() + "s");
      File resourceDirectory = new File(resourceDirectoryPath);
      if (resourceDirectory.isDirectory() && resourceDirectory.list().length > 0) {
        logger.debug("Found existing {}s for site '{}' at {}", new Object[] {
            serializer.getType(),
            site,
            resourceDirectoryPath });
        return;
      }
    }

    // If there is no content repository at the target location, copy the
    // initial bundle contents to the filesystem. Otherwise, keep working with
    // what's there already.
    logger.info("Loading resources for '{}' from bundle '{}'", site, bundle.getSymbolicName());
    try {
      for (Iterator<ResourceURI> pi = getResourceURIsFromBundle(); pi.hasNext();) {
        ResourceURI uri = pi.next();

        try {
          Resource<?> resource = loadResourceFromBundle(uri);
          if (resource == null) {
            throw new ContentRepositoryException("Unable to load " + uri.getType() + " " + uri + " from bundle");
          }

          // Update the uri, it now contains the id in addition to just the path
          uri = resource.getURI();

          // Make sure we are not updating existing resources, since this is the
          // first time import.
          if (exists(uri)) {
            throw new ContentRepositoryException("Error adding resource " + uri + " to repository: a resource with id '" + uri.getIdentifier() + "' or path '" + uri.getPath() + "' already exists");
          }

          logger.info("Loading {} {}:{}", new Object[] {
              uri.getType(),
              site,
              uri });
          Set<? extends ResourceContent> content = resource.contents();
          if (content.size() == 0) {
            put(resource);
          } else {
            for (ResourceContent c : content)
              resource.removeContent(c.getLanguage());
            put(resource);
            for (ResourceContent c : content) {
              InputStream is = null;
              try {
                is = loadResourceContentFromBundle(uri, c);
                if (is == null && c.getExternalLocation() == null)
                  throw new ContentRepositoryException("Resource content " + c + " missing from repository");
                putContent(uri, c, is);
              } finally {
                IOUtils.closeQuietly(is);
              }
            }
          }
        } catch (IOException e) {
          logger.error("Error reading " + uri.getType() + " " + uri + ": " + e.getMessage(), e);
          throw new ContentRepositoryException(e);
        }
      }
    } catch (ContentRepositoryException e) {
      cleanupAfterFailure();
      throw e;
    }

    // Log index statistics to console
    long resourceCount = index.getResourceCount();
    long resourceVersionCount = index.getRevisionCount();
    logger.info("Index contains {} resources and {} revisions", resourceCount, resourceVersionCount - resourceCount);
  }

  /**
   * Closes the index and removes the bundle directory from disk.
   */
  private void cleanupAfterFailure() {
    try {
      index.close();
      FileUtils.deleteDirectory(repositorySiteRoot);
      logger.error("Site index and repository directory have been reset");
    } catch (IOException e2) {
      logger.error("Unable to clean up index and repository directory " + repositorySiteRoot);
    }
  }

  /**
   * Loads all resources from the bundle and returns their uris as an iterator.
   *
   * @return the resource uris
   * @throws ContentRepositoryException
   *           if reading from the repository fails
   */
  @SuppressWarnings("unchecked")
  protected Iterator<ResourceURI> getResourceURIsFromBundle()
      throws ContentRepositoryException {

    List<ResourceURI> resourceURIs = new ArrayList<ResourceURI>();

    // For every serializer, try to load the resources
    for (ResourceSerializer<?, ?> serializer : getSerializers()) {
      String resourceDirectory = serializer.getType() + "s";
      String resourcePathPrefix = UrlUtils.concat(bundlePathPrefix, resourceDirectory);
      Enumeration<URL> entries = bundle.findEntries(resourcePathPrefix, "*.xml", true);
      if (entries != null) {
        while (entries.hasMoreElements()) {
          URL entry = entries.nextElement();
          String path = FilenameUtils.getPath(entry.getPath());
          path = path.substring(resourcePathPrefix.length() - 1);
          long v = ResourceUtils.getVersion(FilenameUtils.getBaseName(entry.getPath()));
          ResourceURI resourceURI = new ResourceURIImpl(serializer.getType(), site, path, v);
          resourceURIs.add(resourceURI);
          logger.trace("Found revision '{}' of {} {}", new Object[] {
              v,
              resourceURI.getType(),
              entry });
        }
      }
    }

    return resourceURIs.iterator();
  }

  /**
   * Loads the specified resource from the bundle rather than from the content
   * repository.
   *
   * @param uri
   *          the uri
   * @return the resource
   * @throws IOException
   *           if reading the resource fails
   */
  protected Resource<?> loadResourceFromBundle(ResourceURI uri)
      throws IOException {
    String uriPath = uri.getPath();
    if (uriPath == null)
      throw new IllegalArgumentException("Resource uri needs a path");
    String entryPath = UrlUtils.concat(bundlePathPrefix, uri.getType() + "s", uriPath, ResourceUtils.getDocument(uri.getVersion()));
    URL url = bundle.getEntry(entryPath);
    if (url == null)
      return null;
    try {
      ResourceSerializer<?, ?> serializer = getSerializerByType(uri.getType());
      if (serializer == null) {
        logger.warn("Unable to read {} {}: no serializer found", uri.getType(), uri);
        return null;
      }
      ResourceReader<?, ?> resourceReader = serializer.getReader();
      return resourceReader.read(url.openStream(), site);
    } catch (SAXException e) {
      throw new RuntimeException("SAX error while reading " + uri.getType() + " '" + uri + "'", e);
    } catch (IOException e) {
      throw new IOException("I/O error while reading " + uri.getType() + " '" + uri + "'", e);
    } catch (ParserConfigurationException e) {
      throw new IllegalStateException("Parser configuration error while reading " + uri.getType() + " '" + uri + "'", e);
    } catch (Throwable t) {
      throw new IllegalStateException(t);
    }
  }

  /**
   * Loads the specified resource from the bundle rather than from the content
   * repository.
   *
   * @param uri
   *          the uri
   * @return the resource
   * @throws IOException
   *           if reading the resource fails
   */
  protected InputStream loadResourceContentFromBundle(ResourceURI uri,
      ResourceContent content) throws IOException {
    String uriPath = uri.getPath();
    if (uriPath == null)
      throw new IllegalArgumentException("Resource uri needs a path");
    String documentName = content.getLanguage().getIdentifier();
    if (!"".equals(FilenameUtils.getExtension(content.getFilename())))
      documentName += "." + FilenameUtils.getExtension(content.getFilename());
    String entryPath = UrlUtils.concat(bundlePathPrefix, uri.getType() + "s", uriPath, documentName);
    URL url = bundle.getEntry(entryPath);
    if (url == null)
      return null;
    return url.openStream();
  }

}
TOP

Related Classes of ch.entwine.weblounge.contentrepository.impl.bundle.WritableBundleContentRepository

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.