Package ch.entwine.weblounge.preview.phantomjs

Source Code of ch.entwine.weblounge.preview.phantomjs.PhantomJsPagePreviewGenerator$PhantomJsProcessExecutor

/*
*  Weblounge: Web Content Management System
*  Copyright (c) 2011 The Weblounge Team
*  http://weblounge.o2it.ch
*
*  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.preview.phantomjs;

import ch.entwine.weblounge.common.content.PreviewGenerator;
import ch.entwine.weblounge.common.content.Resource;
import ch.entwine.weblounge.common.content.ResourceURI;
import ch.entwine.weblounge.common.content.image.ImagePreviewGenerator;
import ch.entwine.weblounge.common.content.image.ImageStyle;
import ch.entwine.weblounge.common.content.page.Page;
import ch.entwine.weblounge.common.content.page.PagePreviewGenerator;
import ch.entwine.weblounge.common.impl.util.config.ConfigurationUtils;
import ch.entwine.weblounge.common.impl.util.process.ProcessExcecutorException;
import ch.entwine.weblounge.common.impl.util.process.ProcessExecutor;
import ch.entwine.weblounge.common.language.Language;
import ch.entwine.weblounge.common.site.Environment;
import ch.entwine.weblounge.common.site.Site;
import ch.entwine.weblounge.common.url.UrlUtils;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentContext;
import org.osgi.util.tracker.ServiceTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

/**
* A <code>PreviewGenerator</code> that will generate previews for pages.
*/
public class PhantomJsPagePreviewGenerator implements PagePreviewGenerator {

  /** Logger factory */
  private static final Logger logger = LoggerFactory.getLogger(PhantomJsPagePreviewGenerator.class);

  /** Page request handler path prefix */
  protected static final String PAGE_HANDLER_PREFIX = "/weblounge-pages/";

  /** Format for the preview images */
  private static final String PREVIEW_FORMAT = "png";

  /** Format for the preview images */
  private static final String PREVIEW_CONTENT_TYPE = "image/png";

  /** Name of the script parameter */
  private static final String PARAM_PREPARE_SCRIPT = "prepare.js";

  /** Name of the script */
  private static final String SCRIPT_FILE = "/phantomjs/render.js";

  /** The preview generators */
  private final List<ImagePreviewGenerator> previewGenerators = new ArrayList<ImagePreviewGenerator>();

  /** The preview generator service tracker */
  private ServiceTracker previewGeneratorTracker = null;

  /** The script template */
  private String scriptTemplate = null;

  /** Directory containing temporary files */
  private File phantomTmpDir = null;

  /** The script */
  private File scriptFile = null;

  /**
   * Called by the {@link PhantomJsActivator} on service activation.
   *
   * @param ctx
   *          the component context
   */
  void activate(ComponentContext ctx) {
    try {
      prepareScript();
    } catch (IOException e) {
      throw new IllegalStateException(e);
    }
    previewGeneratorTracker = new ImagePreviewGeneratorTracker(ctx.getBundleContext());
    previewGeneratorTracker.open();
  }

  /**
   * Called by the {@link PhantomJsActivator} on service inactivation.
   */
  void deactivate() {
    if (previewGeneratorTracker != null) {
      previewGeneratorTracker.close();
    }
    FileUtils.deleteQuietly(phantomTmpDir);
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.content.PreviewGenerator#supports(ch.entwine.weblounge.common.content.Resource)
   */
  public boolean supports(Resource<?> resource) {
    return (resource instanceof Page);
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.content.PreviewGenerator#supports(java.lang.String)
   */
  public boolean supports(String format) {
    for (ImagePreviewGenerator generator : previewGenerators) {
      if (generator.supports(PREVIEW_FORMAT) && generator.supports(format))
        return true;
    }
    return false;
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.content.PreviewGenerator#createPreview(ch.entwine.weblounge.common.content.Resource,
   *      ch.entwine.weblounge.common.site.Environment,
   *      ch.entwine.weblounge.common.language.Language,
   *      ch.entwine.weblounge.common.content.image.ImageStyle, String,
   *      java.io.InputStream, java.io.OutputStream)
   */
  public void createPreview(Resource<?> resource, Environment environment,
      Language language, ImageStyle style, String format, InputStream is,
      OutputStream os) throws IOException {

    // We don't need the input stream
    IOUtils.closeQuietly(is);

    // Find a suitable image preview generator for scaling
    ImagePreviewGenerator imagePreviewGenerator = null;
    synchronized (previewGenerators) {
      for (ImagePreviewGenerator generator : previewGenerators) {
        if (generator.supports(format)) {
          imagePreviewGenerator = generator;
          break;
        }
      }
      if (imagePreviewGenerator == null) {
        logger.debug("Unable to generate page previews since no image renderer is available");
        return;
      }
    }

    // Find the relevant metadata to start the request
    ResourceURI uri = resource.getURI();
    long version = resource.getVersion();
    Site site = uri.getSite();

    // Create the url
    URL pageURL = new URL(UrlUtils.concat(site.getHostname(environment).toExternalForm(), PAGE_HANDLER_PREFIX, uri.getIdentifier()));
    if (version == Resource.WORK) {
      pageURL = new URL(UrlUtils.concat(pageURL.toExternalForm(), "work_" + language.getIdentifier() + ".html"));
    } else {
      pageURL = new URL(UrlUtils.concat(pageURL.toExternalForm(), "index_" + language.getIdentifier() + ".html"));
    }

    // Create a temporary file
    final File rendererdFile = File.createTempFile("phantomjs-", "." + format, phantomTmpDir);
    final URL finalPageURL = pageURL;
    final AtomicBoolean success = new AtomicBoolean();

    // Call PhantomJS to render the page
    try {
      final PhantomJsProcessExecutor phantomjs = new PhantomJsProcessExecutor(scriptFile.getAbsolutePath(), pageURL.toExternalForm(), rendererdFile.getAbsolutePath()) {
        @Override
        protected void onProcessFinished(int exitCode) throws IOException {
          super.onProcessFinished(exitCode);
          switch (exitCode) {
            case 0:
              if (rendererdFile.length() > 0) {
                success.set(true);
                logger.debug("Page preview of {} created at {}", finalPageURL, rendererdFile.getAbsolutePath());
              } else {
                logger.warn("Error creating page preview of {}", finalPageURL);
                success.set(false);
                FileUtils.deleteQuietly(rendererdFile);
              }
              break;
            default:
              success.set(false);
              logger.warn("Error creating page preview of {}", finalPageURL);
              FileUtils.deleteQuietly(rendererdFile);
          }
        }
      };

      // Finally have PhantomJS create the preview
      logger.debug("Creating preview of {}", finalPageURL);
      phantomjs.execute();

    } catch (ProcessExcecutorException e) {
      logger.warn("Error creating page preview of {}: {}", pageURL, e.getMessage());
      throw new IOException(e);
    } finally {
      // If page preview rendering failed, there is no point in scaling the
      // images
      if (!success.get()) {
        logger.debug("Skipping scaling of failed preview rendering {}", pageURL);
        FileUtils.deleteQuietly(rendererdFile);
        return;
      }
    }

    FileInputStream imageIs = null;

    // Scale the image to the correct size
    try {
      imageIs = new FileInputStream(rendererdFile);
      imagePreviewGenerator.createPreview(resource, environment, language, style, PREVIEW_FORMAT, imageIs, os);
    } catch (IOException e) {
      logger.error("Error reading original page preview from " + rendererdFile, e);
      throw e;
    } catch (Throwable t) {
      logger.warn("Error scaling page preview at " + uri + ": " + t.getMessage(), t);
      throw new IOException(t);
    } finally {
      IOUtils.closeQuietly(imageIs);
      FileUtils.deleteQuietly(rendererdFile);
    }

  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.content.PreviewGenerator#getContentType(ch.entwine.weblounge.common.content.Resource,
   *      ch.entwine.weblounge.common.language.Language,
   *      ch.entwine.weblounge.common.content.image.ImageStyle)
   */
  public String getContentType(Resource<?> resource, Language language,
      ImageStyle style) {
    return PREVIEW_CONTENT_TYPE;
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.content.PreviewGenerator#getSuffix(ch.entwine.weblounge.common.content.Resource,
   *      ch.entwine.weblounge.common.language.Language,
   *      ch.entwine.weblounge.common.content.image.ImageStyle)
   */
  public String getSuffix(Resource<?> resource, Language language,
      ImageStyle style) {
    return PREVIEW_FORMAT;
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.content.PreviewGenerator#getPriority()
   */
  public int getPriority() {
    return 100;
  }

  /**
   * Adds the preview generator to the list of registered preview generators.
   *
   * @param generator
   *          the generator
   */
  void addPreviewGenerator(ImagePreviewGenerator generator) {
    synchronized (previewGenerators) {
      previewGenerators.add(generator);
      Collections.sort(previewGenerators, new Comparator<PreviewGenerator>() {
        public int compare(PreviewGenerator a, PreviewGenerator b) {
          return Integer.valueOf(b.getPriority()).compareTo(a.getPriority());
        }
      });
    }
  }

  /**
   * Removes the preview generator from the list of registered preview
   * generators.
   *
   * @param generator
   *          the generator
   */
  void removePreviewGenerator(ImagePreviewGenerator generator) {
    synchronized (previewGenerators) {
      previewGenerators.remove(generator);
    }
  }

  /**
   * Reads the script from the resource, processes the variables and writes it
   * to the file system so it can be accessed by PhantomJS.
   *
   * @throws IOException
   *           if reading the template or writing the file failed
   */
  private void prepareScript() throws IOException {
    InputStream is = null;
    InputStream fis = null;
    OutputStream os = null;
    try {
      // Create the temporary directory for everything PhantomJS
      phantomTmpDir = new File(FileUtils.getTempDirectory(), "phantomjs");
      if (!phantomTmpDir.isDirectory() && !phantomTmpDir.mkdirs()) {
        logger.error("Unable to create temp directory for PhantomJS at {}", phantomTmpDir);
        throw new IOException("Unable to create temp directory for PhantomJS at " + phantomTmpDir);
      }

      // Create the script
      is = PhantomJsPagePreviewGenerator.class.getResourceAsStream(SCRIPT_FILE);
      scriptTemplate = IOUtils.toString(is);
      scriptFile = new File(phantomTmpDir, "pagepreview.js");

      // Process templates
      Map<String, String> properties = new HashMap<String, String>();
      properties.put(PARAM_PREPARE_SCRIPT, "return true;");
      String script = ConfigurationUtils.processTemplate(scriptTemplate, properties);

      // Write the processed script to disk
      fis = IOUtils.toInputStream(script);
      os = new FileOutputStream(scriptFile);
      IOUtils.copy(fis, os);

    } catch (IOException e) {
      logger.error("Error reading phantomjs script template from " + SCRIPT_FILE, e);
      FileUtils.deleteQuietly(scriptFile);
      throw e;
    } finally {
      IOUtils.closeQuietly(is);
      IOUtils.closeQuietly(fis);
      IOUtils.closeQuietly(os);
    }
  }

  /**
   * Implementation of a <code>ServiceTracker</code> that is tracking instances
   * of type {@link ImagePreviewGenerator} with an associated <code>site</code>
   * attribute.
   */
  private class ImagePreviewGeneratorTracker extends ServiceTracker {

    /**
     * Creates a new service tracker that is using the given bundle context to
     * look up service instances.
     *
     * @param ctx
     *          the bundle context
     */
    ImagePreviewGeneratorTracker(BundleContext ctx) {
      super(ctx, ImagePreviewGenerator.class.getName(), null);
    }

    /**
     * {@inheritDoc}
     *
     * @see org.osgi.util.tracker.ServiceTracker#addingService(org.osgi.framework.ServiceReference)
     */
    @Override
    public Object addingService(ServiceReference reference) {
      ImagePreviewGenerator previewGenerator = (ImagePreviewGenerator) super.addingService(reference);
      addPreviewGenerator(previewGenerator);
      return previewGenerator;
    }

    /**
     * {@inheritDoc}
     *
     * @see org.osgi.util.tracker.ServiceTracker#removedService(org.osgi.framework.ServiceReference,
     *      java.lang.Object)
     */
    @Override
    public void removedService(ServiceReference reference, Object service) {
      removePreviewGenerator((ImagePreviewGenerator) service);
    }

  }

  /**
   * This process executor is used to run <code>PhantomJS</code> in the system
   * shell.
   */
  private static class PhantomJsProcessExecutor extends ProcessExecutor<IOException> {

    /**
     * Creates a process executor for the phantom JS rendering process
     *
     * @param script
     *          path to the javascript
     * @param address
     *          the web page to connect to
     * @param file
     *          the file to write to
     */
    protected PhantomJsProcessExecutor(String script, String address,
        String file) {
      super("phantomjs", new String[] { script, address, file });
    }

  }

}
TOP

Related Classes of ch.entwine.weblounge.preview.phantomjs.PhantomJsPagePreviewGenerator$PhantomJsProcessExecutor

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.
ew');