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

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

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.model.group.processor.Minimize;
import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;

/**
 * Default implementation of processors factory. Holds processors in an array list and provide methods which allow
 * adding single processors or a collection of processors.
 *
 * @author Alex Objelean
 * @created 20 Nov 2010
 */
public class SimpleProcessorsFactory
  implements ProcessorsFactory {
  private static final Logger LOG = LoggerFactory.getLogger(SimpleProcessorsFactory.class);
  /**
   * a list of pre processors.
   */
  private final Collection<ResourcePreProcessor> preProcessors = new ArrayList<ResourcePreProcessor>();
  /**
   * a list of post processors.
   */
  private final Collection<ResourcePostProcessor> postProcessors = new ArrayList<ResourcePostProcessor>();

  /**
   * {@inheritDoc}
   */
  public Collection<ResourcePreProcessor> getPreProcessors() {
    return Collections.unmodifiableCollection(preProcessors);
  }

  /**
   * {@inheritDoc}
   */
  public Collection<ResourcePostProcessor> getPostProcessors() {
    return Collections.unmodifiableCollection(postProcessors);
  }

  /**
   * {@inheritDoc}
   */
  public void setResourcePreProcessors(final Collection<ResourcePreProcessor> processors) {
    preProcessors.clear();
    if (processors != null) {
      preProcessors.addAll(processors);
    }
  }

  /**
   * {@inheritDoc}
   */
  public void setResourcePostProcessors(final Collection<ResourcePostProcessor> processors) {
    postProcessors.clear();
    if (processors != null) {
      postProcessors.addAll(processors);
    }
  }

  /**
   * Add a {@link ResourcePreProcessor}.
   */
  public SimpleProcessorsFactory addPreProcessor(final ResourcePreProcessor processor) {
    preProcessors.add(processor);
    return this;
  }

  /**
   * Add a {@link ResourcePostProcessor}.
   */
  public SimpleProcessorsFactory addPostProcessor(final ResourcePostProcessor processor) {
    if (processor.getClass().isAnnotationPresent(Minimize.class)) {
      //TODO move large messages to properties file
      LOG.warn("It is recommended to add minimize aware processors to " +
          "pre processors instead of post processor, otherwise you " +
          "won't be able to disable minimization on specific resources " +
          "using minimize='false' attribute.");
    }
    postProcessors.add(processor);
    return this;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString() {
    return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
  }
}


/*
 *  Copyright 2010.
 */
package ro.isdc.wro.extensions.processor.support.jsonhpack;

import java.io.InputStream;

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

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.extensions.script.RhinoScriptBuilder;
import ro.isdc.wro.util.StopWatch;
import ro.isdc.wro.util.WroUtil;

/**
 * The underlying implementation use the json.hpack project: {@link https://github.com/WebReflection/json.hpack}.
 *
 * @author Alex Objelean
 * @since 1.3.8
 */
public class JsonHPack {
  private static final Logger LOG = LoggerFactory.getLogger(JsonHPack.class);
  private static final String DEFAULT_JS = "json.hpack.min.js";

  /**
   * Initialize script builder for evaluation.
   */
  private RhinoScriptBuilder initScriptBuilder() {
    try {
      return RhinoScriptBuilder.newClientSideAwareChain().addJSON().evaluateChain(
        getScriptAsStream(), DEFAULT_JS);
    } catch (final Exception e) {
      LOG.error("Processing error:" + e.getMessage(), e);
      throw new WroRuntimeException("Processing error", e);
    }
  }

  /**
   * @return stream of the script.
   */
  protected InputStream getScriptAsStream() {
    return JsonHPack.class.getResourceAsStream(DEFAULT_JS);
  }

  public String unpack(final String rawData) {
    final StopWatch stopWatch = new StopWatch();
    stopWatch.start("initContext");
    final RhinoScriptBuilder builder = initScriptBuilder();
    stopWatch.stop();

    stopWatch.start("json.hunpack");

    final boolean isEnclosedInDoubleArray = isEnclosedInDoubleArray(rawData);
    String data = rawData;
    if (!isEnclosedInDoubleArray) {
      data = "[" + data + "]";
    }

    try {
      final String execute = "JSON.stringify(JSON.hunpack(eval(" + WroUtil.toJSMultiLineString(data) + ")));";
      final Object result = builder.evaluate(execute, "unpack");

      String resultAsString = String.valueOf(result);
      if (!isEnclosedInDoubleArray) {
        //remove [] characters in which the json is enclosed
        resultAsString = removeEnclosedArray(resultAsString);
      }
      return resultAsString;
    } finally {
      stopWatch.stop();
      LOG.debug(stopWatch.prettyPrint());
    }
  }

  /**
   * @param data css content to process.
   * @return processed css content.
   */
  public String pack(final String rawData) {

    final StopWatch stopWatch = new StopWatch();
    stopWatch.start("initContext");
    final RhinoScriptBuilder builder = initScriptBuilder();
    stopWatch.stop();

    stopWatch.start("json.hpack");
    final boolean isEnclosedInArray = isEnclosedInArray(rawData);
    String data = rawData;
    if (!isEnclosedInArray) {
      data = "[" + data + "]";
    }

    try {
      final String execute = "JSON.stringify(JSON.hpack(eval(" + WroUtil.toJSMultiLineString(data) + "), 4));";
      final Object result = builder.evaluate(execute, "pack");
      String resultAsString = String.valueOf(result);
      if (!isEnclosedInArray) {
        //remove [] characters in which the json is enclosed
        resultAsString = removeEnclosedArray(resultAsString);
      }
      return resultAsString;
    } finally {
      stopWatch.stop();
      LOG.debug(stopWatch.prettyPrint());
    }
  }

  /**
   * Remove first and last occurrence of '[' and ']' characters.
   */
  private String removeEnclosedArray(final String resultAsString) {
    return resultAsString.replaceFirst("(?ims)\\[", "").replaceFirst("(?ims)\\](?!.*\\])", "");
  }

  /**
   * Check if the string is enclosed with [] (array).
   * @param rawData string to test.
   */
  private boolean isEnclosedInArray(final String rawData) {
    return rawData.matches("(?ims)^\\s*\\[.*\\]");
  }

  /**
   * Check if the string is enclosed with [[]] (double array).
   * @param rawData string to test.
   */
  private boolean isEnclosedInDoubleArray(final String rawData) {
    return rawData.matches("(?ims)^\\s*\\[\\[.*\\]\\]");
  }
}


package ro.isdc.wro.util.provider;

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

import javax.imageio.spi.ServiceRegistry;

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

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.util.Ordered;

/**
 * Helps to find available providers of any supported type.
 *
 * @author Alex Objelean
 * @created 16 Jun 2012
 * @since 1.4.7
 */
public class ProviderFinder<T> {
  private static final Logger LOG = LoggerFactory.getLogger(ProviderFinder.class);

  private final Class<T> type;

  /**
   * @VisibleForTesting.
   * @param type
   *          the type of providers to find.
   */
  ProviderFinder(final Class<T> type) {
    this.type = type;
  }

  /**
   * Creates a {@link ProviderFinder} which will find providers of type provided as argument..
   *
   * @param type
   *          the type of providers to search.
   * @return {@link ProviderFinder} handling providers lookup.
   */
  public static <T> ProviderFinder<T> of(final Class<T> type) {
    return new ProviderFinder<T>(type);
  }

  /**
   * @return the list of all providers found in classpath. The returned list is sorted according to the {@link Ordered} interface (from {@link Ordered#LOWEST} to {@link Ordered#HIGHEST}.
   */
  public List<T> find() {
    final List<T> providers = new ArrayList<T>();
    try {
      final Iterator<T> iterator = lookupProviders(type);
      for (; iterator.hasNext();) {
        final T provider = iterator.next();
        LOG.debug("found provider: {}", provider);
        providers.add(provider);
      }
      collectConfigurableProviders(providers);
    } catch (final Exception e) {
      LOG.error("Failed to discover providers using ServiceRegistry. Cannot continue...", e);
      throw WroRuntimeException.wrap(e);
    }
    Collections.sort(providers, Ordered.DESCENDING_COMPARATOR);
    LOG.debug("found providers: {}", providers);
    return providers;
  }

  /**
   * Collects also providers of type {@link ConfigurableProvider} if the T type is a supertype of
   * {@link ConfigurableProvider}. If the type is already {@link ConfigurableProvider}, it will be ignored to avoid
   * adding of duplicate providers.
   *
   * @param providers
   *          the list where found providers will be added.
   */
  @SuppressWarnings("unchecked")
  private void collectConfigurableProviders(final List<T> providers) {
    if (type.isAssignableFrom(ConfigurableProvider.class) && (type != ConfigurableProvider.class)) {
      final Iterator<ConfigurableProvider> iterator = lookupProviders(ConfigurableProvider.class);
      for (; iterator.hasNext();) {
        final T provider = (T) iterator.next();
        LOG.debug("found provider: {}", provider);
        providers.add(provider);
      }
    }
  }

  /**
   * This method is useful for mocking the lookup operation. The implementation will try to use java.util.ServiceLoader
   * by default (available in jdk6) and will fallback to ServiceRegistry for earlier JDK versions. The reason for this
   * is to support GAE environment which doesn't contain the ServiceRegistry in its whitelist.
   *
   * @param providerClass
   *          the class of the provider to lookup.
   * @VisibleForTesting
   * @return the iterator of found providers.
   */
  @SuppressWarnings("unchecked")
  <P> Iterator<P> lookupProviders(final Class<P> providerClass) {
    LOG.debug("searching for providers of type : {}", providerClass);
    try {
      final Class<?> serviceLoader = getClass().getClassLoader().loadClass("java.util.ServiceLoader");
      LOG.debug("using {} to lookupProviders", serviceLoader.getName());
      return ((Iterable<P>) serviceLoader.getMethod("load", Class.class).invoke(serviceLoader, providerClass)).iterator();
    } catch (final Exception e) {
      LOG.debug("ServiceLoader is not available. Falling back to ServiceRegistry.", e);
    }
    LOG.debug("using {} to lookupProviders", ServiceRegistry.class.getName());
    return ServiceRegistry.lookupProviders(providerClass);
  }
}


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

import javax.servlet.FilterConfig;
import javax.servlet.ServletException;

import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.http.ConfigurableWroFilter;
import ro.isdc.wro.manager.factory.WroManagerFactory;

/**
 * SpringWroFilter.

 * A WroFilter capable to get a {@link WroManagerFactory} instance from spring
 * context. The filter must have initParam called targetBeanName
 * whose value will be looked up in the spring application context.
 *
 * @author Alex Objelean
 * @created Created on Dec 5, 2008
 */
public final class SpringWroFilter extends ConfigurableWroFilter {
  /**
   * Init param for target bean name - used to retrieve factory instance from
   * spring application context.
   */
  private static final String PARAM_TARGET_BEAN_NAME = "targetBeanName";

  /**
   * Default target bean name.
   */
  private static final String DEFAULT_TARGET_BEAN_NAME = "wro4j.wroManagerFactory";

  /**
   * {@link WroManagerFactory} instance from spring application context..
   */
  private WroManagerFactory factory;

  /**
   * {@inheritDoc}
   */
  @Override
  protected void doInit(final FilterConfig filterConfig)
      throws ServletException {
    String targetBeanName = filterConfig
        .getInitParameter(PARAM_TARGET_BEAN_NAME);
    // apply default targetBeanName
    targetBeanName = StringUtils.isEmpty(targetBeanName) ? DEFAULT_TARGET_BEAN_NAME
        : targetBeanName;
    final WebApplicationContext ctx = WebApplicationContextUtils
        .getWebApplicationContext(filterConfig.getServletContext());
    factory = (WroManagerFactory) ctx.getBean(targetBeanName,
        WroManagerFactory.class);
    if (factory == null) {
      throw new WroRuntimeException("Could not locate: "
          + WroManagerFactory.class.getName()
          + " instance in applicationContext with bean name: " + targetBeanName);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected WroManagerFactory newWroManagerFactory() {
    return factory;
  }
}


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

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

import java.io.File;

import org.apache.commons.lang3.StringUtils;
import org.apache.maven.plugin.logging.Log;

import ro.isdc.wro.manager.factory.standalone.StandaloneContext;

/**
 * Encapsulates the details about computation of aggregated folderPath in maven plugin.
 *
 * @author Alex Objelean
 * @created 28 Sep 2013
 * @since 1.7.2
 */
public class AggregatedFolderPathResolver {
  private File cssDestinationFolder;
  private File buildDirectory;
  private File buildFinalName;
  private File destinationFolder;
  private String contextFoldersAsCSV;
  private Log log;

  /**
   * The idea is to compute the aggregatedFolderPath based on a root folder. The root folder is determined by comparing
   * the cssTargetFolder (the folder where aggregated css files are located) with build directory or contextFolder. If
   * rootFolder is null, then the result is also null (equivalent to using the cssTargetFolder the same as the root
   * folder.
   *
   * @return the aggregated folder path, based on the cssDestinationFolder (if set) and the build folder or the
   *         contextFolder.
   */
  public String resolve() {
    notNull(buildDirectory, "Build directory cannot be null!");
    notNull(log, "Logger cannot be null!");
    String result = null;
    final File cssTargetFolder = cssDestinationFolder == null ? destinationFolder : cssDestinationFolder;
    File rootFolder = null;
    notNull(cssTargetFolder, "cssTargetFolder cannot be null!");

    if (buildFinalName != null && cssTargetFolder.getPath().startsWith(buildFinalName.getPath())) {
      rootFolder = buildFinalName;
    } else if (cssTargetFolder.getPath().startsWith(buildDirectory.getPath())) {
      rootFolder = buildDirectory;
    } else {
      // find first best match
      for (final String contextFolder : getContextFolders()) {
        if (cssTargetFolder.getPath().startsWith(contextFolder)) {
          rootFolder = new File(contextFolder);
          break;
        }
      }
    }
    log.debug("buildDirectory: " + buildDirectory);
    log.debug("contextFolders: " + contextFoldersAsCSV);
    log.debug("cssTargetFolder: " + cssTargetFolder);
    log.debug("rootFolder: " + rootFolder);
    if (rootFolder != null) {
      result = StringUtils.removeStart(cssTargetFolder.getPath(), rootFolder.getPath());
    }
    log.debug("computedAggregatedFolderPath: " + result);
    return result;
  }

  /**
   * Use {@link StandaloneContext} to tokenize the contextFoldersAsCSV value.
   */
  private String[] getContextFolders() {
    final StandaloneContext context = new StandaloneContext();
    context.setContextFoldersAsCSV(contextFoldersAsCSV);
    return context.getContextFolders();
  }

  public AggregatedFolderPathResolver setCssDestinationFolder(final File cssDestinationFolder) {
    this.cssDestinationFolder = cssDestinationFolder;
    return this;
  }

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

  public AggregatedFolderPathResolver setBuildFinalName(final File buildFinalName) {
    this.buildFinalName = buildFinalName;
    return this;
  }

  public AggregatedFolderPathResolver setDestinationFolder(final File destinationFolder) {
    this.destinationFolder = destinationFolder;
    return this;
  }

  public AggregatedFolderPathResolver setContextFoldersAsCSV(final String contextFoldersAsCSV) {
    this.contextFoldersAsCSV = contextFoldersAsCSV;
    return this;
  }

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


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

import org.apache.commons.io.FilenameUtils;

/**
 * Contains factory methods for creating {@link Transformer} object.
 *
 * @author Alex Objelean
 */
public class Transformers {
  /**
   * Creates a {@link Transformer} which replace a original filename extension with a new extension.
   * @param newExtension extension to use for the returned value.
   * @return original filename but with the new extension.
   */
  public static Transformer<String> extensionTransformer(final String newExtension) {
    return new Transformer<String>() {
      public String transform(final String input) {
        return FilenameUtils.getBaseName(input) + "." + newExtension;
      }
    };
  }

  /**
   * Appends a suffix to the source baseName.
   * @param suffix to append.
   * @return {@link Transformer} capable to append a suffix to provided baseName.
   */
  public static Transformer<String> baseNameSuffixTransformer(final String suffix) {
    return new Transformer<String>() {
      public String transform(final String input) {
        final String baseName = FilenameUtils.getBaseName(input);
        final String extension = FilenameUtils.getExtension(input);
        return baseName + suffix + "." + extension;
      }
    };
  }

  /**
   * @return a {@link Transformer} which doesn't change the input.
   */
  public static Transformer<String> noOpTransformer() {
    return new Transformer<String>() {
      public String transform(final String input) {
        return input;
      }
    };
  }
}


/*
 * Copyright (c) 2008. All rights reserved.
 */
package ro.isdc.wro.model.resource.processor.support;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PushbackInputStream;

/**
 * JsMin.java.
 *

 * Copyright (c) 2006 John Reilly (www.inconspicuous.org) This work is a
 * translation from C to Java of jsmin.c published by Douglas Crockford.
 * Permission is hereby granted to use the Java version under the same
 * conditions as the jsmin.c on which it is based.
 *

 * http://www.crockford.com/javascript/jsmin.html
 *
 * @author Alex Objelean
 * @created Created on Dec 5, 2008
 */
@SuppressWarnings("serial")
public class JSMin {
  private static final int EOF = -1;

  private final PushbackInputStream in;

  private final OutputStream out;

  private int theA;

  private int theB;

  private int theX = EOF;

  private int theY = EOF;

  public JSMin(final InputStream in, final OutputStream out) {
    this.in = new PushbackInputStream(in);
    this.out = out;
  }

  /**
   * isAlphanum -- return true if the character is a letter, digit, underscore,
   * dollar sign, or non-ASCII character.
   */
  static boolean isAlphanum(final int c) {
    return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')
        || (c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' || c > 126);
  }

  /**
   * get -- return the next character from stdin. Watch out for lookahead. If
   * the character is a control character, translate it to a space or linefeed.
   */
  int get() throws IOException {
    final int c = in.read();

    if (c >= ' ' || c == '\n' || c == EOF) {
      return c;
    }

    if (c == '\r') {
      return '\n';
    }

    return ' ';
  }

  /**
   * Get the next character without getting it.
   */
  int peek() throws IOException {
    final int lookaheadChar = in.read();
    in.unread(lookaheadChar);
    return lookaheadChar;
  }

  /**
   * next -- get the next character, excluding comments. peek() is used to see
   * if a '/' is followed by a '/' or '*'.
   */
  int next() throws IOException, UnterminatedCommentException {
    int c = get();
    if (c == '/') {
      switch (peek()) {
      case '/':
        for (;;) {
          c = get();
          if (c <= '\n') {
            break;
          }
        }
        break;
      case '*':
        get();
        while (c != ' ') {
          switch (get()) {
          case '*':
            if (peek() == '/') {
              get();
              c = ' ';
            }
            break;
          case EOF:
            throw new UnterminatedCommentException();
          }
        }
        break;
      }

    }
    theY = theX;
    theX = c;
    return c;
  }

  /**
   * action -- do something! What you do is determined by the argument:
   *

   *
1 Output A. Copy B to A. Get the next B.

   *
2 Copy B to A. Get the next B. (Delete A).

   *
3 Get the next B. (Delete B).

   *

   * action treats a string as a single character. Wow!

   * action recognizes a regular expression if it is preceded by ( or , or =.
   */

  void action(final int d) throws IOException,
      UnterminatedRegExpLiteralException, UnterminatedCommentException,
      UnterminatedStringLiteralException {
    switch (d) {
    case 1:
      out.write(theA);
      if (theA == theB && (theA == '+' || theA == '-') && theY != theA) {
        out.write(' ');
      }
    case 2:
      theA = theB;

      if (theA == '\'' || theA == '"' || theA == '`') {
        for (;;) {
          out.write(theA);
          theA = get();
          if (theA == theB) {
            break;
          }
          if (theA <= '\n') {
            throw new UnterminatedStringLiteralException();
          }
          if (theA == '\\') {
            out.write(theA);
            theA = get();
          }
        }
      }

    case 3:
      theB = next();
      if (theB == '/'
          && (theA == '(' || theA == ',' || theA == '=' || theA == ':'
              || theA == '[' || theA == '!' || theA == '&' || theA == '|'
              || theA == '?' || theA == '+' || theA == '-' || theA == '~'
              || theA == '*' || theA == '/' || theA == '{' || theA == '\n')) {
        out.write(theA);
        if (theA == '/' || theA == '*') {
          out.write(' ');
        }
        out.write(theB);
        for (;;) {
          theA = get();
          if (theA == '[') {
            for (;;) {
              out.write(theA);
              theA = get();
              if (theA == ']') {
                break;
              }
              if (theA == '\\') {
                out.write(theA);
                theA = get();
              }
              if (theA <= '\n') {
                throw new UnterminatedRegExpLiteralException();
              }
            }
          } else if (theA == '/') {
            switch (peek()) {
            case '/':
            case '*':
              throw new UnterminatedRegExpLiteralException();
            }
            break;
          } else if (theA == '\\') {
            out.write(theA);
            theA = get();
          } else if (theA <= '\n') {
            throw new UnterminatedRegExpLiteralException();
          }
          out.write(theA);
        }
        theB = next();
      }
    }
  }

  /**
   * jsmin -- Copy the input to the output, deleting the characters which are
   * insignificant to JavaScript. Comments will be removed. Tabs will be
   * replaced with spaces. Carriage returns will be replaced with linefeeds.
   * Most spaces and linefeeds will be removed.
   */
  public void jsmin() throws IOException, UnterminatedRegExpLiteralException,
      UnterminatedCommentException, UnterminatedStringLiteralException {
    if (peek() == 0xEF) {
      get();
      get();
      get();
    }
    theA = '\n';
    action(3);
    while (theA != EOF) {
      switch (theA) {
      case ' ':
        if (isAlphanum(theB)) {
          action(1);
        } else {
          action(2);
        }
        break;
      case '\n':
        switch (theB) {
        case '{':
        case '[':
        case '(':
        case '+':
        case '-':
        case '!':
        case '~':
          action(1);
          break;
        case ' ':
          action(3);
          break;
        default:
          if (isAlphanum(theB)) {
            action(1);
          } else {
            action(2);
          }
        }
        break;
      default:
        switch (theB) {
        case ' ':
          if (isAlphanum(theA)) {
            action(1);
            break;
          }
          action(3);
          break;
        case '\n':
          switch (theA) {
          case '}':
          case ']':
          case ')':
          case '+':
          case '-':
          case '"':
          case '\'':
          case '`':
            action(1);
            break;
          default:
            if (isAlphanum(theA)) {
              action(1);
            } else {
              action(3);
            }
          }
          break;
        default:
          action(1);
          break;
        }
      }
    }
    out.flush();
  }

  private static class UnterminatedCommentException extends Exception {
  }

  private static class UnterminatedStringLiteralException extends Exception {
  }

  private static class UnterminatedRegExpLiteralException extends Exception {
  }

}


package ro.isdc.wro.http.support;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

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

import ro.isdc.wro.util.WroUtil;

/**
 * Responseible for resolving a content based on file name.
 *
 * @author Alex Objelean
 */
public class ContentTypeResolver {
  private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
  private static final Logger LOG = LoggerFactory.getLogger(ContentTypeResolver.class);
  private final static Map<String, String> defaultContentTypeMap = new HashMap<String, String>();
  private final static Set<String> requiresCharset = new HashSet<String>();

  static {
    defaultContentTypeMap.put("txt", "text/plain");
    defaultContentTypeMap.put("css", "text/css");
    defaultContentTypeMap.put("html", "text/html");
    defaultContentTypeMap.put("htm", "text/html");
    defaultContentTypeMap.put("xml", "application/xml");
    defaultContentTypeMap.put("xhtml", "application/xhtml+xml");
    defaultContentTypeMap.put("js", "application/javascript");
    defaultContentTypeMap.put("png", "image/png");
    defaultContentTypeMap.put("gif", "image/gif");
    defaultContentTypeMap.put("jpg", "image/jpeg");
    defaultContentTypeMap.put("jpeg", "image/jpeg");
    // font types
    defaultContentTypeMap.put("eot", "application/vnd.ms-fontobject");
    defaultContentTypeMap.put("otf", "application/x-font-opentype");
    defaultContentTypeMap.put("svg", "image/svg+xml");
    defaultContentTypeMap.put("woff", "application/font-woff");
    defaultContentTypeMap.put("woff2", "application/font-woff2");

    requiresCharset.add("text/css");
    requiresCharset.add("text/html");
    requiresCharset.add("text/plain");
    requiresCharset.add("application/xml");
    requiresCharset.add("application/xhtml+xml");
    requiresCharset.add("application/javascript");
  }

  /**
   * Returns a valid HTTP contentType's for a given filename. It first relies on the custom defaultContentTypeMap and if
   * not found it will fall back to defaultFileTypeMap from javax.activation.FileTypeMap. Examples: - "somefile.css"
   * resolves to "text/css". - "somefile.js.png" resolves to "image/png" - "/blah/index.html resolves to "text/html".
   *

   * The implementation uses reflection to load javax.activation.FileTypeMap class (available in jdk6) in
   * order to be compatible with jdk5. If this class is not available, the default content type is returned.
   *
   * @param fileName
   *          with an filename extension
   * @return contentType
   */
  public static String get(final String fileName) {
    final String extension = FilenameUtils.getExtension(WroUtil.removeQueryString(fileName.toLowerCase()));
    if (defaultContentTypeMap.containsKey(extension)) {
      return defaultContentTypeMap.get(extension);
    }
    try {
      final Class<?> fileTypeMapClass = ClassLoader.getSystemClassLoader().loadClass("javax.activation.FileTypeMap");
      LOG.debug("using {} to resolve contentType", fileTypeMapClass.getName());
      final Object fileTypeMap = fileTypeMapClass.getMethod("getDefaultFileTypeMap").invoke(fileTypeMapClass);
      return (String) fileTypeMapClass.getMethod("getContentType", String.class).invoke(fileTypeMap, fileName);
    } catch (final Exception e) {
      LOG.debug("FileTypeMap is not available (probably jdk5 is used). Exception {}, with message: {}", e.getClass(),
          e.getMessage());
      LOG.debug("Will use default content type: {} for fileName: {}", DEFAULT_CONTENT_TYPE, fileName);
    }
    return DEFAULT_CONTENT_TYPE;
  }

  /**
   * Returns a valid HTTP contentType's for a given filename with charset. It first relies on the custom
   * defaultContentTypeMap and if not found it will fall back to defaultFileTypeMap from javax.activation.FileTypeMap.
   * Examples: - ("somefile.css", "UTF-8") resolves to "text/css"; charset=UTF-8". - ("somefile.js.png", "UTF-8")
   * resolves to "image/png" - ("/blah/index.html, "UTF-8") resolves to "text/html; charset=8"
   *
   * @param fileName
   *          with an filename extension
   * @param encoding
   *          which encoding to use
   * @return contentType
   */
  public static String get(final String fileName, final String encoding) {
    final String contentType = get(fileName);
    if (requiresCharset.contains(contentType)) {
      return contentType + "; charset=" + encoding;
    }
    return contentType;
  }
}


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

import static java.lang.String.format;
import static org.apache.commons.lang3.Validate.notNull;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLDecoder;

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;
import ro.isdc.wro.model.resource.locator.wildcard.DefaultWildcardStreamLocator;
import ro.isdc.wro.model.resource.locator.wildcard.JarWildcardStreamLocator;
import ro.isdc.wro.model.resource.locator.wildcard.WildcardStreamLocator;
import ro.isdc.wro.model.resource.locator.wildcard.WildcardUriLocatorSupport;
import ro.isdc.wro.model.transformer.WildcardExpanderModelTransformer.NoMoreAttemptsIOException;
import ro.isdc.wro.util.StringUtils;

/**
 * Implementation of the {@link UriLocator} that is able to read a resource from a classpath.
 *
 * @author Alex Objelean
 * @created Created on Nov 6, 2008
 */
public class ClasspathUriLocator
    extends WildcardUriLocatorSupport {
  private static final Logger LOG = LoggerFactory.getLogger(ClasspathUriLocator.class);
  /**
   * Alias used to register this locator with {@link LocatorProvider}.
   */
  public static final String ALIAS = "classpath";
  /**
   * Prefix of the resource uri used to check if the resource can be read by this {@link UriLocator} implementation.
   */
  public static final String PREFIX = format("%s:", ALIAS);

  /**
   * @return the uri which is acceptable by this locator.
   */
  public static String createUri(final String path) {
    notNull(path);
    return PREFIX + path;
  }

  /**
   * {@inheritDoc}
   */
  public boolean accept(final String url) {
    return isValid(url);
  }

  /**
   * Check if a uri is a classpath resource.
   *
   * @param uri
   *          to check.
   * @return true if the uri is a classpath resource.
   */
  public static boolean isValid(final String uri) {
    return uri.trim().startsWith(PREFIX);
  }

  /**
   * {@inheritDoc}
   */
  public InputStream locate(final String uri)
      throws IOException {
    Validate.notNull(uri, "URI cannot be NULL!");
    // replace prefix & clean path by removing '..' characters if exists and
    // normalizing the location to use.
    String location = StringUtils.cleanPath(uri.replaceFirst(PREFIX, "")).trim();

    if (getWildcardStreamLocator().hasWildcard(location)) {
      try {
        return locateWildcardStream(uri, location);
      } catch (final IOException e) {
        if (location.contains("?")) {
          location = DefaultWildcardStreamLocator.stripQueryPath(location);
          LOG.debug("Trying fallback location: {}", location);
        }
      }
    }
    final InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(location);
    if (is == null) {
      throw new IOException("Couldn't get InputStream from this resource: " + uri);
    }
    return is;
  }

  /**
   * @return an input stream for an uri containing a wildcard for a given location.
   */
  private InputStream locateWildcardStream(final String uri, final String location)
      throws IOException {
    LOG.debug("wildcard detected for location: {}", location);
    // prefix with '/' because we use class relative resource retrieval. Using ClassLoader.getSystemResource doesn't
    // work well.
    final String fullPath = "/" + FilenameUtils.getFullPathNoEndSeparator(location);
    URL url = getClass().getResource(fullPath);
    LOG.debug("Attempting to find resource {} at the following location: {}", uri, fullPath);
    try {
      return locateWildcardStream(uri, url);
    } catch (final IOException e) {
      //do not attempt unless exception is of this type
      if (e instanceof NoMoreAttemptsIOException) {
        throw e;
      }
      // try once more, in order to treat classpath resources located in the currently built project.
      url = getClass().getResource("");
      LOG.debug("Attempting to find resource {} at the following URL: {}", uri, url);
      return locateWildcardStream(uri, url);
    }
  }

  private InputStream locateWildcardStream(final String uri, final URL url)
      throws IOException {
    if (url == null) {
      LOG.debug("Failed to locate stream for {} because URL is null", uri);
      throw new IOException("Cannot locate stream for null URL");
    }
    return getWildcardStreamLocator().locateStream(uri, new File(URLDecoder.decode(url.getFile(), "UTF-8")));
  }

  /**
   * Builds a {@link JarWildcardStreamLocator} in order to get resources from the full classpath.
   */
  @Override
  public WildcardStreamLocator newWildcardStreamLocator() {
    return new JarWildcardStreamLocator() {
      @Override
      public boolean hasWildcard(final String uri) {
        return isEnableWildcards() && super.hasWildcard(uri);
      }
    };
  }
}


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

import java.util.HashMap;
import java.util.Map;

/**
 * Default implementation of {@link NamingStrategyProvider} providing all {@link NamingStrategy} implementations from
 * core module.
 *
 * @author Alex Objelean
 * @created 16 Jun 2012
 * @since 1.4.7
 */
public class DefaultNamingStrategyProvider
    implements NamingStrategyProvider {
  /**
   * {@inheritDoc}
   */
  public Map<String, NamingStrategy> provideNamingStrategies() {
    final Map<String, NamingStrategy> map = new HashMap<String, NamingStrategy>();
    map.put(TimestampNamingStrategy.ALIAS, new TimestampNamingStrategy());
    map.put(NoOpNamingStrategy.ALIAS, new NoOpNamingStrategy());
    map.put(DefaultHashEncoderNamingStrategy.ALIAS, new DefaultHashEncoderNamingStrategy());
    map.put(FolderHashEncoderNamingStrategy.ALIAS, new FolderHashEncoderNamingStrategy());
    return map;
  }
}


package ro.isdc.wro.http.support;

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

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

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

import ro.isdc.wro.model.resource.locator.UriLocator;
import ro.isdc.wro.model.resource.locator.UrlUriLocator;

/**
 * A specialized {@link HttpServletResponseWrapper} responsible for streaming response to provided {@link OutputStream}.
 *
 * @author Alex Objelean
 * @since 1.4.5
 * @created 18 Mar 2012
 */
public class RedirectedStreamServletResponseWrapper
    extends HttpServletResponseWrapper {
  private static final Logger LOG = LoggerFactory.getLogger(RedirectedStreamServletResponseWrapper.class);
  /**
   * PrintWrapper of wrapped response.
   */
  private PrintWriter printWriter;
  /**
   * Servlet output stream of wrapped response.
   */
  private ServletOutputStream servletOutputStream;
  /**
   * Used to locate external resources.
   */
  private final UriLocator externalResourceLocator = newExternalResourceLocator();

  /**
   * @return {@link UriLocator} responsible for resolving external resources.
   */
  protected UriLocator newExternalResourceLocator() {
    return new UrlUriLocator().setEnableWildcards(false);
  }

  /**
   * @param outputStream
   *          the stream where the response will be written.
   * @param response
   *          decorated response.
   */
  public RedirectedStreamServletResponseWrapper(final OutputStream outputStream, final HttpServletResponse response) {
    super(response);
    notNull(outputStream);
    // Both servletOutputStream and PrintWriter must be overridden in order to be sure that dispatched servlet will write
    // to the pipe.
    printWriter = new PrintWriter(outputStream);
    servletOutputStream = new DelegatingServletOutputStream(outputStream);
  }

  @Override
  public void sendError(final int sc)
      throws IOException {
    onError(sc, "");
    super.sendError(sc);
  }

  @Override
  public void sendError(final int sc, final String msg)
      throws IOException {
    onError(sc, msg);
    super.sendError(sc, msg);
  }

  /**
   * Use an empty stream to avoid container writing unwanted message when a resource is missing.
   *
   * @param sc
   *          status code.
   * @param msg
   */
  private void onError(final int sc, final String msg) {
    LOG.debug("Error detected with code: {} and message: {}", sc, msg);
    final OutputStream emptyStream = new ByteArrayOutputStream();
    printWriter = new PrintWriter(emptyStream);
    servletOutputStream = new DelegatingServletOutputStream(emptyStream);
  }

  /**
   * By default, redirect does not allow writing to output stream its content. In order to support this use-case, we
   * need to open a new connection and read the content manually.
   */
  @Override
  public void sendRedirect(final String location)
      throws IOException {
    try {
      LOG.debug("redirecting to: {}", location);
      final InputStream is = externalResourceLocator.locate(location);
      IOUtils.copy(is, servletOutputStream);
      is.close();
      servletOutputStream.close();
    } catch (final IOException e) {
      LOG.warn("{}: Invalid response for location: {}", e.getClass().getName(), location);
      throw e;
    }
  }

  @Override
  public ServletOutputStream getOutputStream()
      throws IOException {
    return servletOutputStream;
  }

  @Override
  public void setStatus(final int sc) {
    super.setStatus(sc);
  }

  @Override
  public PrintWriter getWriter()
      throws IOException {
    return printWriter;
  }
}


/**
 * Copyright@2011 wro4j
 */
package ro.isdc.wro.model.transformer;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

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

import ro.isdc.wro.model.WroModel;
import ro.isdc.wro.model.group.Group;
import ro.isdc.wro.model.group.Inject;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.locator.UriLocator;
import ro.isdc.wro.model.resource.locator.factory.UriLocatorFactory;
import ro.isdc.wro.model.resource.locator.wildcard.DefaultWildcardStreamLocator;
import ro.isdc.wro.model.resource.locator.wildcard.WildcardExpanderHandlerAware;
import ro.isdc.wro.model.resource.locator.wildcard.WildcardStreamLocator;
import ro.isdc.wro.model.resource.locator.wildcard.WildcardUriLocatorSupport;
import ro.isdc.wro.util.Function;
import ro.isdc.wro.util.Transformer;

/**
 * A decorator which looks up for resources containing wildcards and replaces them with the corresponding collection of
 * specific resources. For example, a resource of this type:
 *

 * {@code
 *   <js>/path/to/**.js</js>
 * }
 *

 * can be transformed into:
 *

 * {@code
 *   <js>/path/to/a1.js</js>
 * }

 * {@code
 *   <js>/path/to/a2.js</js>
 * }

 * {@code
 *   <js>/path/to/a3.js</js>
 * }

 *

 * This model transformation is also known as wildcard expander, because it mutates the model after it is built by
 * adding resources to the group which contains resources with wildcard uri.
 *
 * @author Alex Objelean
 * @created 18 Jul 2011
 * @since 1.4.0
 */
@SuppressWarnings("serial")
public class WildcardExpanderModelTransformer
    implements Transformer<WroModel> {
  private static final Logger LOG = LoggerFactory.getLogger(WildcardExpanderModelTransformer.class);

  @Inject
  private UriLocatorFactory locatorFactory;

  /**
   * An instance of IOException having a special purpose: to skip subsequent attempts to localize a stream.
   */
  public static class NoMoreAttemptsIOException
      extends IOException {
    public NoMoreAttemptsIOException(final String message) {
      super(message);
    }
  }

  /**
   * {@inheritDoc}
   */
  public synchronized WroModel transform(final WroModel input) {
    final WroModel model = input;

    for (final Group group : model.getGroups()) {
      final List<Resource> resources = group.getResources();
      for (final Resource resource : resources) {
        processResource(group, resource);
      }
    }
    LOG.debug("Transformed model: {}", model);
    return model;
  }

  /**
   * Process each resource and replace it with a collection of resources if it contains wildcard.
   */
  private void processResource(final Group group, final Resource resource) {
    final UriLocator uriLocator = locatorFactory.getInstance(resource.getUri());

    if (uriLocator instanceof WildcardUriLocatorSupport) {
      final WildcardStreamLocator wildcardStreamLocator = ((WildcardUriLocatorSupport) uriLocator).getWildcardStreamLocator();

      // TODO should we probably handle the situation when wildcard is present, but the implementation is not
      // expandedHandledAware?
      if (wildcardStreamLocator.hasWildcard(resource.getUri())
          && wildcardStreamLocator instanceof WildcardExpanderHandlerAware) {

        final WildcardExpanderHandlerAware expandedHandler = (WildcardExpanderHandlerAware) wildcardStreamLocator;
        LOG.debug("Expanding resource: {}", resource.getUri());

        final String baseNameFolder = computeBaseNameFolder(resource, uriLocator, expandedHandler);
        LOG.debug("baseNameFolder: {}", baseNameFolder);

        expandedHandler.setWildcardExpanderHandler(createExpanderHandler(group, resource, baseNameFolder));
        try {
          // trigger the wildcard replacement
          uriLocator.locate(resource.getUri());
        } catch (final IOException e) {
          // log only
          LOG.debug("[FAIL] problem while trying to expand wildcard for the following resource uri: {}",
              resource.getUri());
        } finally {
          // remove the handler, it is not needed anymore
          expandedHandler.setWildcardExpanderHandler(null);
        }
      }
    }
  }

  /**
   * Computes the file name of the folder where the resource is located. The implementation uses a trick by invoking the
   * {@link WildcardExpanderHandlerAware} to get the baseName.
   */
  private String computeBaseNameFolder(final Resource resource, final UriLocator uriLocator,
      final WildcardExpanderHandlerAware expandedHandler) {
    // Find the baseName
    // add a recursive wildcard to trigger the wildcard detection. The simple wildcard ('*') is not enough because it
    // won't work for folders containing only directories with no files.
    LOG.debug("computeBaseNameFolder for resource {}", resource);
    final String resourcePath = FilenameUtils.getFullPath(resource.getUri())
        + DefaultWildcardStreamLocator.RECURSIVE_WILDCARD;
    LOG.debug("resourcePath: {}", resourcePath);
    // use thread local because we need to assign a File inside an anonymous class and it fits perfectly
    final ThreadLocal<String> baseNameFolderHolder = new ThreadLocal<String>();
    expandedHandler.setWildcardExpanderHandler(createBaseNameComputerFunction(baseNameFolderHolder));

    try {
      uriLocator.locate(resourcePath);
    } catch (final Exception e) {
      LOG.debug("[FAIL] Exception caught during wildcard expanding for resource: {}\n with exception message {}",
          resourcePath, e.getMessage());
    }
    if (baseNameFolderHolder.get() == null) {
      LOG.debug("[FAIL] Cannot compute baseName folder for resource: {}", resource);
    }
    return baseNameFolderHolder.get();
  }

  private Function<Collection<File>, Void> createBaseNameComputerFunction(final ThreadLocal<String> baseNameFolderHolder) {
    return new Function<Collection<File>, Void>() {
      public Void apply(final Collection<File> input)
          throws Exception {
        LOG.debug("\texpanded Files: {}", input);
        for (final File file : input) {
          LOG.debug("\tsetting baseNameFolder: {}", file.getParent());
          baseNameFolderHolder.set(file.getParent());
          // no need to continue
          break;
        }
        // use this to skip wildcard stream detection, we are only interested in the baseName
        throw new NoMoreAttemptsIOException("BaseNameFolder computed successfully, skip further wildcard processing..");
      }
    };
  }

  /**
   * create the handler which expand the resources containing wildcard.
   */
  public Function<Collection<File>, Void> createExpanderHandler(final Group group, final Resource resource,
      final String baseNameFolder) {
    LOG.debug("createExpanderHandler using baseNameFolder: {}\n for resource {}", baseNameFolder, resource);
    return new Function<Collection<File>, Void>() {
      public Void apply(final Collection<File> files) {
        if (baseNameFolder == null) {
          // replacing group with empty list since the original uri has no associated resources.
          // No BaseNameFolder found
          LOG.warn("The resource {} is probably invalid, removing it from the group.", resource);
          group.replace(resource, new ArrayList<Resource>());
        } else {
          final List<Resource> expandedResources = new ArrayList<Resource>();
          LOG.debug("baseNameFolder: {}", baseNameFolder);
          for (final File file : files) {
            final String resourcePath = getFullPathNoEndSeparator(resource);
            LOG.debug("\tresourcePath: {}", resourcePath);
            LOG.debug("\tfile path: {}", file.getPath());
            final String computedResourceUri = resourcePath
                + StringUtils.removeStart(file.getPath(), baseNameFolder).replace('\\', '/');

            final Resource expandedResource = Resource.create(computedResourceUri, resource.getType());
            LOG.debug("\texpanded resource: {}", expandedResource);
            expandedResources.add(expandedResource);
          }
          LOG.debug("\treplace resource {}", resource);
          group.replace(resource, expandedResources);
        }
        return null;
      }

      /**
       * This method fixes the problem when a resource in a group uses deep wildcard and starts at the root.
       *

       * Find more details here.
       */
      private String getFullPathNoEndSeparator(final Resource resource1) {
        final String result = FilenameUtils.getFullPathNoEndSeparator(resource1.getUri());
        if (result != null && 1 == result.length() && 0 == FilenameUtils.indexOfLastSeparator(result)) {
          return "";
        }

        return result;
      }
    };
  }
}


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

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

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

/**
 * Similar to {@link CssDataUriPreProcessor} which instead of replacing a url blindly with dataUri, is is smart enough to
 * detect duplicated image url and avoid replacing it with dataUri.
 *
 * @author Alex Objelean
 * @created May 9, 2010
 */
public class DuplicatesAwareCssDataUriPreProcessor
  extends CssDataUriPreProcessor {
  private static final Logger LOG = LoggerFactory.getLogger(DuplicatesAwareCssDataUriPreProcessor.class);
  public static final String ALIAS_DUPLICATE = "duplicateAwareCssDataUri";
  private final List<String> imageUrls = new ArrayList<String>();

  /**
   * Replace provided url with the new url if needed.
   *
   * @param imageUrl to replace.
   * @param cssUri Uri of the parsed css.
   * @return replaced url.
   */
  @Override
  protected final String replaceImageUrl(final String cssUri, final String imageUrl) {
    if (!imageUrls.contains(imageUrl)) {
      imageUrls.add(imageUrl);
      return super.replaceImageUrl(cssUri, imageUrl);
    }
    LOG.debug("duplicate Image url detected: '{}', skipping dataUri replacement", imageUrl);
    return imageUrl;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void onProcessCompleted() {
    imageUrls.clear();
  }
}


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

import java.io.IOException;
import java.io.InputStream;

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

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.model.resource.locator.UriLocator;

/**
 * Holds a list of uri locators. The uriLocator will be created based on the first
 * uriLocator from the supplied list which will accept the url.
 *
 * @author Alex Objelean
 * @created Created on Nov 4, 2008
 */
public abstract class AbstractUriLocatorFactory implements UriLocatorFactory {
  private static final Logger LOG = LoggerFactory.getLogger(AbstractUriLocatorFactory.class);
  /**
   * Locates an InputStream for the given uri.
   *
   * @param uri to locate.
   * @return {@link InputStream} of the resource.
   * @throws IOException if uri is invalid or resource couldn't be located.
   */
  public final InputStream locate(final String uri)
    throws IOException {
    final UriLocator uriLocator = getInstance(uri);
    if (uriLocator == null) {
      throw new WroRuntimeException("No locator is capable of handling uri: " + uri);
    }
    LOG.debug("[OK] locating {} using locator: {}", uri, uriLocator.getClass().getSimpleName());
    return new AutoCloseInputStream(uriLocator.locate(uri));
  }
}


/*
 * Copyright 2010.
 */
package ro.isdc.wro.extensions.processor.support.coffeescript;

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

import org.mozilla.javascript.ScriptableObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.extensions.locator.WebjarUriLocator;
import ro.isdc.wro.extensions.processor.support.linter.LinterException;
import ro.isdc.wro.extensions.script.RhinoScriptBuilder;
import ro.isdc.wro.util.WroUtil;

/**
 * CoffeeScript is a little language that compiles into JavaScript. Underneath all of those embarrassing braces and
 * semicolons, JavaScript has always had a gorgeous object model at its heart. CoffeeScript is an attempt to expose the
 * good parts of JavaScript in a simple way.
 * <p/>
 * This processor loads coffee-script library from the webjar.
 *
 * @author Alex Objelean
 * @since 1.3.6
 */
public class CoffeeScript {
  private static final Logger LOG = LoggerFactory.getLogger(CoffeeScript.class);
  private static final String DEFAULT_COFFE_SCRIPT = "coffee-script.js";
  private String[] options;
  private ScriptableObject scope;
  private WebjarUriLocator webjarLocator;

  /**
   * Initialize script builder for evaluation.
   */
  private RhinoScriptBuilder initScriptBuilder() {
    try {
      RhinoScriptBuilder builder = null;
      if (scope == null) {
        builder = RhinoScriptBuilder.newChain().evaluateChain(getCoffeeScriptAsStream(), DEFAULT_COFFE_SCRIPT);
        scope = builder.getScope();
      } else {
        builder = RhinoScriptBuilder.newChain(scope);
      }
      return builder;
    } catch (final IOException ex) {
      throw new IllegalStateException("Failed reading init script", ex);
    }
  }

  /**
   * Override this method to use a different version of CoffeeScript. This method is useful for upgrading coffeeScript
   * processor independently of wro4j.
   *
   * @return The stream of the CoffeeScript.
   */
  protected InputStream getCoffeeScriptAsStream() throws IOException {
    return getWebjarLocator().locate(WebjarUriLocator.createUri("coffee-script.min.js"));
  }

  /**
   * @return {@link WebjarUriLocator} instance to retrieve webjars.
   */
  private WebjarUriLocator getWebjarLocator() {
    if (webjarLocator == null) {
      webjarLocator = new WebjarUriLocator();
    }
    return webjarLocator;
  }

  /**
   * Validates a js using jsHint and throws {@link LinterException} if the js is invalid. If no exception is thrown, the
   * js is valid.
   *
   * @param data
   *          js content to process.
   */
  public String compile(final String data) {
    final RhinoScriptBuilder builder = initScriptBuilder();
    final String compileScript = String.format("CoffeeScript.compile(%s, %s);", WroUtil.toJSMultiLineString(data),
        buildOptions());
    return (String) builder.evaluate(compileScript, "CoffeeScript.compile");
  }

  /**
   * @return A javascript representation of the options. The result is a json object.
   */
  private String buildOptions() {
    final StringBuffer sb = new StringBuffer("{");
    if (options != null) {
      for (int i = 0; i < options.length; i++) {
        sb.append(options[i]).append(": true");
        if (i < options.length - 1) {
          sb.append(",");
        }
      }
    }
    sb.append("}");
    return sb.toString();
  }

  /**
   * @param options
   *          the options to set
   */
  public CoffeeScript setOptions(final String... options) {
    this.options = options == null ? new String[] {} : options;
    LOG.debug("setOptions: {}", Arrays.asList(this.options));
    return this;
  }
}


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

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

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.Collection;

import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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

import ro.isdc.wro.config.Context;
import ro.isdc.wro.config.factory.PropertiesAndFilterConfigWroConfigurationFactory;
import ro.isdc.wro.config.jmx.WroConfiguration;
import ro.isdc.wro.http.handler.RequestHandler;
import ro.isdc.wro.http.handler.factory.DefaultRequestHandlerFactory;
import ro.isdc.wro.http.handler.factory.RequestHandlerFactory;
import ro.isdc.wro.http.support.ResponseHeadersConfigurer;
import ro.isdc.wro.http.support.ServletContextAttributeHelper;
import ro.isdc.wro.manager.factory.DefaultWroManagerFactory;
import ro.isdc.wro.manager.factory.WroManagerFactory;
import ro.isdc.wro.model.group.processor.Injector;
import ro.isdc.wro.model.group.processor.InjectorBuilder;
import ro.isdc.wro.model.resource.locator.ServletContextUriLocator;
import ro.isdc.wro.model.resource.locator.support.DispatcherStreamLocator;
import ro.isdc.wro.util.ObjectFactory;
import ro.isdc.wro.util.WroUtil;

/**
 * Main entry point. Perform the request processing by identifying the type of the requested resource. Depending on the
 * way it is configured.
 *
 * @author Alex Objelean
 * @created Created on Oct 31, 2008
 */
public class WroFilter
    implements Filter {
  private static final Logger LOG = LoggerFactory.getLogger(WroFilter.class);
  /**
   * The prefix to use for default mbean name.
   */
  private static final String MBEAN_PREFIX = "wro4j-";
  /**
   * Attribute indicating that the request was passed through {@link WroFilter}. This is required to allow identify
   * requests for wro resources (example: async resourceWatcher which cannot be executed asynchronously unless a wro
   * resource was requested).
   *
   * @VisibleForTesting
   */
  public static final String ATTRIBUTE_PASSED_THROUGH_FILTER = WroFilter.class.getName()
      + ".passed_through_filter";
  /**
   * Filter config.
   */
  private FilterConfig filterConfig;
  private ObjectFactory<WroConfiguration> wroConfigurationFactory;
  /**
   * Wro configuration.
   */
  private WroConfiguration wroConfiguration;
  /**
   * WroManagerFactory. The core of the optimizer.
   */
  private WroManagerFactory wroManagerFactory;
  /**
   * Used to create the collection of requestHandlers to apply
   */
  private RequestHandlerFactory requestHandlerFactory = newRequestHandlerFactory();
  private Collection<RequestHandler> requestHandlers;

  private ResponseHeadersConfigurer headersConfigurer;
  /**
   * Flag used to toggle filter processing. When this flag is false, the filter will proceed with chaining. This flag is
   * true by default.
   */
  private boolean enable = true;
  private Injector injector;
  private MBeanServer mbeanServer = null;

  /**
   * @return true if the provided request contains an attribute indicating that it was handled through {@link WroFilter}
   */
  public static boolean isPassedThroughyWroFilter(final HttpServletRequest request) {
    notNull(request);
    return request.getAttribute(ATTRIBUTE_PASSED_THROUGH_FILTER) != null;
  }

  public final void init(final FilterConfig config)
      throws ServletException {
    this.filterConfig = config;
    // invoke createConfiguration method only if the configuration was not set.
    this.wroConfiguration = createConfiguration();
    this.wroManagerFactory = createWroManagerFactory();
    this.injector = createInjector();
    headersConfigurer = newResponseHeadersConfigurer();
    requestHandlers = createRequestHandlers();

    registerChangeListeners();
    registerMBean();
    doInit(config);
    LOG.info("wro4j version: {}", WroUtil.getImplementationVersion());
    LOG.info("wro4j configuration: {}", wroConfiguration);
  }

  private Collection<RequestHandler> createRequestHandlers() {
    requestHandlers = requestHandlerFactory.create();
    for (final RequestHandler requestHandler : requestHandlers) {
      injector.inject(requestHandler);
    }
    return requestHandlers;
  }

  /**
   * Creates configuration by looking up in servletContext attributes. If none is found, a new one will be created using
   * the configuration factory.
   *
   * @return {@link WroConfiguration} object.
   */
  private WroConfiguration createConfiguration() {
    // Extract config from servletContext (if already configured)
    // TODO use a named helper
    final WroConfiguration configAttribute = ServletContextAttributeHelper.create(filterConfig).getWroConfiguration();
    if (configAttribute != null) {
      setConfiguration(configAttribute);
    }
    return getWroConfigurationFactory().create();
  }

  /**
   * Creates {@link WroManagerFactory}.
   */
  private WroManagerFactory createWroManagerFactory() {
    if (wroManagerFactory == null) {
      final WroManagerFactory managerFactoryAttribute = ServletContextAttributeHelper.create(filterConfig)
          .getManagerFactory();
      LOG.debug("managerFactory attribute: {}", managerFactoryAttribute);
      wroManagerFactory = managerFactoryAttribute != null ? managerFactoryAttribute : newWroManagerFactory();
    }
    LOG.debug("created managerFactory: {}", wroManagerFactory);
    return wroManagerFactory;
  }

  /**
   * Expose MBean to tell JMX infrastructure about our MBean (only if jmxEnabled is true).
   */
  private void registerMBean() {
    if (wroConfiguration.isJmxEnabled()) {
      try {
        mbeanServer = getMBeanServer();
        final ObjectName name = getMBeanObjectName();
        if (!mbeanServer.isRegistered(name)) {
          mbeanServer.registerMBean(wroConfiguration, name);
        }
      } catch (final JMException e) {
        LOG.error("Exception occured while registering MBean", e);
      }
    }
  }

  private void unregisterMBean() {
    try {
      if (mbeanServer != null && mbeanServer.isRegistered(getMBeanObjectName())) {
        mbeanServer.unregisterMBean(getMBeanObjectName());
      }
    } catch (final JMException e) {
      LOG.error("Exception occured while registering MBean", e);
    }
  }

  private ObjectName getMBeanObjectName()
      throws MalformedObjectNameException {
    return new ObjectName(newMBeanName(), "type", WroConfiguration.class.getSimpleName());
  }

  /**
   * @return the name of MBean to be used by JMX to configure wro4j.
   */
  protected String newMBeanName() {
    String mbeanName = wroConfiguration.getMbeanName();
    if (StringUtils.isEmpty(mbeanName)) {
      final String contextPath = getContextPath();
      mbeanName = StringUtils.isEmpty(contextPath) ? "ROOT" : contextPath;
      mbeanName = MBEAN_PREFIX + mbeanName;
    }
    return mbeanName;
  }

  /**
   * @return Context path of the application.
   */
  private String getContextPath() {
    String contextPath = null;
    try {
      contextPath = (String) ServletContext.class.getMethod("getContextPath", new Class<?>[] {}).invoke(
          filterConfig.getServletContext(), new Object[] {});
    } catch (final Exception e) {
      contextPath = "DEFAULT";
      LOG.warn("Couldn't identify contextPath because you are using older version of servlet-api (<2.5). Using "
          + contextPath + " contextPath.");
    }
    return contextPath.replaceFirst(ServletContextUriLocator.PREFIX, "");
  }

  /**
   * Override this method if you want to provide a different MBeanServer.
   *
   * @return {@link MBeanServer} to use for JMX.
   */
  protected MBeanServer getMBeanServer() {
    return ManagementFactory.getPlatformMBeanServer();
  }

  /**
   * Register property change listeners.
   */
  private void registerChangeListeners() {
    wroConfiguration.registerCacheUpdatePeriodChangeListener(new PropertyChangeListener() {
      public void propertyChange(final PropertyChangeEvent event) {
        // reset cache headers when any property is changed in order to avoid browser caching
        headersConfigurer = newResponseHeadersConfigurer();
        wroManagerFactory.onCachePeriodChanged(valueAsLong(event.getNewValue()));
      }
    });
    wroConfiguration.registerModelUpdatePeriodChangeListener(new PropertyChangeListener() {
      public void propertyChange(final PropertyChangeEvent event) {
        headersConfigurer = newResponseHeadersConfigurer();
        wroManagerFactory.onModelPeriodChanged(valueAsLong(event.getNewValue()));
      }
    });
    LOG.debug("Cache & Model change listeners were registered");
  }

  /**
   * @return the {@link ResponseHeadersConfigurer}.
   */
  protected ResponseHeadersConfigurer newResponseHeadersConfigurer() {
    return ResponseHeadersConfigurer.fromConfig(wroConfiguration);
  }

  /**
   * @return default implementation of {@link RequestHandlerFactory}
   */
  protected RequestHandlerFactory newRequestHandlerFactory() {
    return new DefaultRequestHandlerFactory();
  }

  private long valueAsLong(final Object value) {
    notNull(value);
    return Long.valueOf(String.valueOf(value)).longValue();
  }

  /**
   * Custom filter initialization - can be used for extended classes.
   *
   * @see Filter#init(FilterConfig).
   */
  protected void doInit(final FilterConfig config)
      throws ServletException {
  }

  public final 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 (isFilterActive(request)) {
      LOG.debug("processing wro request: {}", request.getRequestURI());
      try {
        // add request, response & servletContext to thread local
        Context.set(Context.webContext(request, response, filterConfig), wroConfiguration);
        addPassThroughFilterAttribute(request);
        if (!handledWithRequestHandler(request, response)) {
          processRequest(request, response);
          onRequestProcessed();
        }
      } catch (final Exception e) {
        onException(e, response, chain);
      } finally {
        Context.unset();
      }
    } else {
      chain.doFilter(request, response);
    }
  }

  private void addPassThroughFilterAttribute(final HttpServletRequest request) {
    request.setAttribute(ATTRIBUTE_PASSED_THROUGH_FILTER, Boolean.TRUE);
  }

  private boolean handledWithRequestHandler(final HttpServletRequest request, final HttpServletResponse response)
      throws ServletException, IOException {
    notNull(requestHandlers, "requestHandlers cannot be null!");
    // create injector used for process injectable fields from each requestHandler.
    for (final RequestHandler requestHandler : requestHandlers) {
      if (requestHandler.isEnabled() && requestHandler.accept(request)) {
        requestHandler.handle(request, response);
        return true;
      }
    }
    return false;
  }

  /**
   * @return {@link Injector} used to inject {@link RequestHandler}'s.
   * @VisibleForTesting
   */
  Injector createInjector() {
    return InjectorBuilder.create(wroManagerFactory).build();
  }

  /**
   * Perform actual processing.
   */
  private void processRequest(final HttpServletRequest request, final HttpServletResponse response)
      throws ServletException, IOException {
    setResponseHeaders(response);
    // process the uri using manager
    wroManagerFactory.create().process();
  }

  /**
   * @return true if the filter should be applied or proceed with chain otherwise.
   */
  private boolean isFilterActive(final HttpServletRequest request) {
    // prevent StackOverflowError by skipping the already included wro request
    return enable && !DispatcherStreamLocator.isIncludedRequest(request);
  }

  /**
   * Invoked when a {@link Exception} is thrown. Allows custom exception handling. The default implementation proceeds
   * with filter chaining when exception is thrown.
   *
   * @param e
   *          {@link Exception} thrown during request processing.
   */
  protected void onException(final Exception e, final HttpServletResponse response, final FilterChain chain) {
    LOG.error("Exception occured", e);
    try {
      LOG.warn("Cannot process. Proceeding with chain execution.");
      chain.doFilter(Context.get().getRequest(), response);
    } catch (final Exception ex) {
      // should never happen (use debug level to suppress unuseful logs)
      LOG.debug("Error while chaining the request", ex);
    }
  }

  /**
   * Method called for each request and responsible for setting response headers, used mostly for cache control.
   * Override this method if you want to change the way headers are set.

   *
   * @param response
   *          {@link HttpServletResponse} object.
   */
  protected void setResponseHeaders(final HttpServletResponse response) {
    headersConfigurer.setHeaders(response);
  }

  /**
   * Allows external configuration of {@link WroManagerFactory} (ex: using spring IoC). When this value is set, the
   * default {@link WroManagerFactory} initialization won't work anymore.
   *

   * Note: call this method before {@link WroFilter#init(FilterConfig)} is invoked.
   *
   * @param wroManagerFactory
   *          the wroManagerFactory to set
   */
  public void setWroManagerFactory(final WroManagerFactory wroManagerFactory) {
    this.wroManagerFactory = wroManagerFactory;
  }

  /**
   * @return configured and decorated {@link WroManagerFactory} instance.
   */
  public final WroManagerFactory getWroManagerFactory() {
    return this.wroManagerFactory;
  }

  /**
   * Sets the RequestHandlerFactory used to create the collection of requestHandlers
   *
   * @param requestHandlerFactory
   *          to set
   */
  public void setRequestHandlerFactory(final RequestHandlerFactory requestHandlerFactory) {
    notNull(requestHandlerFactory);
    this.requestHandlerFactory = requestHandlerFactory;
  }

  /**
   * Factory method for {@link WroManagerFactory}.
   *

   * Creates a {@link WroManagerFactory} configured in {@link WroConfiguration} using reflection. When no configuration
   * is found a default implementation is used.
   *

   * Note: this method is not invoked during initialization if a {@link WroManagerFactory} is set using
   * {@link WroFilter#setWroManagerFactory(WroManagerFactory)}.
   *
   * @return {@link WroManagerFactory} instance.
   */
  protected WroManagerFactory newWroManagerFactory() {
    return DefaultWroManagerFactory.create(wroConfigurationFactory);
  }

  /**
   * @return implementation of {@link ObjectFactory<WroConfiguration>} used to create a {@link WroConfiguration} object.
   */
  protected ObjectFactory<WroConfiguration> newWroConfigurationFactory(final FilterConfig filterConfig) {
    return new PropertiesAndFilterConfigWroConfigurationFactory(filterConfig);
  }

  private ObjectFactory<WroConfiguration> getWroConfigurationFactory() {
    if (wroConfigurationFactory == null) {
      wroConfigurationFactory = newWroConfigurationFactory(filterConfig);
    }
    return wroConfigurationFactory;
  }

  public void setWroConfigurationFactory(final ObjectFactory<WroConfiguration> wroConfigurationFactory) {
    this.wroConfigurationFactory = wroConfigurationFactory;
  }

  /**
   * @return the {@link WroConfiguration} associated with this filter instance.
   * @VisibleForTesting
   */
  public final WroConfiguration getConfiguration() {
    return this.wroConfiguration;
  }

  /**
   * Once set, this configuration will be used, instead of the one built by the factory.
   *
   * @param config
   *          a not null {@link WroConfiguration} to set.
   */
  public final void setConfiguration(final WroConfiguration config) {
    notNull(config);
    wroConfigurationFactory = new ObjectFactory<WroConfiguration>() {
      public WroConfiguration create() {
        return config;
      }
    };
  }

  /**
   * Sets the enable flag used to toggle filter. This might be useful when the filter has to be enabled/disabled based
   * on environment configuration.
   *
   * @param enable
   *          flag for enabling the {@link WroFilter}.
   */
  public void setEnable(final boolean enable) {
    this.enable = enable;
  }

  /**
   * Useful for unit tests to check the post processing.
   */
  protected void onRequestProcessed() {
  }

  /**
   * {@inheritDoc}
   */
  public void destroy() {
    //Avoid memory leak by unregistering mBean on destroy
    unregisterMBean();
    if (wroManagerFactory != null) {
      wroManagerFactory.destroy();
    }
    if (wroConfiguration != null) {
      wroConfiguration.destroy();
    }
    Context.destroy();
  }
}


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

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

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

import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.model.group.Group;

/**
 * The resource model encapsulates the information about all existing groups.
 *
 * @author Alex Objelean
 * @created Created on Oct 30, 2008
 */
public final class WroModel {
  private static final Logger LOG = LoggerFactory.getLogger(WroModel.class);
  /**
   * Set of groups.
   */
  private Set<Group> groups = new TreeSet<Group>();

  /**
   * @return a readonly collection of groups.
   */
  public final Collection<Group> getGroups() {
    return Collections.unmodifiableSet(groups);
  }

  /**
   * @param groups
   *          the groups to set
   */
  public final WroModel setGroups(final Collection<Group> groups) {
    notNull(groups, "groups cannot be null!");
    LOG.debug("setGroups: {}", groups);
    identifyDuplicateGroupNames(groups);
    this.groups = new TreeSet<Group>(groups);
    return this;
  }

  /**
   * Identify duplicate group names.
   *
   * @param groups a collection of group to validate.
   */
  private void identifyDuplicateGroupNames(final Collection<Group> groups) {
    LOG.debug("identifyDuplicateGroupNames");
    final List<String> groupNames = new ArrayList<String>();
    for (final Group group : groups) {
      if (groupNames.contains(group.getName())) {
        throw new WroRuntimeException("Duplicate group name detected: " + group.getName());
      }
      groupNames.add(group.getName());
    }
  }

  /**
   * Merge this model with another model. This is useful for supporting model imports.
   *
   * @param importedModel model to import.
   */
  public void merge(final WroModel importedModel) {
    Validate.notNull(importedModel, "imported model cannot be null!");
    LOG.debug("merging importedModel: {}", importedModel);
    for (final String groupName : new WroModelInspector(importedModel).getGroupNames()) {
      if (new WroModelInspector(this).getGroupNames().contains(groupName)) {
        throw new WroRuntimeException("Duplicate group name detected: " + groupName);
      }
      final Group importedGroup = new WroModelInspector(importedModel).getGroupByName(groupName);
      addGroup(importedGroup);
    }
  }

  /**
   * Add a single group to the model.
   * @param group a not null {@link Group}.
   */
  public WroModel addGroup(final Group group) {
    notNull(group);
    groups.add(group);
    return this;
  }

  @Override
  public boolean equals(final Object obj) {
    return EqualsBuilder.reflectionEquals(this, obj, true);
  }

  @Override
  public int hashCode() {
    return HashCodeBuilder.reflectionHashCode(this);
  }

  @Override
  public String toString() {
    return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE).append(
        "groups", this.groups).toString();
  }
}


/*
 * Copyright (C) 2010.
 * 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.packer.PackerJs;
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;

/**
 *  Uses Dean Edwards packer utility to pack js resources.
 *
 * @author Alex Objelean
 * @created 31 Jul 2010
 */
@Minimize
@SupportedResourceType(ResourceType.JS)
public class PackerJsProcessor
  implements ResourcePreProcessor, ResourcePostProcessor, Destroyable {
  private static final Logger LOG = LoggerFactory.getLogger(PackerJsProcessor.class);
  public static final String ALIAS = "packerJs";
  private ObjectPoolHelper<PackerJs> enginePool;

  public PackerJsProcessor() {
    enginePool = new ObjectPoolHelper<PackerJs>(new ObjectFactory<PackerJs>() {
      @Override
      public PackerJs create() {
        return newPackerJs();
      }
    });
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void process(final Resource resource, final Reader reader, final Writer writer)
    throws IOException {
    final String content = IOUtils.toString(reader);
    final PackerJs packerJs = enginePool.getObject();
    try {
      writer.write(packerJs.pack(content));
    } 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 {
      reader.close();
      writer.close();
      enginePool.returnObject(packerJs);
    }
  }

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

  /**
   * @return PackerJs engine.
   */
  protected PackerJs newPackerJs() {
    return new PackerJs();
  }

  /**
   * {@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();
  }
}


package ro.isdc.wro.model.resource.locator.factory;

import java.io.IOException;
import java.io.InputStream;

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

import ro.isdc.wro.model.group.Inject;
import ro.isdc.wro.model.group.processor.Injector;
import ro.isdc.wro.model.resource.locator.UriLocator;
import ro.isdc.wro.util.AbstractDecorator;

/**
 * A decorator responsible to perform Injection every time
 * {@link InjectableUriLocatorFactoryDecorator#getInstance(String)} getInstance method is invoked.
 *
 * @author Alex Objelean
 * @created 7 Mar 2014
 * @since 1.7.4
 */
public class InjectableUriLocatorFactoryDecorator extends AbstractDecorator<UriLocatorFactory>
    implements UriLocatorFactory  {
  private static final Logger LOG = LoggerFactory.getLogger(InjectableUriLocatorFactoryDecorator.class);
  @Inject
  private Injector injector;
  public InjectableUriLocatorFactoryDecorator(final UriLocatorFactory decorated) {
    super(decorated);
  }

  /**
   * This implementation shows the problem with current design of locator implementation. Needs to be changed.
   */
  public InputStream locate(final String uri)
      throws IOException {
    final UriLocator locator = getInstance(uri);
    if (locator == null) {

      return getDecoratedObject().locate(uri);
    }
    return locator.locate(uri);
  }

  public UriLocator getInstance(final String uri) {
    final UriLocator instance = getDecoratedObject().getInstance(uri);
    if (instance != null) {
      LOG.debug("using {} locator for uri: {}", instance, uri);
      injector.inject(instance);
    }
    return instance;
  }
}


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

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

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.ProxyInputStream;
import org.apache.commons.io.input.ReaderInputStream;
import org.apache.commons.io.output.ProxyOutputStream;
import org.apache.commons.io.output.WriterOutputStream;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.config.Context;
import ro.isdc.wro.config.ReadOnlyContext;
import ro.isdc.wro.config.jmx.WroConfiguration;
import ro.isdc.wro.model.group.Inject;
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.model.resource.processor.support.JSMin;

/**
 * Use JSMin utility for js compression. This processor is annotated with {@link Minimize} because it performs
 * minimization.
 *
 * @author Alex Objelean
 * @created Created on Nov 28, 2008
 */
@Minimize
@SupportedResourceType(ResourceType.JS)
public class JSMinProcessor implements ResourcePreProcessor,
    ResourcePostProcessor {
  public static final String ALIAS = "jsMin";
  @Inject
  private ReadOnlyContext context;
  private String encoding;

  /**
   * {@inheritDoc}
   */
  public void process(final Resource resource, final Reader reader,
    final Writer writer) throws IOException {
      final InputStream is = new ProxyInputStream(new ReaderInputStream(reader, getEncoding())) {};
      final OutputStream os = new ProxyOutputStream(new WriterOutputStream(writer, getEncoding()));
    try {
      new JSMin(is, os).jsmin();
      is.close();
      os.close();
		} catch (final Exception e) {
      throw WroRuntimeException.wrap(e);
    } finally {
      IOUtils.closeQuietly(is);
      IOUtils.closeQuietly(os);
    }
  }

  /**
   * {@inheritDoc}
   */
  public void process(final Reader reader,
      final Writer writer) throws IOException {
    // resource Uri doesn't matter.
    process(null, reader, writer);
  }

  /**
   * @return the encoding
   */
  private String getEncoding() {
    if (encoding == null) {
      //use config is available to get encoding
      this.encoding = Context.isContextSet() ? context.getConfig().getEncoding() : WroConfiguration.DEFAULT_ENCODING;
    }
    return encoding;
  }

  /**
   * @param encoding the encoding to set
   */
  public JSMinProcessor setEncoding(final String encoding) {
    this.encoding = encoding;
    return this;
  }
}


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

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Collection;

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

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.cache.CacheKey;
import ro.isdc.wro.config.ReadOnlyContext;
import ro.isdc.wro.manager.callback.LifecycleCallbackRegistry;
import ro.isdc.wro.model.WroModel;
import ro.isdc.wro.model.WroModelInspector;
import ro.isdc.wro.model.factory.WroModelFactory;
import ro.isdc.wro.model.group.Group;
import ro.isdc.wro.model.group.Inject;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.model.resource.processor.decorator.DefaultProcessorDecorator;
import ro.isdc.wro.model.resource.processor.decorator.ProcessorDecorator;
import ro.isdc.wro.model.resource.processor.factory.ProcessorsFactory;

/**
 * Default group processor which perform preProcessing, merge and postProcessing on groups resources.
 *
 * @author Alex Objelean
 * @created Created on Nov 3, 2008
 */
public class GroupsProcessor {
  private static final Logger LOG = LoggerFactory.getLogger(GroupsProcessor.class);
  @Inject
  private LifecycleCallbackRegistry callbackRegistry;
  @Inject
  private ProcessorsFactory processorsFactory;
  @Inject
  private WroModelFactory modelFactory;
  @Inject
  private ReadOnlyContext context;
  @Inject
  private Injector injector;

  /**
   * This field is transient because {@link PreProcessorExecutor} is not serializable (according to findbugs eclipse
   * plugin).
   */
  @Inject
  private transient PreProcessorExecutor preProcessorExecutor;

  /**
   * @param cacheKey
   *          to process.
   * @return processed content.
   */
  public String process(final CacheKey cacheKey) {
    Validate.notNull(cacheKey);
    try {
      LOG.debug("Starting processing group [{}] of type [{}] with minimized flag: " + cacheKey.isMinimize(),
          cacheKey.getGroupName(), cacheKey.getType());
      // find processed result for a group
      final WroModel model = modelFactory.create();
      final Group group = new WroModelInspector(model).getGroupByName(cacheKey.getGroupName());
      if (group == null) {
        throw new WroRuntimeException("No such group available in the model: " + cacheKey.getGroupName());
      }
      final Group filteredGroup = group.collectResourcesOfType(cacheKey.getType());
      if (filteredGroup.getResources().isEmpty()) {
        LOG.debug("No resources found in group: {} and resource type: {}", group.getName(), cacheKey.getType());
        if (!context.getConfig().isIgnoreEmptyGroup()) {
          throw new WroRuntimeException("No resources found in group: " + group.getName());
        }
      }
      final String result = preProcessorExecutor.processAndMerge(filteredGroup.getResources(), cacheKey.isMinimize());
      return applyPostProcessors(cacheKey, result);
    } catch (final IOException e) {
      throw new WroRuntimeException("Exception while merging resources: " + e.getMessage(), e).logError();
    } finally {
      callbackRegistry.onProcessingComplete();
    }
  }

  /**
   * Apply resourcePostProcessors.
   *
   * @param cacheKey
   *          the {@link CacheKey} being processed.
   * @param content
   *          to process with all postProcessors.
   * @return the post processed content.
   */
  private String applyPostProcessors(final CacheKey cacheKey, final String content)
      throws IOException {
    final Collection<ResourcePostProcessor> processors = processorsFactory.getPostProcessors();
    LOG.debug("appying post processors: {}", processors);
    if (processors.isEmpty()) {
      return content;
    }
    final Resource resource = Resource.create(cacheKey.getGroupName(), cacheKey.getType());

    Reader reader = new StringReader(content.toString());
    Writer writer = null;
    for (final ResourcePostProcessor processor : processors) {
      final ResourcePreProcessor decoratedProcessor = decorateProcessor(processor, cacheKey.isMinimize());
      writer = new StringWriter();
      decoratedProcessor.process(resource, reader, writer);
      reader = new StringReader(writer.toString());
    }
    return writer.toString();
  }

  /**
   * This method is synchronized to ensure that processor is injected before it is being used by other thread.
   *
   * @return a decorated processor.
   */
  private synchronized ProcessorDecorator decorateProcessor(final ResourcePostProcessor processor,
      final boolean minimize) {
    final ProcessorDecorator decorated = new DefaultProcessorDecorator(processor, minimize) {
      @Override
      public void process(final Resource resource, final Reader reader, final Writer writer)
          throws IOException {
        try {
          callbackRegistry.onBeforePostProcess();
          super.process(resource, reader, writer);
        } finally {
          callbackRegistry.onAfterPostProcess();
        }
      }
    };
    injector.inject(decorated);
    return decorated;
  }

  /**
   * @VisibleForTesting
   */
  final void setPreProcessorExecutor(final PreProcessorExecutor preProcessorExecutor) {
    this.preProcessorExecutor = preProcessorExecutor;
  }

  /**
   * Perform cleanup when taken out of service.
   */
  public void destroy() {
    preProcessorExecutor.destroy();
  }
}


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

import java.io.IOException;

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

import ro.isdc.wro.config.Context;
import ro.isdc.wro.config.jmx.WroConfiguration;
import ro.isdc.wro.http.support.ServletContextAttributeHelper;

/**
 * This filter is responsible for setting the {@link Context} to the current request cycle. This is required if you want
 * to use {@link ServletContextAttributeHelper} in order to access wro related attributes from within a tag or a
 * servlet. Usually this filter will be mapped to all requests:
 *
 * <pre>
 *  <filter-mapping>
 *     <filter-name>wroContextFilter</filter-name>
 *     <url-pattern>/*</url-pattern>
 *  </filter-mapping>
 *
 * </pre>
 *
 * @author Alex Objelean
 * @created 12 May 2012
 * @since 1.4.7
 */
public class WroContextFilter
  implements Filter {
  private FilterConfig filterConfig;

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

  /**
   * {@inheritDoc}
   */
  public final void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain)
      throws IOException, ServletException {
    final HttpServletRequest request = (HttpServletRequest) req;
    final HttpServletResponse response = (HttpServletResponse) res;
    //preserve current correlationId to ensure proper clean up during nested requests.
    final String originalCorrelationId = Context.isContextSet() ? Context.getCorrelationId() : null;
    Context.set(Context.webContext(request, response, this.filterConfig), getWroConfiguration());
    final String correlationId = Context.getCorrelationId();
    try {
      chain.doFilter(request, response);
    } finally {
      Context.setCorrelationId(correlationId);
      Context.unset();
      if (originalCorrelationId != null) {
        Context.setCorrelationId(originalCorrelationId);
      }
    }
  }

  /**
   * @return the {@link WroConfiguration} extracted from {@link ServletContext} if exist or default one otherwise.
   */
  private WroConfiguration getWroConfiguration() {
    final WroConfiguration configAttribute = getServletContextAttributeHelper().getWroConfiguration();
    return configAttribute != null ? configAttribute : new WroConfiguration();
  }

  /**
   * @VisibleForTesting
   * @return the instance responsible for {@link WroConfiguration} lookup.
   */
  ServletContextAttributeHelper getServletContextAttributeHelper() {
    return ServletContextAttributeHelper.create(filterConfig);
  }

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


package ro.isdc.wro.http.support;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.List;

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 org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.config.Context;
import ro.isdc.wro.manager.factory.BaseWroManagerFactory;
import ro.isdc.wro.model.group.processor.Injector;
import ro.isdc.wro.model.group.processor.InjectorBuilder;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.util.StopWatch;

/**
 * Allows configuration of a list of processors to be applied on the
 *
 * @author Alex Objelean
 * @since 1.4.5
 * @created 17 Mar 2012
 */
public abstract class AbstractProcessorsFilter
    implements Filter {
  private static final Logger LOG = LoggerFactory.getLogger(AbstractProcessorsFilter.class);
  private FilterConfig filterConfig;

  /**
   * {@inheritDoc}
   */
  public final void init(final FilterConfig config)
      throws ServletException {
    this.filterConfig = config;
    doInit(config);
  }

  /**
   * Allows custom initialization.
   */
  protected void doInit(final FilterConfig config) {
  }

  /**
   * {@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;
    try {
      // add request, response & servletContext to thread local
      Context.set(Context.webContext(request, response, filterConfig));
      final ByteArrayOutputStream os = new ByteArrayOutputStream();
      final HttpServletResponse wrappedResponse = new RedirectedStreamServletResponseWrapper(os, response);
      chain.doFilter(request, wrappedResponse);
      final Reader reader = new StringReader(new String(os.toByteArray(), Context.get().getConfig().getEncoding()));

      final StringWriter writer = new StringWriter();
      final String requestUri = request.getRequestURI().replaceFirst(request.getContextPath(), "");
      doProcess(requestUri, reader, writer);
      // it is important to update the contentLength to new value, otherwise the transfer can be closed without all
      // bytes being read. Some browsers (chrome) complains with the following message: ERR_CONNECTION_CLOSED
      final int contentLength = writer.getBuffer().length();
      response.setContentLength(contentLength);
      // Content length can be 0 when the 30x (not modified) status code is returned.
      if (contentLength > 0) {
        IOUtils.write(writer.toString(), response.getOutputStream());
      }
    } catch (final RuntimeException e) {
      onRuntimeException(e, response, chain);
    } finally {
      Context.unset();
    }
  }

  /**
   * Applies configured processor on the intercepted stream.
   */
  private void doProcess(final String requestUri, final Reader reader, final Writer writer)
      throws IOException {
    Reader input = reader;
    Writer output = null;
    LOG.debug("processing resource: {}", requestUri);
    try {
      final StopWatch stopWatch = new StopWatch();
      final Injector injector = InjectorBuilder.create(new BaseWroManagerFactory()).build();
      final List<ResourcePreProcessor> processors = getProcessorsList();
      if (processors == null || processors.isEmpty()) {
        IOUtils.copy(reader, writer);
      } else {
        for (final ResourcePreProcessor processor : processors) {
          stopWatch.start("Using " + processor.getClass().getSimpleName());
          // inject all required properties
          injector.inject(processor);

          output = new StringWriter();
          LOG.debug("Using {} processor", processor);
          processor.process(createResource(requestUri), input, output);

          input = new StringReader(output.toString());
          stopWatch.stop();
        }
        LOG.debug(stopWatch.prettyPrint());
        if (output != null) {
          writer.write(output.toString());
        }
      }
    } finally {
      reader.close();
      writer.close();
    }
  }

  /**
   * @param requestUri
   *          the uri of the requested resource.
   * @return the {@link Resource} to pass as argument to the processor.
   */
  protected Resource createResource(final String requestUri) {
    return Resource.create(requestUri);
  }

  /**
   * Invoked when a {@link RuntimeException} is thrown. Allows custom exception handling. The default implementation
   * redirects to 404 for a specific {@link WroRuntimeException} exception when in DEPLOYMENT mode.
   *
   * @param e
   *          {@link RuntimeException} thrown during request processing.
   */
  protected void onRuntimeException(final RuntimeException e, final HttpServletResponse response,
      final FilterChain chain) {
    LOG.debug("RuntimeException occured", e);
    try {
      LOG.debug("Cannot process. Proceeding with chain execution.");
      chain.doFilter(Context.get().getRequest(), response);
    } catch (final Exception ex) {
      // should never happen
      LOG.error("Error while chaining the request.");
    }
  }

  /**
   * @return a list of processors to apply for this filter.
   */
  protected abstract List<ResourcePreProcessor> getProcessorsList();

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