content: package ro.isdc.wro.model.group.processor;
  
  import static org.apache.commons.lang3.Validate.notNull;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.Reader;
  import java.io.StringReader;
  import java.io.StringWriter;
  import java.io.Writer;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.List;
  import java.util.concurrent.Callable;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  import java.util.concurrent.Future;
  
  import org.apache.commons.io.IOUtils;
  import org.apache.commons.io.input.BOMInputStream;
  import org.apache.commons.lang3.StringUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import ro.isdc.wro.WroRuntimeException;
  import ro.isdc.wro.config.Context;
  import ro.isdc.wro.config.ReadOnlyContext;
  import ro.isdc.wro.config.support.ContextPropagatingCallable;
  import ro.isdc.wro.manager.callback.LifecycleCallbackRegistry;
  import ro.isdc.wro.model.group.Inject;
  import ro.isdc.wro.model.resource.Resource;
  import ro.isdc.wro.model.resource.locator.factory.UriLocatorFactory;
  import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
  import ro.isdc.wro.model.resource.processor.decorator.DefaultProcessorDecorator;
  import ro.isdc.wro.model.resource.processor.factory.ProcessorsFactory;
  import ro.isdc.wro.model.resource.processor.support.ProcessingCriteria;
  import ro.isdc.wro.model.resource.processor.support.ProcessingType;
  import ro.isdc.wro.util.WroUtil;
  
  
  /**
   * TODO: refactor this class. Apply all preProcessor on provided {@link Resource} and returns the result of execution as
   * String.
   * <p>
   * This is useful when you want to preProcess a resource which is not a part of the model (css import use-case).
   *
   * @author Alex Objelean
   */
  public class PreProcessorExecutor {
    private static final Logger LOG = LoggerFactory.getLogger(PreProcessorExecutor.class);
    @Inject
    private UriLocatorFactory uriLocatorFactory;
    @Inject
    private ProcessorsFactory processorsFactory;
    @Inject
    private ReadOnlyContext context;
    @Inject
    private LifecycleCallbackRegistry callbackRegistry;
    @Inject
    private Injector injector;
    /**
     * Runs the preProcessing in parallel.
     */
    private ExecutorService executor;
  
    /**
     * Apply preProcessors on resources and merge them after all preProcessors are applied.
     *
     * @param resources
     *          what are the resources to merge.
     * @param minimize
     *          whether minimize aware processors must be applied or not.
     * @return preProcessed merged content.
     */
    public String processAndMerge(final List<Resource> resources, final boolean minimize)
        throws IOException {
      return processAndMerge(resources, ProcessingCriteria.create(ProcessingType.ALL, minimize));
    }
  
    /**
     * Apply preProcessors on resources and merge them.
     *
     * @param resources
     *          what are the resources to merge.
     * @param criteria
     *          {@link ProcessingCriteria} used to identify the processors to apply and those to skip.
     * @return preProcessed merged content.
     */
    public String processAndMerge(final List<Resource> resources, final ProcessingCriteria criteria)
        throws IOException {
      notNull(criteria);
      LOG.debug("criteria: {}", criteria);
      callbackRegistry.onBeforeMerge();
      try {
        notNull(resources);
        LOG.debug("process and merge resources: {}", resources);
        final StringBuffer result = new StringBuffer();
        if (shouldRunInParallel(resources)) {
          result.append(runInParallel(resources, criteria));
        } else {
          for (final Resource resource : resources) {
            LOG.debug("\tmerging resource: {}", resource);
            result.append(applyPreProcessors(resource, criteria));
          }
        }
        return result.toString();
      } finally {
        callbackRegistry.onAfterMerge();
      }
    }
  
    private boolean shouldRunInParallel(final List<Resource> resources) {
      final boolean isParallel = context.getConfig().isParallelPreprocessing();
      final int availableProcessors = Runtime.getRuntime().availableProcessors();
      return isParallel && resources.size() > 1 && availableProcessors > 1;
    }
  
    /**
     * runs the pre processors in parallel.
     *
     * @return merged and pre processed content.
     */
    private String runInParallel(final List<Resource> resources, final ProcessingCriteria criteria)
        throws IOException {
      LOG.debug("Running preProcessing in Parallel");
      final StringBuffer result = new StringBuffer();
      final List<Callable<String>> callables = new ArrayList<Callable<String>>();
      for (final Resource resource : resources) {
        callables.add(new Callable<String>() {
          public String call()
              throws Exception {
            LOG.debug("Callable started for resource: {} ...", resource);
            return applyPreProcessors(resource, criteria);
          }
        });
      }
      final ExecutorService exec = getExecutorService();
      final List<Future<String>> futures = new ArrayList<Future<String>>();
      for (final Callable<String> callable : callables) {
        // decorate with ContextPropagatingCallable in order to allow spawn threads to access the Context
        final Callable<String> decoratedCallable = new ContextPropagatingCallable<String>(callable);
        futures.add(exec.submit(decoratedCallable));
      }
  
      for (final Future<String> future : futures) {
        try {
          result.append(future.get());
        } catch (final Exception e) {
          // propagate original cause
          final Throwable cause = e.getCause();
          if (cause instanceof WroRuntimeException) {
            throw (WroRuntimeException) cause;
          } else if (cause instanceof IOException) {
            throw (IOException) cause;
          } else {
            throw new WroRuntimeException("Problem during parallel pre processing", e);
          }
        }
      }
      return result.toString();
    }
  
    private ExecutorService getExecutorService() {
      if (executor == null) {
        // use at most the number of available processors (true parallelism)
        final int threadPoolSize = Runtime.getRuntime().availableProcessors();
        LOG.debug("Parallel thread pool size: {}", threadPoolSize);
        executor = Executors.newFixedThreadPool(threadPoolSize,
            WroUtil.createDaemonThreadFactory("parallelPreprocessing"));
      }
      return executor;
    }
  
    /**
     * Apply a list of preprocessors on a resource.
     *
     * @param resource
     *          the {@link Resource} on which processors will be applied
     * @param processors
     *          the list of processor to apply on the resource.
     */
    private String applyPreProcessors(final Resource resource, final ProcessingCriteria criteria)
        throws IOException {
      final Collection<ResourcePreProcessor> processors = processorsFactory.getPreProcessors();
      LOG.debug("applying preProcessors: {}", processors);
  
      String resourceContent = null;
      try {
        resourceContent = getResourceContent(resource);
      } catch (final IOException e) {
        LOG.debug("Invalid resource found: {}", resource);
        if (Context.get().getConfig().isIgnoreMissingResources()) {
          return StringUtils.EMPTY;
        } else {
          LOG.error("Cannot ignore missing resource:  {}", resource);
          throw e;
        }
      }
      if (!processors.isEmpty()) {
        Writer writer = null;
        for (final ResourcePreProcessor processor : processors) {
          final ResourcePreProcessor decoratedProcessor = decoratePreProcessor(processor, criteria);
  
          writer = new StringWriter();
          final Reader reader = new StringReader(resourceContent);
          // decorate and process
          decoratedProcessor.process(resource, reader, writer);
          // use the outcome for next input
          resourceContent = writer.toString();
        }
      }
      // add explicitly new line at the end to avoid unexpected comment issue
      return String.format("%s%n", resourceContent);
    }
  
    /**
     * Decorates preProcessor with mandatory decorators.
     * This method is synchronized to ensure that processor is injected before it is being used by other thread.
     */
    private synchronized ResourcePreProcessor decoratePreProcessor(final ResourcePreProcessor processor,
        final ProcessingCriteria criteria) {
      final ResourcePreProcessor decorated = new DefaultProcessorDecorator(processor, criteria) {
        @Override
        public void process(final Resource resource, final Reader reader, final Writer writer)
            throws IOException {
          try {
            callbackRegistry.onBeforePreProcess();
            super.process(resource, reader, writer);
          } finally {
            callbackRegistry.onAfterPreProcess();
          }
        }
      };
      injector.inject(decorated);
      return decorated;
    }
  
    /**
     * @return a Reader for the provided resource.
     * @param resource
     *          {@link Resource} which content to return.
     * @param resources
     *          the list of all resources processed in this context, used for duplicate resource detection.
     */
    private String getResourceContent(final Resource resource)
        throws IOException {
      InputStream is = null;
      try {
        is = new BOMInputStream(uriLocatorFactory.locate(resource.getUri()));
        final String result = IOUtils.toString(is, context.getConfig().getEncoding());
        if (StringUtils.isEmpty(result)) {
          LOG.debug("Empty resource detected: {}", resource.getUri());
        }
        return result;
      } finally {
        IOUtils.closeQuietly(is);
      }
    }
  
    /**
     * Perform cleanUp on service shut down.
     */
    public void destroy() {
      getExecutorService().shutdownNow();
    }
  }
  