/*
 * Copyright (c) 2008. All rights reserved.
 */
package ro.isdc.wro.manager.factory;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.cache.CacheKey;
import ro.isdc.wro.cache.CacheStrategy;
import ro.isdc.wro.cache.CacheValue;
import ro.isdc.wro.cache.factory.CacheKeyFactory;
import ro.isdc.wro.cache.factory.DefaultCacheKeyFactory;
import ro.isdc.wro.cache.impl.LruMemoryCacheStrategy;
import ro.isdc.wro.config.metadata.DefaultMetaDataFactory;
import ro.isdc.wro.config.metadata.MetaDataFactory;
import ro.isdc.wro.manager.WroManager;
import ro.isdc.wro.model.WroModel;
import ro.isdc.wro.model.factory.WroModelFactory;
import ro.isdc.wro.model.factory.XmlModelFactory;
import ro.isdc.wro.model.group.DefaultGroupExtractor;
import ro.isdc.wro.model.group.GroupExtractor;
import ro.isdc.wro.model.group.processor.Injector;
import ro.isdc.wro.model.resource.locator.factory.DefaultUriLocatorFactory;
import ro.isdc.wro.model.resource.locator.factory.UriLocatorFactory;
import ro.isdc.wro.model.resource.processor.factory.DefaultProcessorsFactory;
import ro.isdc.wro.model.resource.processor.factory.ProcessorsFactory;
import ro.isdc.wro.model.resource.support.DefaultResourceAuthorizationManager;
import ro.isdc.wro.model.resource.support.ResourceAuthorizationManager;
import ro.isdc.wro.model.resource.support.change.ResourceWatcher;
import ro.isdc.wro.model.resource.support.hash.HashStrategy;
import ro.isdc.wro.model.resource.support.hash.SHA1HashStrategy;
import ro.isdc.wro.model.resource.support.naming.NamingStrategy;
import ro.isdc.wro.model.resource.support.naming.NoOpNamingStrategy;
import ro.isdc.wro.util.DestroyableLazyInitializer;
import ro.isdc.wro.util.Transformer;

/**
 * Default implementation of {@link WroManagerFactory} which creates default locators and processors and handles the
 * injection logic by creating an {@link Injector} and injecting where it is appropriate.
 *
 * @author Alex Objelean
 * @created Created on Dec 30, 2009
 */
public class BaseWroManagerFactory
    implements WroManagerFactory {
  private static final Logger LOG = LoggerFactory.getLogger(BaseWroManagerFactory.class);

  private GroupExtractor groupExtractor;
  private WroModelFactory modelFactory;
  private CacheStrategy<CacheKey, CacheValue> cacheStrategy;
  private HashStrategy hashStrategy;
  /**
   * A list of model transformers. Allows manager to mutate the model before it is being parsed and processed.
   */
  private List<Transformer<WroModel>> modelTransformers;
  private UriLocatorFactory uriLocatorFactory;
  private ProcessorsFactory processorsFactory;
  private NamingStrategy namingStrategy;
  private ResourceAuthorizationManager authorizationManager;
  private CacheKeyFactory cacheKeyFactory;
  private MetaDataFactory metaDataFactory;
  private ResourceWatcher resourceWatcher;
  /**
   * Handles the lazy synchronized creation of the manager
   */
  private final DestroyableLazyInitializer<WroManager> managerInitializer = new DestroyableLazyInitializer<WroManager>() {
    @Override
    protected WroManager initialize() {
      final WroManager.Builder managerBuilder = new WroManager.Builder();
      if (modelFactory == null) {
        modelFactory = newModelFactory();
      }
      if (groupExtractor == null) {
        groupExtractor = newGroupExtractor();
      }
      if (cacheStrategy == null) {
        cacheStrategy = newCacheStrategy();
      }
      if (hashStrategy == null) {
        hashStrategy = newHashStrategy();
      }
      if (modelTransformers == null) {
        modelTransformers = newModelTransformers();
      }
      if (processorsFactory == null) {
        processorsFactory = newProcessorsFactory();
      }
      if (uriLocatorFactory == null) {
        uriLocatorFactory = newUriLocatorFactory();
      }
      // use NoOp as default naming strategy
      if (namingStrategy == null) {
        namingStrategy = newNamingStrategy();
      }
      if (authorizationManager == null) {
        authorizationManager = newAuthorizationManager();
      }
      if (cacheKeyFactory == null) {
        cacheKeyFactory = newCacheKeyFactory();
      }
      if (metaDataFactory == null) {
        metaDataFactory = newMetaDataFactory();
      }
      if (groupExtractor != null) {
        managerBuilder.setGroupExtractor(groupExtractor);
      }
      if (cacheStrategy != null) {
        managerBuilder.setCacheStrategy(cacheStrategy);
      }
      if (hashStrategy != null) {
        managerBuilder.setHashStrategy(hashStrategy);
      }
      if (uriLocatorFactory != null) {
        managerBuilder.setLocatorFactory(uriLocatorFactory);
      }
      if (processorsFactory != null) {
        managerBuilder.setProcessorsFactory(processorsFactory);
      }
      if (namingStrategy != null) {
        managerBuilder.setNamingStrategy(namingStrategy);
      }
      if (modelTransformers != null) {
        managerBuilder.setModelTransformers(modelTransformers);
      }
      if (modelFactory != null) {
        managerBuilder.setModelFactory(modelFactory);
      }
      if (authorizationManager != null) {
        managerBuilder.setAuthorizationManager(authorizationManager);
      }
      if (cacheKeyFactory != null) {
        managerBuilder.setCacheKeyFactory(cacheKeyFactory);
      }
      if (metaDataFactory != null) {
        managerBuilder.setMetaDataFactory(metaDataFactory);
      }
      if (resourceWatcher == null) {
        resourceWatcher = new ResourceWatcher();
      }
      final WroManager manager = managerBuilder.build();

      onAfterInitializeManager(manager);
      return manager;
    }
  };

  /**
   * Creates default singleton instance of manager, by initializing manager dependencies with default values
   * (processors).
   */
  public final WroManager create() {
    return managerInitializer.get();
  }

  /**
   * @return default implementation of {@link ResourceAuthorizationManager}.
   */
  protected ResourceAuthorizationManager newAuthorizationManager() {
    return new DefaultResourceAuthorizationManager();
  }

  /**
   * Allows factory to do additional manager configuration after it was initialzed. One use-case is to configure
   * callbacks. Default implementation does nothing. Do not set anything else except callbacks in this method, otherwise
   * the initialization will not be performed properly.
   *
   * @param manager
   *          initialized instance of {@link WroManager}.
   */
  protected void onAfterInitializeManager(final WroManager manager) {
  }

  /**
   * @param namingStrategy
   *          the namingStrategy to set
   */
  public BaseWroManagerFactory setNamingStrategy(final NamingStrategy namingStrategy) {
    this.namingStrategy = namingStrategy;
    return this;
  }

  /**
   * @return default implementation of modelTransformers.
   */
  protected List<Transformer<WroModel>> newModelTransformers() {
    return this.modelTransformers;
  }

  /**
   * Override to provide a different or modified default factory implementation.
   *
   * @return {@link ProcessorsFactory} object.
   */
  protected ProcessorsFactory newProcessorsFactory() {
    return new DefaultProcessorsFactory();
  }

  /**
   * Override to provide a different or modified factory.
   *
   * @return {@link UriLocatorFactory} object.
   */
  protected UriLocatorFactory newUriLocatorFactory() {
    return new DefaultUriLocatorFactory();
  }

  /**
   * @return {@link HashStrategy} instance.
   */
  protected HashStrategy newHashStrategy() {
    return new SHA1HashStrategy();
  }

  /**
   * @return default {@link NamingStrategy} to be used by this {@link WroManagerFactory}
   */
  protected NamingStrategy newNamingStrategy() {
    return new NoOpNamingStrategy();
  }

  /**
   * {@inheritDoc}
   */
  public void onCachePeriodChanged(final long period) {
    try {
      managerInitializer.get().onCachePeriodChanged(period);
    } catch (final WroRuntimeException e) {
      LOG.warn("[FAIL] Unable to reload cache, probably because invoked outside of context");
    }
  }

  /**
   * {@inheritDoc}
   */
  public void onModelPeriodChanged(final long period) {
    try {
      managerInitializer.get().onModelPeriodChanged(period);
    } catch (final WroRuntimeException e) {
      LOG.warn("[FAIL] Unable to reload model, probably because invoked outside of context");
    }
  }

  /**
   * @return {@link CacheStrategy} instance for resources' group caching.
   */
  protected CacheStrategy<CacheKey, CacheValue> newCacheStrategy() {
    return new LruMemoryCacheStrategy<CacheKey, CacheValue>();
  }

  /**
   * @return {@link GroupExtractor} implementation.
   */
  protected GroupExtractor newGroupExtractor() {
    return new DefaultGroupExtractor();
  }

  /**
   * @return default implementation of {@link CacheKeyFactory}.
   */
  protected CacheKeyFactory newCacheKeyFactory() {
    return new DefaultCacheKeyFactory();
  }

  /**
   * @param servletContext
   *          {@link ServletContext} which could be useful for creating dynamic {@link WroModel}.
   * @return {@link WroModelFactory} implementation
   */
  protected WroModelFactory newModelFactory() {
    try {
      LOG.debug("Trying to use SmartWroModelFactory as default model factory");
      final Class<? extends WroModelFactory> smartFactoryClass = Class.forName(
          "ro.isdc.wro.extensions.model.factory.SmartWroModelFactory").asSubclass(WroModelFactory.class);
      return smartFactoryClass.newInstance();
    } catch (final Exception e) {
      LOG.debug("SmartWroModelFactory is not available. Using default model factory.");
      LOG.debug("Reason: {}", e.getMessage());
    }
    return new XmlModelFactory();
  }

  /**
   * @return default implementation of {@link MetaDataFactory} used when no {@link MetaDataFactory} is set.
   */
  protected MetaDataFactory newMetaDataFactory() {
    return new DefaultMetaDataFactory();
  }

  /**
   * @param groupExtractor
   *          the groupExtractor to set
   */
  public BaseWroManagerFactory setGroupExtractor(final GroupExtractor groupExtractor) {
    this.groupExtractor = groupExtractor;
    return this;
  }

  /**
   * @param modelFactory
   *          the modelFactory to set
   */
  public BaseWroManagerFactory setModelFactory(final WroModelFactory modelFactory) {
    this.modelFactory = modelFactory;
    return this;
  }

  /**
   * @param hashBuilder
   *          the hashBuilder to set
   */
  public BaseWroManagerFactory setHashStrategy(final HashStrategy hashStrategy) {
    this.hashStrategy = hashStrategy;
    return this;
  }

  public void setCacheKeyFactory(final CacheKeyFactory cacheKeyFactory) {
    this.cacheKeyFactory = cacheKeyFactory;
  }

  /**
   * @param modelTransformers
   *          the modelTransformers to set
   */
  public BaseWroManagerFactory setModelTransformers(final List<Transformer<WroModel>> modelTransformers) {
    this.modelTransformers = modelTransformers;
    return this;
  }

  /**
   * Add a single model transformer.
   */
  public BaseWroManagerFactory addModelTransformer(final Transformer<WroModel> modelTransformer) {
    if (modelTransformers == null) {
      modelTransformers = new ArrayList<Transformer<WroModel>>();
    }
    this.modelTransformers.add(modelTransformer);
    return this;
  }

  /**
   * @param cacheStrategy
   *          the cacheStrategy to set
   */
  public BaseWroManagerFactory setCacheStrategy(final CacheStrategy<CacheKey, CacheValue> cacheStrategy) {
    this.cacheStrategy = cacheStrategy;
    return this;
  }

  /**
   * @param uriLocatorFactory
   *          the uriLocatorFactory to set
   */
  public BaseWroManagerFactory setUriLocatorFactory(final UriLocatorFactory uriLocatorFactory) {
    this.uriLocatorFactory = uriLocatorFactory;
    return this;
  }

  /**
   * @param processorsFactory
   *          the processorsFactory to set
   */
  public BaseWroManagerFactory setProcessorsFactory(final ProcessorsFactory processorsFactory) {
    this.processorsFactory = processorsFactory;
    return this;
  }

  public BaseWroManagerFactory setResourceAuthorizationManager(final ResourceAuthorizationManager authorizationManager) {
    this.authorizationManager = authorizationManager;
    return this;
  }

  public void setMetaDataFactory(final MetaDataFactory metaDataFactory) {
    this.metaDataFactory = metaDataFactory;
  }

  public BaseWroManagerFactory setResourceWatcher(final ResourceWatcher resourceWatcher) {
    this.resourceWatcher = resourceWatcher;
    return this;
  }

  /**
   * {@inheritDoc}
   */
  public void destroy() {
    managerInitializer.destroy();
  }
}


package ro.isdc.wro.manager;

import java.io.IOException;
import java.io.OutputStream;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.cache.CacheKey;
import ro.isdc.wro.cache.CacheStrategy;
import ro.isdc.wro.cache.CacheValue;
import ro.isdc.wro.cache.factory.CacheKeyFactory;
import ro.isdc.wro.config.Context;
import ro.isdc.wro.config.ReadOnlyContext;
import ro.isdc.wro.config.jmx.WroConfiguration;
import ro.isdc.wro.http.support.HttpHeader;
import ro.isdc.wro.model.group.Inject;
import ro.isdc.wro.model.resource.ResourceType;
import ro.isdc.wro.util.WroUtil;

/**
 * Encapsulates the bundle creation.
 *
 * @author Alex Objelean
 * @created 18 Jun 2012
 * @since 1.4.7
 */
public class ResourceBundleProcessor {
  private static final Logger LOG = LoggerFactory.getLogger(ResourceBundleProcessor.class);
  /**
   * A cacheStrategy used for caching processed results. <GroupName, processed result>.
   */
  @Inject
  private CacheStrategy<CacheKey, CacheValue> cacheStrategy;
  @Inject
  private ReadOnlyContext context;
  @Inject
  private CacheKeyFactory cacheKeyFactory;

  /**
   * Write to stream the content of the processed resource bundle.
   */
  public void serveProcessedBundle()
      throws IOException {
    final WroConfiguration configuration = context.getConfig();
    final HttpServletRequest request = context.getRequest();
    final HttpServletResponse response = context.getResponse();

    OutputStream os = null;
    try {

      final CacheKey cacheKey = getSafeCacheKey(request);
      initAggregatedFolderPath(request, cacheKey.getType());
      final CacheValue cacheValue = cacheStrategy.get(cacheKey);

      // TODO move ETag check in wroManagerFactory
      final String ifNoneMatch = request.getHeader(HttpHeader.IF_NONE_MATCH.toString());

      // enclose etag value in quotes to be compliant with the RFC
      final String etagValue = String.format("\"%s\"", cacheValue.getHash());

      if (etagValue != null && etagValue.equals(ifNoneMatch)) {
        LOG.debug("ETag hash detected: {}. Sending {} status code", etagValue, HttpServletResponse.SC_NOT_MODIFIED);
        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
        // because we cannot return null, return a stream containing nothing.
        // TODO close output stream?
        return;
      }
      /**
       * Set contentType before actual content is written, solves

       * issue341
       */
      response.setContentType(cacheKey.getType().getContentType() + "; charset=" + configuration.getEncoding());
      // set ETag header
      response.setHeader(HttpHeader.ETAG.toString(), etagValue);

      os = response.getOutputStream();
      if (cacheValue.getRawContent() != null) {
        // use gziped response if supported & Set content length based on gzip flag
        if (isGzipAllowed()) {
          response.setContentLength(cacheValue.getGzippedContent().length);
          // add gzip header and gzip response
          response.setHeader(HttpHeader.CONTENT_ENCODING.toString(), "gzip");
          response.setHeader("Vary", "Accept-Encoding");
          IOUtils.write(cacheValue.getGzippedContent(), os);
        } else {
          //using getRawContent().length() is not the same and can return 2Bytes smaller size.
          response.setContentLength(cacheValue.getRawContent().getBytes(configuration.getEncoding()).length);
          IOUtils.write(cacheValue.getRawContent(), os, configuration.getEncoding());
        }
      }
    } finally {
      if (os != null) {
        IOUtils.closeQuietly(os);
      }
    }
  }

  private CacheKey getSafeCacheKey(final HttpServletRequest request) {
    final CacheKey cacheKey = cacheKeyFactory.create(request);
    if (cacheKey == null) {
      throw new WroRuntimeException("Cannot build valid CacheKey from request: " + request.getRequestURI());
    }
    return cacheKey;
  }

  private boolean isGzipAllowed() {
    return context.getConfig().isGzipEnabled() && WroUtil.isGzipSupported(context.getRequest());
  }

  /**
   * Set the aggregatedFolderPath if required.
   */
  private void initAggregatedFolderPath(final HttpServletRequest request, final ResourceType type) {
    if (ResourceType.CSS == type && context.getAggregatedFolderPath() == null) {
      final String requestUri = request.getRequestURI();
      final String cssFolder = StringUtils.removeEnd(requestUri, FilenameUtils.getName(requestUri));
      final String aggregatedFolder = StringUtils.removeStart(cssFolder, request.getContextPath());
      LOG.debug("set aggregatedFolderPath: {}", aggregatedFolder);
      Context.get().setAggregatedFolderPath(aggregatedFolder);
    }
  }
}


/*
 * Copyright (c) 2009.
 */
package ro.isdc.wro.model.resource.processor.impl.css;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.ResourceType;
import ro.isdc.wro.model.resource.SupportedResourceType;
import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.util.WroUtil;

/**
 * Processor of css resources, responsible for replacing variables. (@see
 * http://disruptive-innovations.com/zoo/cssvariables/). This is a pre processor, because it makes sense to apply
 * variables only on the same css. <br/>
 * This processor is implemented as both: preprocessor & postprocessor.
 *
 * @author Alex Objelean
 * @created Created on Jul 05, 2009
 */
@SupportedResourceType(ResourceType.CSS)
public class CssVariablesProcessor
    implements ResourcePreProcessor, ResourcePostProcessor {
  private static final Logger LOG = LoggerFactory.getLogger(CssVariablesProcessor.class);
  public static final String ALIAS = "cssVariables";
  /**
   * Compiled pattern for REGEX_VARIABLES_DEFINITION regex.
   */
  private static final Pattern PATTERN_VARIABLES_DEFINITION = Pattern.compile(WroUtil.loadRegexpWithKey("cssVariables.definition"));
  /**
   * Compiled pattern for REGEX_VARIABLES_BODY pattern.
   */
  private static final Pattern PATTERN_VARIABLES_BODY = Pattern.compile(WroUtil.loadRegexpWithKey("cssVariables.body"));
  /**
   * Compiled pattern for REGEX_VARIABLE_HOLDER pattern.
   */
  private static final Pattern PATTERN_VARIABLE_HOLDER = Pattern.compile(WroUtil.loadRegexpWithKey("cssVariables.holder"));

  /**
   * Extract variables map from variables body.
   *
   * @param variablesBody
   *          string containing variables mappings.
   * @return map with extracted variables.
   */
  private Map<String, String> extractVariables(final String variablesBody) {
    final Map<String, String> map = new HashMap<String, String>();
    final Matcher m = PATTERN_VARIABLES_BODY.matcher(variablesBody);
    LOG.debug("parsing variables body");
    while (m.find()) {
      final String key = m.group(1);
      final String value = m.group(2);
      if (map.containsKey(key)) {
        LOG.warn("A duplicate variable name found with name: {} and value: {}.", key, value);
      }
      map.put(key, value);
    }
    return map;
  }

  /**
   * {@inheritDoc}
   */
  public void process(final Reader reader, final Writer writer)
      throws IOException {
    process(null, reader, writer);
  }

  /**
   * {@inheritDoc}
   */
  public void process(final Resource resource, final Reader reader, final Writer writer)
      throws IOException {
    try {
      final String css = IOUtils.toString(reader);
      final String result = parseCss(css);
      writer.write(result);
    } finally {
      reader.close();
      writer.close();
    }
  }

  /**
   * Parse css, find all defined variables & replace them.
   *
   * @param css
   *          to parse.
   */
  private String parseCss(final String css) {
    // map containing variables & their values
    final Map<String, String> map = new HashMap<String, String>();
    final StringBuffer sb = new StringBuffer();
    final Matcher m = PATTERN_VARIABLES_DEFINITION.matcher(css);
    while (m.find()) {
      final String variablesBody = m.group(1);
      // LOG.debug("variables body: " + variablesBody);
      // extract variables
      map.putAll(extractVariables(variablesBody));
      // remove variables definition
      m.appendReplacement(sb, "");
    }
    m.appendTail(sb);

    // LOG.debug("replaced variables: " + result);
    return replaceVariables(sb.toString(), map);
  }

  /**
   * Replace variables from css with provided variables map.
   *
   * @param cleanCss
   *          used to replace variable placeholders.
   * @param variables
   *          map of variables used for substitution.
   * @return css with all variables replaced.
   */
  private String replaceVariables(final String css, final Map<String, String> variables) {
    final StringBuffer sb = new StringBuffer();
    final Matcher m = PATTERN_VARIABLE_HOLDER.matcher(css);
    while (m.find()) {
      final String oldMatch = m.group();
      final String variableName = m.group(1);
      final String variableValue = variables.get(variableName);
      if (variableValue != null) {
        final String newReplacement = oldMatch.replace(oldMatch, variableValue);
        m.appendReplacement(sb, newReplacement.trim());
      } else {
        LOG.warn("No variable with name " + variableName + " was found!");
      }
    }
    m.appendTail(sb);
    return sb.toString();
  }
}


package ro.isdc.wro.model.resource.processor.decorator;

import static org.apache.commons.lang3.Validate.notNull;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

import org.apache.commons.io.IOUtils;

import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.processor.support.ProcessingCriteria;

/**
 * Decorates the processor with a set of common useful decorators during each processing.
 *
 * @author Alex Objelean
 * @created 7 Oct 2012
 * @since 1.6.0
 */
public class DefaultProcessorDecorator
    extends ProcessorDecorator {
  private final ProcessingCriteria criteria;

  public DefaultProcessorDecorator(final Object processor, final boolean minimize) {
    this(processor, ProcessingCriteria.createDefault(minimize));
  }

  public DefaultProcessorDecorator(final Object processor, final ProcessingCriteria criteria) {
    super(decorate(processor, criteria));
    this.criteria = criteria;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void process(final Resource resource, final Reader reader, final Writer writer)
      throws IOException {
    try {
      super.process(resource, reader, writer);
    } finally {
      IOUtils.closeQuietly(reader);
      IOUtils.closeQuietly(writer);
    }
  }

  private static ProcessorDecorator decorate(final Object processor, final ProcessingCriteria criteria) {
    notNull(criteria);
    return new BenchmarkProcessorDecorator(new ExceptionHandlingProcessorDecorator(
        new SupportAwareProcessorDecorator(new MinimizeAwareProcessorDecorator(new ImportAwareProcessorDecorator(
            processor, criteria.getProcessingType())))));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected boolean isEnabled(final Resource resource) {
    final boolean isApplicable = resource != null ? isEligible(criteria.isMinimize(), resource.getType()) : true;
    return super.isEnabled(resource) && isApplicable;
  }
}


/*
 * Licensed 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 ro.isdc.wro.model.resource.support.naming;

import java.io.InputStream;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;

/**
 * This naming strategy append a timestamp to the name of the file. This is especially useful when wro4j is used at
 * build time with the maven plugin.
 *
 * @author Julien Wajsberg
 * @since 1.4.3
 */
public class TimestampNamingStrategy
    implements NamingStrategy {
  /**
   * Alias used identify this implementation when configuring which {@link NamingStrategy} to use.
   */
  public static final String ALIAS = "timestamp";
  /**
   * {@inheritDoc}
   */
  public String rename(final String originalName, final InputStream inputStream) {
    final String baseName = FilenameUtils.getBaseName(originalName);
    final String extension = FilenameUtils.getExtension(originalName);
    final long timestamp = getTimestamp();
    final StringBuilder sb = new StringBuilder(baseName).append("-").append(timestamp);
    if (!StringUtils.isEmpty(extension)) {
      sb.append(".").append(extension);
    }
    return sb.toString();
  }

  /* protected to make the class testable. Should we ? */
  protected long getTimestamp() {
    return System.currentTimeMillis();
  }

}


package ro.isdc.wro.cache;

import java.util.Map;

import ro.isdc.wro.cache.impl.LruMemoryCacheStrategy;
import ro.isdc.wro.cache.spi.CacheStrategyProvider;
import ro.isdc.wro.model.resource.support.AbstractConfigurableSingleStrategy;

/**
 * Uses the {@link CacheStrategy} implementation associated with an alias read from properties file.
 *
 * @author Alex Objelean
 * @created 22 Sep 2012
 * @since 1.5.0
 */
public class ConfigurableCacheStrategy
    extends AbstractConfigurableSingleStrategy<CacheStrategy<CacheKey, CacheValue>, CacheStrategyProvider>
    implements CacheStrategy<CacheKey, CacheValue> {
  /**
   * Property name to specify alias.
   */
  public static final String KEY = "cacheStrategy";

  /**
   * {@inheritDoc}
   */
  public void clear() {
    getConfiguredStrategy().clear();
  }

  /**
   * {@inheritDoc}
   */
  public void destroy() {
    getConfiguredStrategy().destroy();
  }

  /**
   * {@inheritDoc}
   */
  public CacheValue get(final CacheKey key) {
    return getConfiguredStrategy().get(key);
  }

  /**
   * {@inheritDoc}
   */
  public void put(final CacheKey key, final CacheValue value) {
    getConfiguredStrategy().put(key, value);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected String getStrategyKey() {
    return KEY;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected CacheStrategy<CacheKey, CacheValue> getDefaultStrategy() {
    return new LruMemoryCacheStrategy<CacheKey, CacheValue>();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected Map<String, CacheStrategy<CacheKey, CacheValue>> getStrategies(final CacheStrategyProvider provider) {
    return provider.provideCacheStrategies();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected Class<CacheStrategyProvider> getProviderClass() {
    return CacheStrategyProvider.class;
  }
}


/**
 * Copyright wro4j@2011
 */
package ro.isdc.wro.model.resource.processor.decorator;

import static org.apache.commons.lang3.Validate.notNull;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.ResourceType;
import ro.isdc.wro.model.resource.SupportedResourceType;
import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;

/**
 * Default implementation which can decorate a processor. This class is still named {@link ProcessorDecorator}, though
 * it is not abstract (for backward compatibility reasons). It will be renamed to ProcessorDecorator.
 *
 * @author Alex Objelean
 * @created 16 Sep 2011
 * @since 1.4.1
 */
public class ProcessorDecorator
    extends AbstractProcessorDecoratorSupport<ResourcePreProcessor> {
  private static final Logger LOG = LoggerFactory.getLogger(ProcessorDecorator.class);

  /**
   * Hides the postProcessor adaptation logic. This exist due to differences between pre & post processor interface.
   * This will be removed in 1.5.0 when all processors will have an unified interface.
   */
  public ProcessorDecorator(final Object processor) {
    super(transform(processor));
  }

  private static ResourcePreProcessor transform(final Object processor) {
    notNull(processor);
    if (processor instanceof ResourcePreProcessor) {
      return (ResourcePreProcessor) processor;
    } else if (processor instanceof ResourcePostProcessor) {
      return toPreProcessor((ResourcePostProcessor) processor);
    } else {
      throw new IllegalArgumentException("Invalid processor: " + processor);
    }
  }

  /**
   * Transforms a post processor into pre processor.
   */
  private static ResourcePreProcessor toPreProcessor(final ResourcePostProcessor postProcessor) {
    return new AbstractProcessorDecoratorSupport<ResourcePostProcessor>(postProcessor) {
      public void process(final Resource resource, final Reader reader, final Writer writer)
          throws IOException {
        postProcessor.process(reader, writer);
      }

      @Override
      protected boolean isMinimizeInternal() {
        return isMinimizeForProcessor(postProcessor);
      }

      @Override
      protected SupportedResourceType getSupportedResourceTypeInternal() {
        return getSupportedResourceTypeForProcessor(postProcessor);
      }

      @Override
      public String toString() {
        return postProcessor.toString();
      }
    };
  }

  public void process(final Resource resource, final Reader reader, final Writer writer)
      throws IOException {
    if (isEnabled(resource)) {
      LOG.debug("Applying processor: {}", toString());
      getDecoratedObject().process(resource, reader, writer);
    } else {
      LOG.debug("Skipping processor: {}", getDecoratedObject());
      IOUtils.copy(reader, writer);
    }
  }

  /**
   * Indicates if the processor is eligible for usage based on provided criteria.
   *
   * @param minimize
   *          - when true the processor should be minimize aware.
   * @param searchedType
   *          - the type of the accepted processor. If the processor will have no type specified it will still be
   *          eligible.
   * @return true if this processor is of searchedType.
   */
  public final boolean isEligible(final boolean minimize, final ResourceType searchedType) {
    Validate.notNull(searchedType);

    final SupportedResourceType supportedType = getSupportedResourceType();
    final boolean isTypeSatisfied = supportedType == null
        || (supportedType != null && searchedType == supportedType.value());
    final boolean isMinimizedSatisfied = minimize == true || !isMinimize();
    return isTypeSatisfied && isMinimizedSatisfied;
  }

  /**
   * @param resource
   *          {@link ResourcePreProcessor} for which enabled flag should be checked.
   * @return a flag indicating if this processor is enabled. When false, the processing will be skipped and the content
   *         will be left unchanged. This value is true by default.
   */
  protected boolean isEnabled(final Resource resource) {
    return true;
  }

  @Override
  public String toString() {
    return getDecoratedObject().toString();
  }
}


/*
 * Copyright (C) 2011.
 * All rights reserved.
 */
package ro.isdc.wro.extensions.processor.js;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.extensions.processor.support.ObjectPoolHelper;
import ro.isdc.wro.extensions.processor.support.linter.AbstractLinter;
import ro.isdc.wro.extensions.processor.support.linter.LinterException;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.ResourceType;
import ro.isdc.wro.model.resource.SupportedResourceType;
import ro.isdc.wro.model.resource.processor.Destroyable;
import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.util.ObjectFactory;

/**
 * Processor which analyze the js code and warns you about any problems. The processing result won't change no matter
 * if the processed script contains errors or not.
 *
 * @author Alex Objelean
 * @since 1.3.5
 * @created 1 Mar 2011
 */
@SupportedResourceType(ResourceType.JS)
public abstract class AbstractLinterProcessor
  implements ResourcePreProcessor, ResourcePostProcessor, Destroyable {
  private static final Logger LOG = LoggerFactory.getLogger(AbstractLinterProcessor.class);
  private ObjectPoolHelper<AbstractLinter> enginePool;
  /**
   * Options to use to configure the linter.
   */
  private String options;

  public AbstractLinterProcessor() {
    enginePool = new ObjectPoolHelper<AbstractLinter>(new ObjectFactory<AbstractLinter>() {
      @Override
      public AbstractLinter create() {
        return newLinter();
      }
    });
  }

  @Override
  public void process(final Resource resource, final Reader reader, final Writer writer)
    throws IOException {
    final String content = IOUtils.toString(reader);
    final AbstractLinter linter = enginePool.getObject();
    try {
      // TODO investigate why linter fails when trying to reuse the same instance twice
      linter.setOptions(getOptions()).validate(content);
    } catch (final LinterException e) {
      onLinterException(e, resource);
    } catch (final WroRuntimeException e) {
      onException(e);
      final String resourceUri = resource == null ? StringUtils.EMPTY : "[" + resource.getUri() + "]";
      LOG.warn("Exception while applying " + getClass().getSimpleName() + " processor on the " + resourceUri
          + " resource, no processing applied...", e);
    } finally {
      // don't change the processed content no matter what happens.
      writer.write(content);
      reader.close();
      writer.close();
      enginePool.returnObject(linter);
    }
  }

  /**
   * Invoked when a processing exception occurs.
   */
  protected void onException(final WroRuntimeException e) {
    throw e;
  }

  @Override
  public void process(final Reader reader, final Writer writer) throws IOException {
    process(null, reader, writer);
  }

  /**
   * Called when {@link LinterException} is thrown. Allows subclasses to re-throw this exception as a
   * {@link RuntimeException} or handle it differently. The default implementation simply logs the errors.
   *
   * @param e {@link LinterException} which has occurred.
   * @param resource the processed resource which caused the exception.
   */
  protected void onLinterException(final LinterException e, final Resource resource) {
    LOG.error("The following resource: " + resource + " has " + e.getErrors().size() + " errors.", e);
  }

  /**
   * @return an options as CSV.
   */
  private String getOptions() {
    if (options == null) {
      options = createDefaultOptions();
    }
    return options;
  }

  /**
   * @return default options to use for linting.
   */
  protected String createDefaultOptions() {
    return StringUtils.EMPTY;
  }

  /**
   * @param options comma separated list of options.
   */
  public AbstractLinterProcessor setOptionsAsString(final String options) {
    this.options = options;
    return this;
  }

  /**
   * @return the linter to use for js code validation.
   */
  protected abstract AbstractLinter newLinter();

  @Override
  public void destroy() throws Exception {
    enginePool.destroy();
  }
}


package ro.isdc.wro.http;

import static org.apache.commons.lang3.Validate.notNull;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.config.factory.ServletContextPropertyWroConfigurationFactory;
import ro.isdc.wro.config.jmx.WroConfiguration;
import ro.isdc.wro.http.support.ServletContextAttributeHelper;
import ro.isdc.wro.manager.factory.DefaultWroManagerFactory;
import ro.isdc.wro.manager.factory.WroManagerFactory;

/**
 * A listener which loads wroConfiguration and stores it in servletContext. If you want to have multiple listeners,
 * extend this class and override the {@link WroServletContextListener#getListenerName()} to provide a unique non-empty
 * name (defaulted to "default").
 *
 * @author Alex Objelean
 * @created 6 May 2012
 * @since 1.4.6
 */
public class WroServletContextListener
    implements ServletContextListener {
  private static final Logger LOG = LoggerFactory.getLogger(WroServletContextListener.class);
  private WroConfiguration configuration;
  private WroManagerFactory managerFactory;
  private ServletContext servletContext;
  private ServletContextAttributeHelper attributeHelper;

  /**
   * {@inheritDoc}
   */
  public final void contextInitialized(final ServletContextEvent event) {
    this.servletContext = event.getServletContext();
    attributeHelper = new ServletContextAttributeHelper(this.servletContext, getListenerName());
    initListener(event.getServletContext());
  }

  private void initListener(final ServletContext servletContext) {
    if (attributeHelper.getWroConfiguration() != null || attributeHelper.getManagerFactory() != null) {
      final String message = "Cannot initialize context because there is already a listener present - withName: "
          + getListenerName()
          + ". Check whether you have multiple listener* (of type WroServletContextListener) definitions in your web.xml!";
      LOG.error(message);
      throw new IllegalStateException(message);
    }
    // create configuration first because managerFactory require it during creation.
    this.configuration = createConfiguration();
    this.managerFactory = createManagerFactory();
    LOG.debug("Loaded managerFactory: {}", this.managerFactory.getClass());

    attributeHelper.setWroConfiguration(this.configuration);
    attributeHelper.setManagerFactory(this.managerFactory);
  }

  /**
   * @return a not null {@link WroConfiguration} object.
   */
  private WroConfiguration createConfiguration() {
    return this.configuration != null ? this.configuration : newConfiguration();
  }

  /**
   * Create the ContextLoader to use. Can be overridden in subclasses.
   *
   * @return the new ContextLoader
   */
  protected WroConfiguration newConfiguration() {
    return new ServletContextPropertyWroConfigurationFactory(servletContext).create();
  }

  /**
   * @return decorated instance of {@link WroManagerFactory}.
   */
  private WroManagerFactory createManagerFactory() {
    return this.managerFactory != null ? managerFactory : newManagerFactory();
  }

  /**
   * @return default implementation of {@link WroManagerFactory}.
   */
  protected WroManagerFactory newManagerFactory() {
    return DefaultWroManagerFactory.create(configuration);
  }

  /**
   * {@inheritDoc}
   */
  public final void contextDestroyed(final ServletContextEvent servletContextEvent) {
    attributeHelper.clear();
  }

  /**
   * Set the manager factory to be initialized by this listener.
   *
   * @param managerFactory
   *          a not null manager instance.
   */
  public final void setManagerFactory(final WroManagerFactory managerFactory) {
    notNull(managerFactory);
    this.managerFactory = managerFactory;
  }

  /**
   * @return the name of the listener. Override it to provide a different name if you need to configure multiple
   *         listener.
   */
  protected String getListenerName() {
    return ServletContextAttributeHelper.DEFAULT_NAME;
  }

  /**
   * Set the configuration to be initialized by this listener.
   *
   * @param configuration
   *          a not null configuration instance.
   */
  public final void setConfiguration(final WroConfiguration configuration) {
    notNull(configuration);
    this.configuration = configuration;
  }

  /**
   * @VisibleForTesting
   * @return the {@link WroConfiguration} object built by this listener.
   */
  final WroConfiguration getConfiguration() {
    return configuration;
  }

  /**
   * @VisibleForTesting
   * @return the {@link WroManagerFactory} object built by this listener.
   */
  final WroManagerFactory getManagerFactory() {
    return managerFactory;
  }
}


/*
 * Copyright (c) 2008. All rights reserved.
 */
package ro.isdc.wro.extensions.processor.js;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.dojotoolkit.shrinksafe.Compressor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.model.group.processor.Minimize;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.ResourceType;
import ro.isdc.wro.model.resource.SupportedResourceType;
import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.util.StopWatch;

import com.github.lltyk.rhino17r1.Context;

/**
 * Compresses javascript code using compressor implemented by Dojo Shrinksafe utility.
 *
 * @author Alex Objelean
 * @created Created on Dec 4, 2008
 */
@Minimize
@SupportedResourceType(ResourceType.JS)
public class DojoShrinksafeCompressorProcessor
  implements ResourcePostProcessor, ResourcePreProcessor {
  private static final Logger LOG = LoggerFactory.getLogger(DojoShrinksafeCompressorProcessor.class);
  public static final String ALIAS = "dojoShrinksafe";

  /**
   * {@inheritDoc}
   */
  public void process(final Reader reader, final Writer writer)
    throws IOException {
    process(null, reader, writer);
  }

  /**
   * {@inheritDoc}
   */
  public void process(final Resource resource, final Reader reader, final Writer writer)
    throws IOException {
    final StopWatch watch = new StopWatch();
    watch.start("pack");
    //initialize rhino context
    Context.enter();
    try {
      final String script = IOUtils.toString(reader);
      final String stripConsole = null; //normal, warn, all
      LOG.debug("compressing script: {}", StringUtils.abbreviate(script, 40));
      final String out = Compressor.compressScript(script, 0, 0, stripConsole);

      writer.write(out);
    } finally {
      Context.exit();
      reader.close();
      writer.close();
      watch.stop();
      LOG.debug(watch.prettyPrint());
    }
  }
}


package ro.isdc.wro.model.resource.processor.decorator;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.config.ReadOnlyContext;
import ro.isdc.wro.model.group.Inject;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.util.StopWatch;

/**
 * A decorator responsible for tracking the time spent with processing.
 *
 * @author Alex Objelean
 * @since 1.6.3
 * @created 12 Jan 2013
 */
public class BenchmarkProcessorDecorator
    extends ProcessorDecorator {
  @Inject
  private ReadOnlyContext context;
  private static final Logger LOG = LoggerFactory.getLogger(BenchmarkProcessorDecorator.class);
  public BenchmarkProcessorDecorator(final Object processor) {
    super(processor);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void process(final Resource resource, final Reader reader, final Writer writer)
      throws IOException {
    StopWatch stopWatch = null;
    if (isDebug()) {
      stopWatch = new StopWatch();
      before(stopWatch);
    }
    try {
      super.process(resource, reader, writer);
    } finally {
      if (isDebug()) {
        after(stopWatch);
      }
    }
  }

  /**
   * required to allow processor work even outside of Context cycle.
   */
  private boolean isDebug() {
    return context != null ? context.getConfig().isDebug() : true;
  }

  /**
   * @VisibleForTesting
   */
  void before(final StopWatch stopWatch) {
    stopWatch.start("Using " + this.toString());
  }

  /**
   * @VisibleForTesting
   */
  void after(final StopWatch stopWatch) {
    stopWatch.stop();
    LOG.debug(stopWatch.prettyPrint());
  }
}


/**
 * Copyright@2011 wro4j
 */
package ro.isdc.wro.model.resource.locator.factory;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.io.input.AutoCloseInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.model.resource.locator.UriLocator;
import ro.isdc.wro.model.resource.locator.support.LocatorProvider;
import ro.isdc.wro.model.resource.processor.factory.ProcessorsFactory;
import ro.isdc.wro.model.resource.support.AbstractConfigurableMultipleStrategy;

/**
 * A {@link ProcessorsFactory} implementation which is easy to configure using a {@link Properties} object.
 *
 * @author Alex Objelean
 * @created 30 Jul 2011
 * @since 1.4.0
 */
public class ConfigurableLocatorFactory
    extends AbstractConfigurableMultipleStrategy<UriLocator, LocatorProvider>
    implements UriLocatorFactory {
  private static final Logger LOG = LoggerFactory.getLogger(ConfigurableLocatorFactory.class);
  /**
   * Name of init param used to specify uri locators.
   */
  public static final String PARAM_URI_LOCATORS = "uriLocators";

  private final UriLocatorFactory locatorFactory = newLocatorFactory();

  @Override
  protected String getStrategyKey() {
    return PARAM_URI_LOCATORS;
  }

  @Override
  protected Map<String, UriLocator> getStrategies(final LocatorProvider provider) {
    return provider.provideLocators();
  }

  /**
   * {@inheritDoc}
   */
  public InputStream locate(final String uri)
      throws IOException {
    return new AutoCloseInputStream(locatorFactory.locate(uri));
  }

  /**
   * {@inheritDoc}
   */
  private UriLocatorFactory newLocatorFactory() {
    final SimpleUriLocatorFactory factory = new SimpleUriLocatorFactory();
    final List<UriLocator> locators = getConfiguredStrategies();
    for (final UriLocator locator : locators) {
      factory.addLocator(locator);
    }
    // use default when none provided
    if (locators.isEmpty()) {
      LOG.debug("No locators configured. Using Default locator factory.");
      return new DefaultUriLocatorFactory();
    }
    return factory;
  }

  /**
   * {@inheritDoc}
   */
  public UriLocator getInstance(final String uri) {
    return locatorFactory.getInstance(uri);
  }

  @Override
  protected Class<LocatorProvider> getProviderClass() {
    return LocatorProvider.class;
  }
}


/*
 * Copyright (c) 2017. All rights reserved.
 */
package ro.isdc.wro.model.resource.locator;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.model.resource.locator.support.LocatorProvider;

/**
 * Adjusted loader class for saas resources "scss" files. As so imports can now the achieved using
 * the following configuration in the wro.properties:
 *
 *

 * managerFactoryClassName=ro.isdc.wro.manager.factory.ConfigurableWroManagerFactory
 * preProcessors=lessCssImport
 * postProcessors=rubySassCss
 * uriLocators=webjar,saasUri,uri,classpath
 *

 *
 * @author Paul Sterl
 * @created Created on Jun 22 2017
 */
public class SaasUriLocator implements UriLocator {
    private static final Logger LOG = LoggerFactory.getLogger(SaasUriLocator.class);
    /**
     * Alias used to register this locator with {@link LocatorProvider}.
     */
    public static final String ALIAS = "saasUri";

    /**
     * {@inheritDoc}
     */
    public boolean accept(final String url) {
        if (url == null) return false;
        final String extension = FilenameUtils.getExtension(url);
        // scss file have either no extension or scss
        // maybe check for the "_"?
        if ("".equals(extension) || "scss".equals(extension)) {
            boolean result = getScssFile(url) != null;
            if (!result) {
                LOG.debug("Possible scss file not found {}", url);
            }
            return result;
        } else {
            return false;
        }
    }

    File getScssFile(String url) {
        if (url == null) return null;
        File result;
        // remove any "file:" at start
        if (url.startsWith("file:")) url = url.replace("file:", "");

        result = new File(url);
        if (result.isFile()) return result;
        result = new File(url + ".scss");
        if (result.isFile()) return result;

        // if we don't have a scss file end the ending isn't a / it is most likely an import
        if (!url.endsWith(".scss") && !url.endsWith("/")) {
            final int lastSlash = url.lastIndexOf('/') + 1;
            String cleanUrl = url.substring(0, lastSlash);
            cleanUrl = cleanUrl + "_" + url.substring(lastSlash, url.length()) + ".scss";
            result = new File(cleanUrl);
        } else {
            result = new File(url);
        }

        if (result.isFile()) return result;
        else return null;
    }

    /**
     * {@inheritDoc}
     */
    public InputStream locate(final String uri) throws IOException {
        Validate.notNull(uri, "URI cannot be NULL!");
        LOG.debug("loading  scss file: {}", uri);
        return new FileInputStream(getScssFile(uri));
    }
}


/**
 * Copyright Alex Objelean
 */
package ro.isdc.wro.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.model.resource.processor.Destroyable;

/**
 * A subclass of {@link LazyInitializer} allows to destroy the created object.
 *
 * @author Alex Objelean
 * @created 24 Oct 2011
 * @since 1.4.2
 */
public abstract class DestroyableLazyInitializer<T> extends LazyInitializer<T> {
  private static final Logger LOG = LoggerFactory.getLogger(DestroyableLazyInitializer.class);
  /**
   * Destroy the initialized object. This will trigger the re-initialization when
   * {@link DestroyableLazyInitializer#get()} method is invoked.
   */
  public void destroy() {
    if (isInitialized()) {
      if (get() instanceof Destroyable) {
        try {
          ((Destroyable) get()).destroy();
        } catch (final Exception e) {
          LOG.error("destroy operation failed", e);
        }
      }
    }
    object = null;
  }
}


package ro.isdc.wro.model.resource.processor.impl.css;

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

import ro.isdc.wro.model.resource.processor.support.CssUrlInspector;
import ro.isdc.wro.model.resource.processor.support.FallbackCssDataUriUrlInspector;

/**
 * Preserves the original css uri along with the new one. This should work also with browsers which do not support
 * dataURI's.
 *

 * Sample Input:
 *
 *

 * div {
 *   background: url('image.png');
 * }
 *

 *
 * Sample output:
 *
 *

 *  div {
 *   background: url('image.png');
 *   background: url('data:image/png;base64,iVBORw0...');
 * }
 *

 *
 * Applies the graceful degradation technique. For example, if browser can't parse second rule, it'll use first one.
 *
 * @author Alex Objelean
 * @created 4 Jun 2012
 * @since 1.4.7
 */
public class FallbackCssDataUriProcessor
    extends CssDataUriPreProcessor {
  private static final Logger LOG = LoggerFactory.getLogger(FallbackCssDataUriProcessor.class);
  private static final String SEPARATOR = ";";
  public static final String ALIAS = "fallbackCssDataUri";

  /**
   * {@inheritDoc}
   */
  @Override
  protected String replaceDeclaration(final String originalDeclaration, final String modifiedDeclaration) {
    return originalDeclaration.equals(modifiedDeclaration) ? modifiedDeclaration : computeNewDeclaration(
        originalDeclaration, modifiedDeclaration);
  }

  /**
   * @return the new declaration which contains both: old and new modified declarations.
   */
  private String computeNewDeclaration(final String originalDeclaration, final String modifiedDeclaration) {
    LOG.debug("originalDeclaration: {}", originalDeclaration);
    LOG.debug("modifiedDeclaration: {}", modifiedDeclaration);
    // helps to avoid duplicate unnecessary separator
    final String separator = originalDeclaration.trim().endsWith(SEPARATOR) ? StringUtils.EMPTY : SEPARATOR;
    return originalDeclaration + separator + modifiedDeclaration;
  }

  @Override
  protected CssUrlInspector newCssUrlInspector() {
    return new FallbackCssDataUriUrlInspector();
  }
}


package ro.isdc.wro.http;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.GZIPOutputStream;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.CountingOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.http.support.HttpHeader;
import ro.isdc.wro.http.support.RedirectedStreamServletResponseWrapper;
import ro.isdc.wro.model.resource.locator.support.DispatcherStreamLocator;
import ro.isdc.wro.util.WroUtil;

/**
 * A filter responsible for gzipping all content served through this filter.
 *
 * @author Alex Objelean
 * @created 11 Apr 2013
 * @since 1.7.0
 */
public class GzipFilter
    implements Filter {
  private static final Logger LOG = LoggerFactory.getLogger(GzipFilter.class);

  /**
   * {@inheritDoc}
   */
  public void init(final FilterConfig filterConfig)
      throws ServletException {
  }

  /**
   * {@inheritDoc}
   */
  public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain)
      throws IOException, ServletException {
    final HttpServletRequest request = (HttpServletRequest) req;
    final HttpServletResponse response = (HttpServletResponse) res;

    if (isGzipAllowed(request)) {
      doGzipResponse(request, response, chain);
    } else {
      LOG.debug("Gzip not allowed. Proceeding with chain.");
      chain.doFilter(request, response);
    }
  }

  /**
   * Performs actual gzip of the filtered content.
   */
  private void doGzipResponse(final HttpServletRequest req, final HttpServletResponse response, final FilterChain chain)
      throws IOException, ServletException {
    LOG.debug("Applying gzip on resource: " + req.getRequestURI());
    response.setHeader(HttpHeader.CONTENT_ENCODING.toString(), "gzip");
    final ByteArrayOutputStream baos = new ByteArrayOutputStream();
    final CountingOutputStream countingStream = new CountingOutputStream(new GZIPOutputStream(new BufferedOutputStream(
        baos)));
    // final GZIPOutputStream gzout = new GZIPOutputStream(new BufferedOutputStream(baos));
    // Perform gzip operation in-memory before sending response
    final HttpServletResponseWrapper wrappedResponse = new RedirectedStreamServletResponseWrapper(countingStream,
        response);
    chain.doFilter(req, wrappedResponse);
    // close underlying stream
    countingStream.close();
    response.setContentLength(countingStream.getCount());
    // avoid NO CONTENT error thrown by jetty when gzipping empty response
    if (countingStream.getCount() > 0) {
      IOUtils.write(baos.toByteArray(), response.getOutputStream());
    }
  }

  /**
   * Checks if the request supports gzip and is not a include request (these cannot be gzipped)
   */
  private boolean isGzipAllowed(final HttpServletRequest request) {
    return !DispatcherStreamLocator.isIncludedRequest(request) && WroUtil.isGzipSupported(request);
  }

  /**
   * {@inheritDoc}
   */
  public void destroy() {
  }

}


package ro.isdc.wro.extensions.processor;

import static org.apache.commons.lang3.Validate.notEmpty;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Arrays;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.AntPathMatcher;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.processor.decorator.ProcessorDecorator;
import ro.isdc.wro.util.WroUtil;

/**
 * A {@link ProcessorDecorator} with Ant path style support. This processor requires AntPathMatcher provided by spring
 * framework.
 *

 * The implementation is inspired from mwa project created by
 * Edgar Espina.
 *
 * @author Alex Objelean
 * @since 1.5.0
 * @created 15 Sep 2012
 */
public final class PathPatternProcessorDecorator
    extends ProcessorDecorator {
  private static final Logger LOG = LoggerFactory.getLogger(PathPatternProcessorDecorator.class);
  /**
   * The ant path patterns.
   */
  private final String[] patterns;

  /**
   * The path matcher.
   */
  private final AntPathMatcher matcher;

  /**
   * Shall we include paths?
   */
  private final boolean includes;

  /**
   * Decorates the processor which should be applied or not (based on includes parameter) on resources which matches the
   * provided patterns.
   *
   * @param processor The processor to decorate.
   * @param includes the flag indicating if the patterns should be used for inclusion or exclusion.
   * @param patterns an array of patterns.
   */
  private PathPatternProcessorDecorator(final Object processor, final boolean includes, final String... patterns) {
    super(processor);
    notEmpty(patterns, "A pattern set is required.");
    this.includes = includes;
    matcher = new AntPathMatcher();
    this.patterns = patterns;
    LOG.debug("{} patterns {}", includes ? "include" : "exclude", Arrays.toString(patterns));
  }

  /**
   * Decorates a processor which will be applied on provided patterns.
   *
   * @param processor
   *          The processor to decorate.
   * @param patterns
   *          an array of patterns on which the processor will be applied.
   * @return decorated processor.
   */
  public static PathPatternProcessorDecorator include(final Object processor, final String... patterns) {
    return new PathPatternProcessorDecorator(processor, true, patterns);
  }

  /**
   * Decorates a processor which will not be applied on provided patterns.
   *
   * @param processor
   *          The processor to decorate.
   * @param patterns
   *          an array of patterns on which the processor will not be applied.
   * @return decorated processor.
   */
  public static PathPatternProcessorDecorator exclude(final Object processor, final String... patterns) {
    return new PathPatternProcessorDecorator(processor, false, patterns);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void process(final Resource resource, final Reader reader,
      final Writer writer)
      throws IOException {
    if (resource != null) {
      final String uri = resource.getUri();
      LOG.debug("matching uri: {}", uri);
      if (includes) {
        // Match (p1 OR p2 OR .. pn)
        for (String pattern : patterns) {
          if (matcher.match(pattern, uri)) {
            LOG.debug("Processing resource: {}. Match found: {}",
                uri, toString());
            getDecoratedObject().process(resource, reader, writer);
            return;
          }
        }
      } else {
        boolean process = true;
        // Match !(p1 AND p2 AND .. pn)
        for (String pattern : patterns) {
          if (matcher.match(pattern, uri)) {
            process = false;
            break;
          }
        }
        if (process) {
          LOG.debug("Processing resource: {}. Match found: {}", uri,
              toString());
          getDecoratedObject().process(resource, reader, writer);
          return;
        }
      }
      LOG.debug("Skipping {} from {}. No match found: {}", new Object[] {
          uri, getDecoratedObject(), toString() });
      WroUtil.safeCopy(reader, writer);
    } else {
      throw new WroRuntimeException("Wrong usage of "
          + toString() + ". Please use it as a pre-processor.");
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString() {
    final String processorName = getOriginalDecoratedObject().getClass().getSimpleName();
    StringBuilder buffer = new StringBuilder(processorName).append(": ").append(includes ? "(" : "!(");
    String separator = includes ? " || " : " && ";
    for (String pattern : patterns) {
      buffer.append(pattern).append(separator);
    }
    buffer.setLength(buffer.length() - separator.length());
    buffer.append(")");
    return buffer.toString();
  }
}


package ro.isdc.wro.maven.plugin.support;

import static org.apache.commons.lang3.Validate.notNull;

import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.maven.plugin.logging.Log;
import org.sonatype.plexus.build.incremental.BuildContext;

import ro.isdc.wro.manager.WroManager;
import ro.isdc.wro.manager.factory.WroManagerFactory;
import ro.isdc.wro.model.group.processor.InjectorBuilder;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.ResourceType;
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.ExceptionHandlingProcessorDecorator;
import ro.isdc.wro.model.resource.processor.impl.css.AbstractCssImportPreProcessor;
import ro.isdc.wro.model.resource.processor.impl.css.CssImportPreProcessor;
import ro.isdc.wro.model.resource.support.hash.HashStrategy;
import ro.isdc.wro.util.Function;

import com.google.common.annotations.VisibleForTesting;

/**
 * Encapsulates the details about resource change detection and persist the change information in build context.
 *
 * @author Alex Objelean
 * @created 2 Oct 2013
 * @since 1.7.2
 */
public class ResourceChangeHandler {
  private enum ChangeStatus {
    CHANGED, NOT_CHANGED
  }

  private WroManagerFactory managerFactory;
  private Log log;
  /**
   * Responsible for build storage persistence. Uses configured {@link BuildContext} as a primary storage object.
   */
  private BuildContextHolder buildContextHolder;
  private BuildContext buildContext;
  private File buildDirectory;
  private boolean incrementalBuildEnabled;
  /**
   * Contains the set of already remembered resources. Used to avoid duplicate hash computation.
   */
  private final Set<String> rememberedSet = new HashSet<String>();

  /**
   * Factory method which requires all mandatory fields.
   */
  public static ResourceChangeHandler create(final WroManagerFactory managerFactory, final Log log) {
    notNull(managerFactory, "WroManagerFactory was not set");
    notNull(log, "Log was not set");
    return new ResourceChangeHandler().setManagerFactory(managerFactory).setLog(log);
  }

  private ResourceChangeHandler() {
  }

  public boolean isResourceChanged(final Resource resource) {
    notNull(resource, "Invalid resource provided");

    final WroManager manager = getManagerFactory().create();
    final HashStrategy hashStrategy = manager.getHashStrategy();
    final UriLocatorFactory locatorFactory = manager.getUriLocatorFactory();
    // using AtomicBoolean because we need to mutate this variable inside an anonymous class.
    final AtomicBoolean changeDetected = new AtomicBoolean(false);
    try {
      final String fingerprint = hashStrategy.getHash(locatorFactory.locate(resource.getUri()));
      final String previousFingerprint = getBuildContextHolder().getValue(resource.getUri());

      final boolean newValue = fingerprint != null && !fingerprint.equals(previousFingerprint);
      changeDetected.set(newValue);

      if (!changeDetected.get() && resource.getType() == ResourceType.CSS) {
        final Reader reader = new InputStreamReader(locatorFactory.locate(resource.getUri()));
        getLog().debug("Check @import directive from " + resource);
        // detect changes in imported resources.
        detectChangeForCssImports(resource, reader, changeDetected);
      }
      return changeDetected.get();
    } catch (final IOException e) {
      getLog().error("failed to check for delta resource: " + resource, e);
    }
    return false;
  }

  private void detectChangeForCssImports(final Resource resource, final Reader reader,
      final AtomicBoolean changeDetected)
      throws IOException {
    forEachCssImportApply(new Function<String, ChangeStatus>() {
      public ChangeStatus apply(final String importedUri) throws Exception {
        final boolean isImportChanged = isResourceChanged(Resource.create(importedUri, ResourceType.CSS));
        getLog().debug("\tisImportChanged: " + isImportChanged);
        if (isImportChanged) {
          changeDetected.set(true);
          return ChangeStatus.CHANGED;
        }
        return ChangeStatus.NOT_CHANGED;
      }
    }, resource, reader);
  }

  /**
   * Will persist the information regarding the provided resource in some internal store. This information will be used
   * later to check if the resource is changed.
   *
   * @param resource
   *          {@link Resource} to touch.
   */
  public void remember(final Resource resource) {
    final WroManager manager = getManagerFactory().create();
    final HashStrategy hashStrategy = manager.getHashStrategy();
    final UriLocatorFactory locatorFactory = manager.getUriLocatorFactory();

    if (rememberedSet.contains(resource.getUri())) {
      // only calculate fingerprints and check imports if not already done
      getLog().debug("Resource with uri '" + resource.getUri() + "' has already been updated in this run.");
    } else {
      try {
        final String fingerprint = hashStrategy.getHash(locatorFactory.locate(resource.getUri()));
        getBuildContextHolder().setValue(resource.getUri(), fingerprint);
        rememberedSet.add(resource.getUri());
        getLog().debug("Persist fingerprint for resource '" + resource.getUri() + "' : " + fingerprint);
        if (resource.getType() == ResourceType.CSS) {
          final Reader reader = new InputStreamReader(locatorFactory.locate(resource.getUri()));
          getLog().debug("Check @import directive from " + resource);
          // persist fingerprints in imported resources.
          persistFingerprintsForCssImports(resource, reader);
        }
      } catch (final IOException e) {
        getLog().debug("could not check fingerprint of resource: " + resource);
      }
    }
  }

  private void persistFingerprintsForCssImports(final Resource resource, final Reader reader)
      throws IOException {
    forEachCssImportApply(new Function<String, ChangeStatus>() {
      public ChangeStatus apply(final String importedUri) throws Exception {
        remember(Resource.create(importedUri, ResourceType.CSS));
        return ChangeStatus.NOT_CHANGED;
      }
    }, resource, reader);
  }

  /**
   * Invokes the provided function for each detected css import.
   *
   * @param func
   *          a function (closure) invoked for each found import. It will be provided as argument the uri of imported
   *          css.
   */
  private void forEachCssImportApply(final Function<String, ChangeStatus> func, final Resource resource, final Reader reader)
      throws IOException {
    final ResourcePreProcessor processor = createCssImportProcessor(func);
    InjectorBuilder.create(getManagerFactory()).build().inject(processor);
    processor.process(resource, reader, new StringWriter());
  }

  private ResourcePreProcessor createCssImportProcessor(final Function<String, ChangeStatus> func) {
    final ResourcePreProcessor cssImportProcessor = new AbstractCssImportPreProcessor() {
      @Override
      protected void onImportDetected(final String importedUri) {
        getLog().debug("Found @import " + importedUri);
        try {
          final ChangeStatus status = func.apply(importedUri);
          getLog().debug("ChangeStatus for " + importedUri + ": " + status);
          if (ChangeStatus.NOT_CHANGED.equals(status)) {
        	  remember(Resource.create(importedUri, ResourceType.CSS));
          }
        } catch (final Exception e) {
          getLog().error("Cannot apply a function on @import resource: " + importedUri + ". Ignoring it.", e);
        }
        remember(Resource.create(importedUri, ResourceType.CSS));
      }

      @Override
      protected String doTransform(final String cssContent, final List<Resource> foundImports)
          throws IOException {
        // no need to build the content, since we are interested in finding imported resources only
        return "";
      }

      @Override
      public String toString() {
        return CssImportPreProcessor.class.getSimpleName();
      }
    };
    return new ExceptionHandlingProcessorDecorator(cssImportProcessor) {
      @Override
      protected boolean isIgnoreFailingProcessor() {
        return true;
      }
    };
  }

  private BuildContextHolder getBuildContextHolder() {
    if (buildContextHolder == null) {
      buildContextHolder = new BuildContextHolder(buildContext, buildDirectory);
      buildContextHolder.setIncrementalBuildEnabled(incrementalBuildEnabled);
    }
    return buildContextHolder;
  }

  @VisibleForTesting
  void setBuildContextHolder(final BuildContextHolder buildContextHolder) {
    this.buildContextHolder = buildContextHolder;
  }

  private WroManagerFactory getManagerFactory() {
    return managerFactory;
  }

  public Log getLog() {
    return log;
  }

  public ResourceChangeHandler setManagerFactory(final WroManagerFactory wroManagerFactory) {
    this.managerFactory = wroManagerFactory;
    return this;
  }

  public ResourceChangeHandler setLog(final Log log) {
    this.log = log;
    return this;
  }

  public ResourceChangeHandler setBuildContext(final BuildContext buildContext) {
    this.buildContext = buildContext;
    return this;
  }

  public ResourceChangeHandler setBuildDirectory(final File buildDirectory) {
    this.buildDirectory = buildDirectory;
    return this;
  }

  public ResourceChangeHandler setIncrementalBuildEnabled(final boolean incrementalBuildEnabled) {
    this.incrementalBuildEnabled = incrementalBuildEnabled;
    return this;
  }

  public boolean isIncrementalBuild() {
    return getBuildContextHolder().isIncrementalBuild();
  }

  /**
   * Destroys all information about resource tracked for changes.
   */
  public void destroy() {
    getBuildContextHolder().destroy();
    rememberedSet.clear();
  }

  /**
   * After invoking this method on a resource, the next invocation of {@link #isResourceChanged(Resource)} will return
   * true.
   *
   * @param resourceUri
   *          uri of the resource to clear from persisted storage.
   */
  public void forget(final Resource resource) {
    if (resource != null) {
      getBuildContextHolder().setValue(resource.getUri(), null);
      rememberedSet.remove(resource.getUri());
    }
  }

  /**
   * Persist the values stored in BuildContext(Holder)
   */
  public void persist() {
    getBuildContextHolder().persist();
  }
}


package ro.isdc.wro.model.resource.support.hash;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import org.apache.commons.io.IOUtils;

import ro.isdc.wro.model.resource.support.AbstractConfigurableSingleStrategy;

/**
 * Uses the {@link HashStrategy} implementation associated with an alias read from properties file.
 *
 * @author Alex Objelean
 * @created 17 Jun 2012
 * @since 1.4.7
 */
public class ConfigurableHashStrategy
    extends AbstractConfigurableSingleStrategy<HashStrategy, HashStrategyProvider>
    implements HashStrategy {
  /**
   * Property name to specify alias.
   */
  public static final String KEY = "hashStrategy";

  /**
   * {@inheritDoc}
   */
  public String getHash(final InputStream inputStream)
      throws IOException {
    try{
      return getConfiguredStrategy().getHash(inputStream);
	}finally{
      IOUtils.closeQuietly(inputStream);
	}
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected String getStrategyKey() {
    return KEY;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected HashStrategy getDefaultStrategy() {
    return new SHA1HashStrategy();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected Map<String, HashStrategy> getStrategies(final HashStrategyProvider provider) {
    return provider.provideHashStrategies();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected Class<HashStrategyProvider> getProviderClass() {
    return HashStrategyProvider.class;
  }
}


package ro.isdc.wro.extensions.support.lint;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;

/**
 * Encapsulates lint reports of type <T> found for a resource. This class is generic, because there are different
 * types of errors.
 *
 * @author Alex Objelean
 * @created 15 Sep 2012
 * @since 1.5.0
 */
public class ResourceLintReport<T> {
  /**
   * @VisibleForTesting
   */
  static final String UNKNOWN_PATH = "UnknownFile";
  private Collection<T> lints = new ArrayList<T>();
  private String resourcePath = UNKNOWN_PATH;

  /**
   * Required for JSON serialization.
   */
  public ResourceLintReport() {
  }

  /**
   * Factory method for creating a {@link ResourceLintReport} instance.
   *
   * @param resourcePath
   *          the path of the of the resource
   * @param lints
   *          a collection of lints associated with a resource.
   * @return {@link ResourceLintReport} with all lints set.
   */
  public static <T> ResourceLintReport<T> create(final String resourcePath, final Collection<T> lints) {
    return new ResourceLintReport<T>(resourcePath, lints);
  }

  private ResourceLintReport(final String resourcePath, final Collection<T> errors) {
    this.resourcePath = StringUtils.isEmpty(resourcePath) ? UNKNOWN_PATH : resourcePath;
    this.lints = filter(errors);
  }

  /**
   * This filtering is required, in order to ensure that no nulls are passed (which happens when using gson for
   * deserializing json collection.
   *
   * @param collection
   *          to filter.
   * @return a null free collection of item by filtering found nulls.
   */
  private List<T> filter(final Collection<T> collection) {
    final List<T> nullFreeList = new ArrayList<T>();
    if (collection != null) {
      for (final T item : collection) {
        if (item != null) {
          nullFreeList.add(item);
        }
      }
    }
    return nullFreeList;
  }

  /**
   * @return a readonly collection of errors containd in this resource.
   */
  public Collection<T> getLints() {
    return Collections.unmodifiableCollection(lints);
  }

  /**
   * @return the path to the resource containing errors.
   */
  public String getResourcePath() {
    return resourcePath;
  }

  public void setLints(final Collection<T> lints) {
    Validate.notNull(lints);
    this.lints = lints;
  }

  public void setResourcePath(final String resourcePath) {
    this.resourcePath = resourcePath == null ? UNKNOWN_PATH : resourcePath;
  }
}


/*
 * Copyright (c) 2009.
 */
package ro.isdc.wro.model.group;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.config.Context;
import ro.isdc.wro.model.resource.ResourceType;

/**
 * Default implementation capable of extracting a single group from the request.
 *
 * @author Alex Objelean
 * @created Created on Nov 3, 2008
 */
public class DefaultGroupExtractor
  implements GroupExtractor {
  private static final Logger LOG = LoggerFactory.getLogger(DefaultGroupExtractor.class);
  /**
   * The name of the attribute where the servlet path is stored when requestDispatcher.include is called.
   */
  public static final String ATTR_INCLUDE_PATH = "javax.servlet.include.servlet_path";
  /**
   * The name of the parameter used to decide if the group must be minimized.
   */
  public static final String PARAM_MINIMIZE = "minimize";

  /**
   * {@inheritDoc}
   */
  public String getGroupName(final HttpServletRequest request) {
    Validate.notNull(request);
    String uri = request.getRequestURI();
    // check if include or uri path are present and use one of these as request uri.
    final String includeUriPath = (String) request.getAttribute(ATTR_INCLUDE_PATH);
    uri = includeUriPath != null ? includeUriPath : uri;
    final String groupName = FilenameUtils.getBaseName(stripSessionID(uri));
    return StringUtils.isEmpty(groupName) ? null : groupName;
  }

  /**
   * Extracts the resource type, by parsing the uri & finds the extension. If extension is valid ('css' or 'js'),
   * returns corresponding ResourceType, otherwise throws exception.
   *

   * Valid examples of uri are: /context/somePath/test.js or /context/somePath/test.css
   * {@inheritDoc}
   */
  public ResourceType getResourceType(final HttpServletRequest request) {
    Validate.notNull(request);
    final String uri = request.getRequestURI();
    Validate.notNull(uri);
    ResourceType type = null;
    try {
      type = ResourceType.get(FilenameUtils.getExtension(stripSessionID(uri)));
    } catch (final IllegalArgumentException e) {
      LOG.debug("[FAIL] Cannot identify resourceType for uri: {}", uri);
    }
    return type;
  }

  /**
   * The uri is cleaned up (the ;jsessionID is removed).
   * @return the extension of the resource.
   */
  private String stripSessionID(final String uri) {
    return uri.replaceFirst("(?i)(;jsessionid.*)", "");
  }

  /**
   * {@inheritDoc}
   */
  public String encodeGroupUrl(final String groupName, final ResourceType resourceType, final boolean minimize) {
    return String.format("%s.%s?" + PARAM_MINIMIZE + "=%s", groupName, resourceType.name().toLowerCase(), minimize);
  }

  /**
   * The minimization is can be switched off only in debug mode.
   *
   * @return false if the request contains parameter {@link DefaultGroupExtractor#PARAM_MINIMIZE} with value false,
   *         otherwise returns true.
   */
  public boolean isMinimized(final HttpServletRequest request) {
    Validate.notNull(request);
    final String minimizeAsString = request.getParameter(PARAM_MINIMIZE);
    return !(Context.get().getConfig().isDebug() && "false".equalsIgnoreCase(minimizeAsString));
  }
}


/**
 * Copyright Alex Objelean
 */
package ro.isdc.wro.model.resource.processor.impl.js;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;

import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.ResourceType;
import ro.isdc.wro.model.resource.SupportedResourceType;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.util.WroUtil;

/**
 * A preProcessor, responsible for adding a ';' character to the end of each js file. This ensure that no errors occurs
 * after the merge.
 *
 * @author Alex Objelean
 */
@SupportedResourceType(ResourceType.JS)
public class SemicolonAppenderPreProcessor
  implements ResourcePreProcessor {
  public static final String ALIAS = "semicolonAppender";
  /**
   * check if the last character is a semicolon and append only if one is missing.
   */
  private static final String PATTERN = WroUtil.loadRegexpWithKey("javascript.hasSemicolon");

  /**
   * {@inheritDoc}
   */
  public void process(final Resource resource, final Reader reader, final Writer writer)
    throws IOException {
    try {
      final String script = IOUtils.toString(reader);
      writer.write(script);
      if (isSemicolonNeeded(script)) {
        writer.write(';');
      }
    } finally {
      reader.close();
      writer.close();
    }
  }

  /**
   * @param script script to process.
   * @return true if the processed content requires semicolon.
   */
  private boolean isSemicolonNeeded(final String script) {
    return !(script.matches(PATTERN) || StringUtils.isEmpty(script));
  }
}


/*
 * Copyright (C) 2011. All rights reserved.
 */
package ro.isdc.wro.extensions.processor.js;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.extensions.processor.support.ObjectPoolHelper;
import ro.isdc.wro.extensions.processor.support.jsonhpack.JsonHPack;
import ro.isdc.wro.model.group.processor.Minimize;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.ResourceType;
import ro.isdc.wro.model.resource.SupportedResourceType;
import ro.isdc.wro.model.resource.processor.Destroyable;
import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.util.ObjectFactory;

/**
 * A processor using json.hpack compression algorithm: @see https://github.com/WebReflection/json.hpack
 *
 * @author Alex Objelean
 * @since 1.3.8
 * @created 7 Jun 2011
 */
@Minimize
@SupportedResourceType(ResourceType.JS)
public class JsonHPackProcessor
    implements ResourcePreProcessor, ResourcePostProcessor, Destroyable {
  private static final Logger LOG = LoggerFactory.getLogger(JsonHPackProcessor.class);
  public static final String ALIAS_PACK = "jsonh-pack";
  public static final String ALIAS_UNPACK = "jsonh-unpack";
  /**
   * Engine.
   */
  private ObjectPoolHelper<JsonHPack> enginePool;
  /**
   * If true, the packing will be used, otherwise unpack.
   */
  private boolean pack;

  public JsonHPackProcessor(final boolean pack) {
    this.pack = pack;
    enginePool = new ObjectPoolHelper<JsonHPack>(new ObjectFactory<JsonHPack>() {
      @Override
      public JsonHPack create() {
        return newEngine();
      }
    });
  }

  public static JsonHPackProcessor packProcessor() {
    return new JsonHPackProcessor(true);
  }

  public static JsonHPackProcessor unpackProcessor() {
    return new JsonHPackProcessor(false);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void process(final Resource resource, final Reader reader, final Writer writer)
      throws IOException {
    final String content = IOUtils.toString(reader);
    try {
      writer.write(doProcess(content));
    } catch (final WroRuntimeException e) {
      onException(e);
      final String resourceUri = resource == null ? StringUtils.EMPTY : "[" + resource.getUri() + "]";
      LOG.warn("Exception while applying hpack processor on the " + resourceUri
          + " resource, no processing applied...", e);
    } finally {
      reader.close();
      writer.close();
    }
  }

  private String doProcess(final String content) {
    final JsonHPack engine = enginePool.getObject();
    try {
      if (pack) {
        return engine.pack(content);
      }
      return engine.unpack(content);
    } finally {
      enginePool.returnObject(engine);
    }
  }

  /**
   * Invoked when a processing exception occurs.
   */
  protected void onException(final WroRuntimeException e) {
    throw e;
  }

  /**
   * @return the {@link JsonHPack} engine implementation. Override it to provide a different version of the
   *         json.hpack.js library. Useful for upgrading the processor outside the wro4j release.
   */
  protected JsonHPack newEngine() {
    return new JsonHPack();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void process(final Reader reader, final Writer writer)
      throws IOException {
    process(null, reader, writer);
  }

  @Override
  public void destroy() throws Exception {
    enginePool.destroy();
  }
}


/**
 * Copyright Alex Objelean
 */
package ro.isdc.wro.model.resource.processor.impl.css;

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.config.Context;
import ro.isdc.wro.config.jmx.WroConfiguration;
import ro.isdc.wro.model.group.Inject;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.ResourceType;
import ro.isdc.wro.model.resource.SupportedResourceType;
import ro.isdc.wro.model.resource.locator.factory.UriLocatorFactory;
import ro.isdc.wro.model.resource.processor.ImportAware;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.model.resource.processor.support.CssImportInspector;
import ro.isdc.wro.util.StringUtils;
import ro.isdc.wro.util.WroUtil;

/**
 * CssImport Processor responsible for handling css @import statement. It is implemented as both:
 * preProcessor & postProcessor. It is necessary because preProcessor is responsible for updating model with found
 * imported resources, while post processor removes import occurrences.
 *

 * When processor finds an import which is not valid, it will check the
 * {@link WroConfiguration#isIgnoreMissingResources()} flag. If it is set to false, the processor will fail.
 *
 * @author Alex Objelean
 */
@SupportedResourceType(ResourceType.CSS)
public abstract class AbstractCssImportPreProcessor
  implements ResourcePreProcessor, ImportAware {
  private static final Logger LOG = LoggerFactory.getLogger(AbstractCssImportPreProcessor.class);
  /**
   * Contains a {@link UriLocatorFactory} reference injected externally.
   */
  @Inject
  private UriLocatorFactory uriLocatorFactory;
  /**
   * A map useful for detecting deep recursion. The key (correlationId) - identifies a processing unit, while the value
   * contains a pair between the list o processed resources and a stack holding recursive calls (value contained on this
   * stack is not important). This map is used to ensure that the processor is thread-safe and doesn't erroneously
   * detect recursion when running in concurrent environment (when processor is invoked from within the processor for
   * child resources).
   */
  private final Map<String, Pair<List<String>, Stack<String>>> contextMap = new ConcurrentHashMap<String, Pair<List<String>, Stack<String>>>() {
    /**
     * Make sure that the get call will always return a not null object. To avoid growth of this map, it is important to
     * call remove for each invoked get.
     */
    @Override
    public Pair<List<String>, Stack<String>> get(final Object key) {
      Pair<List<String>, Stack<String>> result = super.get(key);
      if (result == null) {
        final List<String> list = new ArrayList<String>();
        result = ImmutablePair.of(list, new Stack<String>());
        put(key.toString(), result);
      }
      return result;
    };
  };

  /**
   * Useful to check that there is no memory leak after processing completion.
   * @VisibleForTesting
   */
  protected final Map<String, Pair<List<String>, Stack<String>>> getContextMap() {
    return contextMap;
  }

  /**
   * {@inheritDoc}
   */
  public final void process(final Resource resource, final Reader reader, final Writer writer)
    throws IOException {
    LOG.debug("Applying {} processor", toString());
    validate();
    try {
      final String result = parseCss(resource, IOUtils.toString(reader));
      writer.write(result);
    } finally {
      //imkportant to avoid memory leak
      clearProcessedImports();
      reader.close();
      writer.close();
    }
  }

  /**
   * Checks if required fields were injected.
   */
  private void validate() {
    Validate.notNull(uriLocatorFactory);
  }

  /**
   * @param resource {@link Resource} to process.
   * @param cssContent Reader for processed resource.
   * @return css content with all imports processed.
   */
  private String parseCss(final Resource resource, final String cssContent)
    throws IOException {
    if (isImportProcessed(resource.getUri())) {
      LOG.debug("[WARN] Recursive import detected: {}", resource);
      onRecursiveImportDetected();
      return "";
    }
    final String importedUri = resource.getUri().replace(File.separatorChar,'/');
    addProcessedImport(importedUri);
    final List<Resource> importedResources = findImportedResources(resource.getUri(), cssContent);
    return doTransform(cssContent, importedResources);
  }

  private boolean isImportProcessed(final String uri) {
    return getProcessedImports().contains(uri);
  }

  private void addProcessedImport(final String importedUri) {
    final String correlationId = Context.getCorrelationId();
    contextMap.get(correlationId).getValue().push(importedUri);
    getProcessedImports().add(importedUri);
  }

  private List<String> getProcessedImports() {
    return contextMap.get(Context.getCorrelationId()).getKey();
  }

  private void clearProcessedImports() {
    final String correlationId = Context.getCorrelationId();
    final Stack<String> stack = contextMap.get(correlationId).getValue();
    if (!stack.isEmpty()) {
      stack.pop();
    }
    if (stack.isEmpty()) {
      contextMap.remove(correlationId);
    }
  }

  /**
   * Find a set of imported resources inside a given resource.
   */
  private List<Resource> findImportedResources(final String resourceUri, final String cssContent)
    throws IOException {
    // it should be sorted
    final List<Resource> imports = new ArrayList<Resource>();
    final String css = cssContent;
    final List<String> foundImports = findImports(css);
    for (final String importUrl : foundImports) {
      final Resource importedResource = createImportedResource(resourceUri, importUrl);
      // check if already exist
      if (imports.contains(importedResource)) {
        LOG.debug("[WARN] Duplicate imported resource: {}", importedResource);
      } else {
        imports.add(importedResource);
        onImportDetected(importedResource.getUri());
      }
    }
    return imports;
  }

  /**
   * Extracts a list of imports from css content.
   *
   * @return a list of found imports.
   */
  protected List<String> findImports(final String css) {
    return new CssImportInspector(css).findImports();
  }

  /**
   * Build a {@link Resource} object from a found importedResource inside a given resource.
   */
  private Resource createImportedResource(final String resourceUri, final String importUrl) {
    final String absoluteUrl = uriLocatorFactory.getInstance(importUrl) != null ? importUrl
        : computeAbsoluteUrl(resourceUri, importUrl);
    return Resource.create(absoluteUrl, ResourceType.CSS);
  }

  /**
   * Computes absolute url of the imported resource.
   *
   * @param relativeResourceUri uri of the resource containing the import statement.
   * @param importUrl found import url.
   * @return absolute url of the resource to import.
   */
  private String computeAbsoluteUrl(final String relativeResourceUri, final String importUrl) {
    final String folder = WroUtil.getFullPath(relativeResourceUri);
    // remove '../' & normalize the path.
    return StringUtils.cleanPath(folder + importUrl);
  }

  /**
   * Perform actual transformation of provided cssContent and the list of found import resources.
   *
   * @param cssContent
   *          the css to transform.
   * @param importedResources
   *          the list of found imports.
   */
  protected abstract String doTransform(final String cssContent, final List<Resource> importedResources)
      throws IOException;

  /**
   * Invoked when an import is detected. By default this method does nothing.
   * @param foundImportUri the uri of the detected imported resource
   */
  protected void onImportDetected(final String foundImportUri) {
  }

  /**
   * Invoked when a recursive import is detected. Used to assert the recursive import detection correct behavior. By
   * default this method does nothing.
   *
   * @VisibleForTesting
   */
  protected void onRecursiveImportDetected() {
  }

  /**
   * {@inheritDoc}
   */
  public boolean isImportAware() {
    //We want this processor to be applied when processing resources referred with @import directive
    return true;
  }
}