package ro.isdc.wro.http.handler;

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

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

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

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

import ro.isdc.wro.config.ReadOnlyContext;
import ro.isdc.wro.http.WroFilter;
import ro.isdc.wro.http.support.ContentTypeResolver;
import ro.isdc.wro.http.support.HttpHeader;
import ro.isdc.wro.http.support.ResponseHeadersConfigurer;
import ro.isdc.wro.http.support.UnauthorizedRequestException;
import ro.isdc.wro.model.group.Inject;
import ro.isdc.wro.model.resource.locator.factory.UriLocatorFactory;
import ro.isdc.wro.model.resource.support.ResourceAuthorizationManager;

/**
 * Provides access to wro resources via a resource proxy. This request handler accepts the following uri:
 *
 *

 * ?wroAPI=wroResources?id=/path/to/resourceId.js
 *

 *
 * Notice that this uri has the following mandatory parameters:
 *

 *
wroAPI - having the 'wroResources' value

 *
id - the id of the proxy resource

 *

 *
 * @author Ivar Conradi Østhus
 * @created 19 May 2012
 * @since 1.4.7
 */
public class ResourceProxyRequestHandler
    extends RequestHandlerSupport {
  private static final Logger LOG = LoggerFactory.getLogger(ResourceProxyRequestHandler.class);

  public static final String PARAM_RESOURCE_ID = "id";
  public static final String PATH_RESOURCES = "wroResources";
  /**
   * The alias of this {@link RequestHandler} used for configuration.
   */
  public static final String ALIAS = "resourceProxy";

  @Inject
  private UriLocatorFactory locatorFactory;

  @Inject
  private ReadOnlyContext context;

  @Inject
  private ResourceAuthorizationManager authManager;
  private ResponseHeadersConfigurer headersConfigurer;

  @Override
  public void handle(final HttpServletRequest request, final HttpServletResponse response)
      throws IOException {
    verifyAccess(request, response);
    serveProxyResourceUri(request, response);
  }

  @Override
  public boolean accept(final HttpServletRequest request) {
    return isProxyRequest(request);
  }

  private void serveProxyResourceUri(final HttpServletRequest request, final HttpServletResponse response)
      throws IOException {
    final String resourceUri = getResourceUri(request);
    LOG.debug("[OK] serving proxy resource: {}", resourceUri);
    final OutputStream outputStream = response.getOutputStream();
    response.setContentType(ContentTypeResolver.get(resourceUri, context.getConfig().getEncoding()));

    if (isResourceChanged(request)) {
      // set expiry headers
      getHeadersConfigurer().setHeaders(response);
      InputStream is = null;
      try {
        is = new AutoCloseInputStream(locatorFactory.locate(resourceUri));
        final int length = IOUtils.copy(is, outputStream);
        // servlet engine may ignore this if content body is flushed to client
        response.setContentLength(length);
        response.setStatus(HttpServletResponse.SC_OK);
      } finally {
        IOUtils.closeQuietly(is);
        IOUtils.closeQuietly(outputStream);
      }
    } else {
      response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
    }
  }

  /**
   * Used to identify whether the {@link HttpServletResponse#SC_NOT_MODIFIED} or {@link HttpServletResponse#SC_OK}
   * should be returned. Currently a single timestamp is used to detect the change for all resources. This might be no
   * accurate, but at least it allows sending NOT_MODIFIED header much often resulting in less load on the server.
   *

   * Override this method if a different way detecting change is required.
   *
   * @return true if the requested resource is changed on the server and the latest version should be returned.
   */
  protected boolean isResourceChanged(final HttpServletRequest request) {
    try {
      final long ifModifiedSince = request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.toString());
      return ifModifiedSince < getHeadersConfigurer().getLastModifiedTimestamp();
    } catch (final Exception e) {
      LOG.warn("Could not extract IF_MODIFIED_SINCE header for request: " + request.getRequestURI() + ". Assuming content is changed. ", e);
      return true;
    }
  }

  private void verifyAccess(final HttpServletRequest request, final HttpServletResponse response) {
    final String resourceUri = getResourceUri(request);
    if (!authManager.isAuthorized(resourceUri)) {
      LOG.debug("[FAIL] Unauthorized proxy resource: {}", resourceUri);
      response.setStatus(HttpServletResponse.SC_FORBIDDEN);
      throw new UnauthorizedRequestException("Unauthorized resource request detected: " + resourceUri);
    }
  }

  private String getResourceUri(final HttpServletRequest request) {
    return request.getParameter(PARAM_RESOURCE_ID);
  }

  private final ResponseHeadersConfigurer getHeadersConfigurer() {
    if (headersConfigurer == null) {
      headersConfigurer = newResponseHeadersConfigurer();
    }
    return headersConfigurer;
  }

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

  /**
   * Builds the request path for this request handler using the assumption that {@link WroFilter} has a mapping ending
   * with a * character.
   *
   * @param requestUri
   *          of wro request used to compute the path to this request handler. This request uri is required, because we
   *          do not know how the wro filter is mapped.
   * @return the path for this handler.
   */
  public static String createProxyPath(final String requestUri, final String resourceId) {
    notNull(requestUri);
    notNull(resourceId);
    return requestUri + getRequestHandlerPath(resourceId);
  }

  /**
   * Checks if the provided url is a resource proxy request.
   * @param url
   *          to check.
   * @return true if the provided url is a proxy resource.
   */
  public static boolean isProxyUri(final String url) {
    notNull(url);
    return url.contains(getRequestHandlerPath());
  }

  private boolean isProxyRequest(final HttpServletRequest request) {
    final String apiHandlerValue = request.getParameter(PATH_API);
    final String resourceId = request.getParameter(PARAM_RESOURCE_ID);
    return PATH_RESOURCES.equals(apiHandlerValue) && resourceId != null;
  }

  private static String getRequestHandlerPath() {
    return String.format("?%s=%s", PATH_API, PATH_RESOURCES);
  }

  private static String getRequestHandlerPath(final String resourceId) {
    return String.format("%s&%s=%s", getRequestHandlerPath(), PARAM_RESOURCE_ID, resourceId);
  }
}


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

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

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

import ro.isdc.wro.WroRuntimeException;

/**
 * Abstracts the configurable creation of the strategies. Responsible for configuration of a list of strategies which
 * are configured as a CSV of aliases.
 *
 * @author Alex Objelean
 * @created 26 Jun 2012
 * @since 1.4.7
 */
public abstract class AbstractConfigurableMultipleStrategy<S, P>
    extends AbstractConfigurableStrategySupport<S, P> {
  private static final Logger LOG = LoggerFactory.getLogger(AbstractConfigurableMultipleStrategy.class);
  /**
   * Delimit tokens containing a list of locators, preProcessors & postProcessors.
   */
  private static final String TOKEN_DELIMITER = ",";

  /**
   * Creates a comma separated list of items.
   *
   * @VisibleForTesting
   */
  public static String createItemsAsString(final String... items) {
    final StringBuffer sb = new StringBuffer();
    for (int i = 0; i < items.length; i++) {
      sb.append(items[i]);
      if (i < items.length - 1) {
        sb.append(TOKEN_DELIMITER);
      }
    }
    return sb.toString();
  }

  /**
   * @return the strategy S whose alias is found configured in the properties. This method will never return null. If an
   *         invalid alias is provided - a runtime exception will be thrown.
   * @VisibleForTesting
   */
  public final List<S> getConfiguredStrategies() {
    final List<S> strategies = new ArrayList<S>();
    final List<String> aliasList = getAliasList(getConfiguredValue());
    for (String alias : aliasList) {
      final S strategy = getStrategyForAlias(alias);
      if (strategy == null) {
        throw new WroRuntimeException("Invalid strategy alias provided: <" + alias + ">. Available aliases are: "
            + getAvailableAliases());
      }
      strategies.add(strategy);
    }
    LOG.debug("using strategies: {}", strategies);
    return strategies;
  }

  /**
   * Creates a list of aliases based on provided string containing comma separated values of aliases.
   *
   * @param aliasCsv
   *          string representation of tokens separated by ',' character.
   * @return a list of non empty strings.
   */
  private List<String> getAliasList(final String aliasCsv) {
    LOG.debug("configured aliases: {}", aliasCsv);
    final List<String> list = new ArrayList<String>();
    if (!StringUtils.isEmpty(aliasCsv)) {
      final String[] tokens = aliasCsv.split(TOKEN_DELIMITER);
      for (final String token : tokens) {
        list.add(token.trim());
      }
    }
    return list;
  }
}


/**
 * Copyright Alex Objelean
 */
package ro.isdc.wro.extensions.script;

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

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.AutoCloseInputStream;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.RhinoException;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.tools.ToolErrorReporter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.extensions.locator.WebjarUriLocator;

/**
 * Used to evaluate javascript on the serverside using rhino javascript engine. Encapsulate and hides all implementation
 * details used by rhino to evaluate javascript on the serverside.
 *
 * @author Alex Objelean
 */
public final class RhinoScriptBuilder {
  private static final Logger LOG = LoggerFactory.getLogger(RhinoScriptBuilder.class);
  private static final String SCRIPT_ENV = "env.rhino.min.js";
  private static final String SCRIPT_JSON = "json2.min.js";
  private static final String SCRIPT_CYCLE = "cycle.js";
  private final ScriptableObject scope;

  private RhinoScriptBuilder() {
    this(null);
  }

  private RhinoScriptBuilder(final ScriptableObject scope) {
    this.scope = createContext(scope);
  }

  private Context getContext() {
    initContext();
    return Context.getCurrentContext();
  }

  /**
   * @return the context
   */
  public ScriptableObject getScope() {
    return this.scope;
  }

  /**
   * Initialize the context.
   */
  private ScriptableObject createContext(final ScriptableObject initialScope) {
    final Context context = getContext();
    context.setOptimizationLevel(-1);
    // TODO redirect errors from System.err to LOG.error()
    context.setErrorReporter(new ToolErrorReporter(false));
    context.setLanguageVersion(Context.VERSION_1_8);
    InputStream script = null;
    final ScriptableObject scriptCommon = (ScriptableObject) context.initStandardObjects(initialScope);
    try {
      script = new AutoCloseInputStream(getClass().getResourceAsStream("commons.js"));
      context.evaluateReader(scriptCommon, new InputStreamReader(script), "commons.js", 1, null);
    } catch (final IOException e) {
      throw new RuntimeException("Problem while evaluationg commons script.", e);
    } finally {
      IOUtils.closeQuietly(script);
    }
    return scriptCommon;
  }

  /**
   * Add a client side environment to the script context (client-side aware).
   *
   * @return {@link RhinoScriptBuilder} used to chain evaluation of the scripts.
   * @throws IOException
   */
  public RhinoScriptBuilder addClientSideEnvironment() {
    try {
      //final InputStream scriptEnv = getClass().getResourceAsStream(SCRIPT_ENV);
      final InputStream scriptEnv = new WebjarUriLocator().locate("env.rhino.js");
      evaluateChain(scriptEnv, SCRIPT_ENV);
      return this;
    } catch (final IOException e) {
      throw new RuntimeException("Couldn't initialize env.rhino script", e);
    }
  }

  /**
   * This method will load JSON utility and aslo a Douglas Crockford's <a
   * href="https://github.com/douglascrockford/JSON-js/blob/master/cycle.js">utility</a> required for decycling objects
   * which would fail otherwise when using JSON.stringify.
   */
  public RhinoScriptBuilder addJSON() {
    try {
      final InputStream script = new AutoCloseInputStream(
          new WebjarUriLocator().locate(WebjarUriLocator.createUri("20110223/json2.js")));
      final InputStream scriptCycle = getClass().getResourceAsStream(SCRIPT_CYCLE);

      evaluateChain(script, SCRIPT_JSON);
      evaluateChain(scriptCycle, SCRIPT_CYCLE);
      return this;
    } catch (final IOException e) {
      throw new RuntimeException("Couldn't initialize json2.min.js script", e);
    }
  }

  /**
   * Evaluates a script and return {@link RhinoScriptBuilder} for a chained script evaluation.
   *
   * @param stream {@link InputStream} of the script to evaluate.
   * @param sourceName the name of the evaluated script.
   * @return {@link RhinoScriptBuilder} chain with required script evaluated.
   * @throws IOException if the script couldn't be retrieved.
   */
  public RhinoScriptBuilder evaluateChain(final InputStream stream, final String sourceName)
    throws IOException {
    notNull(stream);
    try {
      getContext().evaluateReader(scope, new InputStreamReader(stream), sourceName, 1, null);
      return this;
    } catch(final RhinoException e) {
      if (e instanceof RhinoException) {
        LOG.error("RhinoException: {}", RhinoUtils.createExceptionMessage(e));
      }
      throw e;
    } catch (final RuntimeException e) {
      LOG.error("Exception caught", e);
      throw e;
    } finally {
      stream.close();
    }
  }

  /**
   * Makes sure the context is properly initialized.
   */
  private void initContext() {
    if (Context.getCurrentContext() == null) {
      Context.enter();
    }
  }

  /**
   * Evaluates a script and return {@link RhinoScriptBuilder} for a chained script evaluation.
   *
   * @param script the string representation of the script to evaluate.
   * @param sourceName the name of the evaluated script.
   * @return evaluated object.
   * @throws IOException if the script couldn't be retrieved.
   */
  public RhinoScriptBuilder evaluateChain(final String script, final String sourceName) {
    notNull(script);
    getContext().evaluateString(scope, script, sourceName, 1, null);
    return this;
  }

  /**
   * Evaluates a script from a reader.
   *
   * @param reader {@link Reader} of the script to evaluate.
   * @param sourceName the name of the evaluated script.
   * @return evaluated object.
   * @throws IOException if the script couldn't be retrieved.
   */
  public Object evaluate(final Reader reader, final String sourceName)
    throws IOException {
    notNull(reader);
    try {
      return evaluate(IOUtils.toString(reader), sourceName);
    } finally {
      reader.close();
    }
  }

  /**
   * Evaluates a script.
   *
   * @param script string representation of the script to evaluate.
   * @param sourceName the name of the evaluated script.
   * @return evaluated object.
   * @throws IOException if the script couldn't be retrieved.
   */
  public Object evaluate(final String script, final String sourceName) {
    notNull(script);
    // make sure we have a context associated with current thread
    try {
      return getContext().evaluateString(scope, script, sourceName, 1, null);
    } catch (final RhinoException e) {
      final String message = RhinoUtils.createExceptionMessage(e);
      LOG.error("JavaScriptException occured: {}", message);
      throw new WroRuntimeException(message);
    } finally {
      // Rhino throws an exception when trying to exit twice. Make sure we don't get any exception
      if (Context.getCurrentContext() != null) {
        Context.exit();
      }
    }
  }

  /**
   * @return default {@link RhinoScriptBuilder} for script evaluation chaining.
   */
  public static RhinoScriptBuilder newChain() {
    return new RhinoScriptBuilder();
  }

  public static RhinoScriptBuilder newChain(final ScriptableObject scope) {
    return new RhinoScriptBuilder(scope);
  }

  /**
   * @return default {@link RhinoScriptBuilder} for script evaluation chaining.
   */
  public static RhinoScriptBuilder newClientSideAwareChain() {
    return new RhinoScriptBuilder().addClientSideEnvironment();
  }
}


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

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

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

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

import ro.isdc.wro.cache.CacheKey;
import ro.isdc.wro.cache.CacheStrategy;
import ro.isdc.wro.cache.CacheValue;
import ro.isdc.wro.cache.factory.CacheKeyFactory;
import ro.isdc.wro.cache.factory.DefaultCacheKeyFactory;
import ro.isdc.wro.cache.impl.LruMemoryCacheStrategy;
import ro.isdc.wro.cache.support.DefaultSynchronizedCacheStrategyDecorator;
import ro.isdc.wro.config.Context;
import ro.isdc.wro.config.jmx.WroConfiguration;
import ro.isdc.wro.config.metadata.DefaultMetaDataFactory;
import ro.isdc.wro.config.metadata.MetaDataFactory;
import ro.isdc.wro.config.support.ContextPropagatingCallable;
import ro.isdc.wro.config.support.WroConfigurationChangeListener;
import ro.isdc.wro.manager.callback.LifecycleCallback;
import ro.isdc.wro.manager.callback.LifecycleCallbackRegistry;
import ro.isdc.wro.manager.factory.WroManagerFactory;
import ro.isdc.wro.manager.runnable.ReloadCacheRunnable;
import ro.isdc.wro.manager.runnable.ReloadModelRunnable;
import ro.isdc.wro.model.WroModel;
import ro.isdc.wro.model.factory.DefaultWroModelFactoryDecorator;
import ro.isdc.wro.model.factory.WroModelFactory;
import ro.isdc.wro.model.group.DefaultGroupExtractor;
import ro.isdc.wro.model.group.GroupExtractor;
import ro.isdc.wro.model.group.Inject;
import ro.isdc.wro.model.group.processor.InjectorBuilder;
import ro.isdc.wro.model.resource.ResourceType;
import ro.isdc.wro.model.resource.locator.factory.DefaultUriLocatorFactory;
import ro.isdc.wro.model.resource.locator.factory.UriLocatorFactory;
import ro.isdc.wro.model.resource.processor.Destroyable;
import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.model.resource.processor.factory.ProcessorsFactory;
import ro.isdc.wro.model.resource.processor.factory.SimpleProcessorsFactory;
import ro.isdc.wro.model.resource.support.DefaultResourceAuthorizationManager;
import ro.isdc.wro.model.resource.support.ResourceAuthorizationManager;
import ro.isdc.wro.model.resource.support.change.ResourceWatcher;
import ro.isdc.wro.model.resource.support.hash.HashStrategy;
import ro.isdc.wro.model.resource.support.hash.SHA1HashStrategy;
import ro.isdc.wro.model.resource.support.naming.NamingStrategy;
import ro.isdc.wro.model.resource.support.naming.NoOpNamingStrategy;
import ro.isdc.wro.model.transformer.WildcardExpanderModelTransformer;
import ro.isdc.wro.util.LazyInitializer;
import ro.isdc.wro.util.ObjectFactory;
import ro.isdc.wro.util.SchedulerHelper;
import ro.isdc.wro.util.Transformer;

/**
 * Contains all the factories used by optimizer in order to perform the logic. This object should be created through
 * {@link WroManagerFactory}, in order to ensure that all dependencies are injected properly. In other words, avoid
 * setting the fields explicitly after creating a new instance of {@link WroManager}.
 *

 * Most of the fields of this class are annotated with @Inject, in order to ensure that each instance can benefit use
 * @Inject annotation on its fields.
 *
 * @author Alex Objelean
 * @created Created on Oct 30, 2008
 */
public class WroManager
    implements WroConfigurationChangeListener {
  private static final Logger LOG = LoggerFactory.getLogger(WroManager.class);
  @Inject
  private final WroModelFactory modelFactory;
  @Inject
  private final GroupExtractor groupExtractor;
  /**
   * A cacheStrategy used for caching processed results. <GroupName, processed result>.
   */
  @Inject
  private final CacheStrategy<CacheKey, CacheValue> cacheStrategy;
  @Inject
  private final ProcessorsFactory processorsFactory;
  @Inject
  private final UriLocatorFactory locatorFactory;
  /**
   * Rename the file name based on its original name and content.
   */
  @Inject
  private final NamingStrategy namingStrategy;
  @Inject
  private LifecycleCallbackRegistry callbackRegistry;
  /**
   * HashBuilder for creating a hash based on the processed content.
   */
  @Inject
  private final HashStrategy hashStrategy;
  @Inject
  private ResourceBundleProcessor resourceBundleProcessor;
  @Inject
  private final ResourceAuthorizationManager authorizationManager;
  @Inject
  private final CacheKeyFactory cacheKeyFactory;
  @Inject
  private final MetaDataFactory metaDataFactory;
  @Inject
  private final ResourceWatcher resourceWatcher;
  /**
   * Schedules the model update.
   */
  private final SchedulerHelper modelSchedulerHelper = SchedulerHelper.create(new LazyInitializer<Runnable>() {
    @Override
    protected Runnable initialize() {
      // decorate with ContextPropagatingCallable to make context available in the new thread
      return ContextPropagatingCallable.decorate(new ReloadModelRunnable(getModelFactory()));
    }
  }, ReloadModelRunnable.class.getSimpleName());
  /**
   * Schedules the cache update.
   */
  private final SchedulerHelper cacheSchedulerHelper = SchedulerHelper.create(new LazyInitializer<Runnable>() {
    @Override
    protected Runnable initialize() {
      // decorate with ContextPropagatingCallable to make context available in the new thread
      return ContextPropagatingCallable.decorate(new ReloadCacheRunnable(getCacheStrategy()));
    }
  }, ReloadCacheRunnable.class.getSimpleName());

  private WroManager(final Builder builder) {
    this.authorizationManager = builder.authorizationManager;
    this.cacheKeyFactory = builder.cacheKeyFactory;
    this.cacheStrategy = DefaultSynchronizedCacheStrategyDecorator.decorate(builder.cacheStrategy);
    this.callbackRegistry = builder.callbackRegistry;
    this.groupExtractor = builder.groupExtractor;
    this.hashStrategy = builder.hashStrategy;
    this.locatorFactory = builder.locatorFactory;
    this.metaDataFactory = builder.metaDataFactory;
    this.namingStrategy = builder.namingStrategy;
    this.processorsFactory = builder.processorsFactory;
    this.modelFactory = DefaultWroModelFactoryDecorator.decorate(builder.modelFactory, builder.modelTransformers);
    this.resourceWatcher = new ResourceWatcher();
  }

  /**
   * Perform processing of the uri.
   *
   * @throws IOException
   *           when any IO related problem occurs or if the request cannot be processed.
   */
  public final void process()
      throws IOException {
    // reschedule cache & model updates
    final WroConfiguration config = Context.get().getConfig();
    cacheSchedulerHelper.scheduleWithPeriod(config.getCacheUpdatePeriod());
    modelSchedulerHelper.scheduleWithPeriod(config.getModelUpdatePeriod());
    resourceBundleProcessor.serveProcessedBundle();
  }

  /**
   * Encodes a fingerprint of the resource into the path. The result may look like this: ${fingerprint}/myGroup.js
   *
   * @return a path to the resource with the fingerprint encoded as a folder name.
   */
  public final String encodeVersionIntoGroupPath(final String groupName, final ResourceType resourceType,
      final boolean minimize) {
    // TODO use CacheKeyFactory
    final CacheKey key = new CacheKey(groupName, resourceType, minimize);
    final CacheValue cacheValue = cacheStrategy.get(key);
    final String groupUrl = groupExtractor.encodeGroupUrl(groupName, resourceType, minimize);
    // encode the fingerprint of the resource into the resource path
    return formatVersionedResource(cacheValue.getHash(), groupUrl);
  }

  /**
   * Format the version of the resource in the path. Default implementation use hash as a folder: <hash>/groupName.js.
   * The implementation can be changed to follow a different versioning style, like version parameter:
   * /groupName.js?version=<hash>
   *
   * @param hash
   *          Hash of the resource.
   * @param resourcePath
   *          Path of the resource.
   * @return formatted versioned path of the resource.
   */
  protected String formatVersionedResource(final String hash, final String resourcePath) {
    return String.format("%s/%s", hash, resourcePath);
  }

  public final void onCachePeriodChanged(final long period) {
    LOG.info("onCachePeriodChanged with value {} has been triggered!", period);
    cacheSchedulerHelper.scheduleWithPeriod(period);
    // flush the cache by destroying it.
    cacheStrategy.clear();
  }

  public final void onModelPeriodChanged(final long period) {
    LOG.info("onModelPeriodChanged with value {} has been triggered!", period);
    // trigger model destroy
    getModelFactory().destroy();
    modelSchedulerHelper.scheduleWithPeriod(period);
  }

  /**
   * Called when {@link WroManager} is being taken out of service.
   */
  public final void destroy() {
    try {
      cacheSchedulerHelper.destroy();
      modelSchedulerHelper.destroy();
      cacheStrategy.destroy();
      modelFactory.destroy();
      resourceWatcher.destroy();
      destroyProcessors();
    } catch (final Exception e) {
      LOG.error("Exception occured during manager destroy!", e);
    } finally {
      LOG.debug("WroManager destroyed");
    }
  }

  /**
   * Invokes destroy method on all {@link Destroyable} processors.
   */
  private void destroyProcessors() throws Exception {
    for (final ResourcePreProcessor processor : processorsFactory.getPreProcessors()) {
      if (processor instanceof Destroyable) {
        ((Destroyable) processor).destroy();
      }
    }
    for (final ResourcePostProcessor processor : processorsFactory.getPostProcessors()) {
      if (processor instanceof Destroyable) {
        ((Destroyable) processor).destroy();
      }
    }
  }

  public final HashStrategy getHashStrategy() {
    return hashStrategy;
  }

  /**
   * @return the modelFactory
   */
  public final WroModelFactory getModelFactory() {
    return modelFactory;
  }

  /**
   * @return the processorsFactory used by this WroManager.
   */
  public final ProcessorsFactory getProcessorsFactory() {
    return processorsFactory;
  }

  /**
   * @return the cacheStrategy
   */
  public final CacheStrategy<CacheKey, CacheValue> getCacheStrategy() {
    return cacheStrategy;
  }

  /**
   * @return the uriLocatorFactory
   */
  public final UriLocatorFactory getUriLocatorFactory() {
    return locatorFactory;
  }

  /**
   * @return The strategy used to rename bundled resources.
   */
  public final NamingStrategy getNamingStrategy() {
    return this.namingStrategy;
  }

  public final GroupExtractor getGroupExtractor() {
    return groupExtractor;
  }

  public CacheKeyFactory getCacheKeyFactory() {
    return cacheKeyFactory;
  }

  public MetaDataFactory getMetaDataFactory() {
    return metaDataFactory;
  }

  public ResourceWatcher getResourceWatcher() {
    return resourceWatcher;
  }

  /**
   * Registers a callback.
   *
   * @param callbackFactory
   *          {@link ObjectFactory} responsible for creating {@link LifecycleCallback} instance.
   */
  public final void registerCallback(final ObjectFactory<LifecycleCallback> callbackFactory) {
    notNull(callbackFactory);
    getCallbackRegistry().registerCallback(callbackFactory);
  }

  public LifecycleCallbackRegistry getCallbackRegistry() {
    // TODO check if initialization is required.
    if (callbackRegistry == null) {
      callbackRegistry = new LifecycleCallbackRegistry();
    }
    return callbackRegistry;
  }

  public ResourceAuthorizationManager getResourceAuthorizationManager() {
    return authorizationManager;
  }

  private static class EmptyModelFactory
      implements WroModelFactory {
    public WroModel create() {
      return new WroModel();
    }

    public void destroy() {
    }
  }

  public static class Builder {
    private WroModelFactory modelFactory = new EmptyModelFactory();
    private GroupExtractor groupExtractor = new DefaultGroupExtractor();
    private CacheStrategy<CacheKey, CacheValue> cacheStrategy = new LruMemoryCacheStrategy<CacheKey, CacheValue>();
    private ProcessorsFactory processorsFactory = new SimpleProcessorsFactory();
    private UriLocatorFactory locatorFactory = new DefaultUriLocatorFactory();
    private NamingStrategy namingStrategy = new NoOpNamingStrategy();
    private LifecycleCallbackRegistry callbackRegistry = new LifecycleCallbackRegistry();
    private HashStrategy hashStrategy = new SHA1HashStrategy();
    private List<Transformer<WroModel>> modelTransformers = createDefaultTransformers();
    private ResourceAuthorizationManager authorizationManager = new DefaultResourceAuthorizationManager();
    private CacheKeyFactory cacheKeyFactory = new DefaultCacheKeyFactory();
    private MetaDataFactory metaDataFactory = new DefaultMetaDataFactory();

    public Builder() {
    }

    public Builder(final WroManager manager) {
      notNull(manager);
      this.groupExtractor = manager.getGroupExtractor();
      this.cacheStrategy = manager.getCacheStrategy();
      this.processorsFactory = manager.getProcessorsFactory();
      this.locatorFactory = manager.getUriLocatorFactory();
      this.namingStrategy = manager.getNamingStrategy();
      this.callbackRegistry = manager.getCallbackRegistry();
      this.hashStrategy = manager.getHashStrategy();
      this.modelFactory = manager.getModelFactory();
      this.authorizationManager = manager.getResourceAuthorizationManager();
      this.cacheKeyFactory = manager.getCacheKeyFactory();
      this.metaDataFactory = manager.getMetaDataFactory();
    }

    public Builder setModelFactory(final WroModelFactory modelFactory) {
      this.modelFactory = modelFactory;
      return this;
    }

    public Builder setGroupExtractor(final GroupExtractor groupExtractor) {
      notNull(groupExtractor);
      this.groupExtractor = groupExtractor;
      return this;
    }

    public Builder setCacheStrategy(final CacheStrategy<CacheKey, CacheValue> cacheStrategy) {
      notNull(cacheStrategy);
      this.cacheStrategy = cacheStrategy;
      return this;
    }

    public Builder setProcessorsFactory(final ProcessorsFactory processorsFactory) {
      notNull(processorsFactory);
      this.processorsFactory = processorsFactory;
      return this;
    }

    public Builder setLocatorFactory(final UriLocatorFactory locatorFactory) {
      notNull(locatorFactory);
      this.locatorFactory = locatorFactory;
      return this;
    }

    public Builder setNamingStrategy(final NamingStrategy namingStrategy) {
      notNull(namingStrategy);
      this.namingStrategy = namingStrategy;
      return this;
    }

    public Builder setCallbackRegistry(final LifecycleCallbackRegistry callbackRegistry) {
      notNull(callbackRegistry);
      this.callbackRegistry = callbackRegistry;
      return this;
    }

    public Builder setHashStrategy(final HashStrategy hashStrategy) {
      notNull(hashStrategy);
      this.hashStrategy = hashStrategy;
      return this;
    }

    public Builder setModelTransformers(final List<Transformer<WroModel>> modelTransformers) {
      notNull(modelTransformers);
      this.modelTransformers = modelTransformers;
      return this;
    }

    public Builder setAuthorizationManager(final ResourceAuthorizationManager authorizationManager) {
      notNull(authorizationManager);
      this.authorizationManager = authorizationManager;
      return this;
    }

    public Builder setCacheKeyFactory(final CacheKeyFactory cacheKeyFactory) {
      notNull(cacheKeyFactory);
      this.cacheKeyFactory = cacheKeyFactory;
      return this;
    }

    public Builder setMetaDataFactory(final MetaDataFactory metaDataFactory) {
      notNull(metaDataFactory);
      this.metaDataFactory = metaDataFactory;
      return this;
    }

    private List<Transformer<WroModel>> createDefaultTransformers() {
      final List<Transformer<WroModel>> list = new ArrayList<Transformer<WroModel>>();
      list.add(new WildcardExpanderModelTransformer());
      return list;
    }

    public WroManager build() {
      final WroManager manager = new WroManager(this);
      InjectorBuilder.create(manager).build().inject(manager);
      return manager;
    }
  }
}


package ro.isdc.wro.util.provider;

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

import ro.isdc.wro.cache.CacheKey;
import ro.isdc.wro.cache.CacheStrategy;
import ro.isdc.wro.cache.CacheValue;
import ro.isdc.wro.http.handler.RequestHandler;
import ro.isdc.wro.model.factory.WroModelFactory;
import ro.isdc.wro.model.resource.locator.UriLocator;
import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.model.resource.support.hash.HashStrategy;
import ro.isdc.wro.model.resource.support.naming.NamingStrategy;

/**
 * Default implementation of {@link ConfigurableProvider} which provides nothing by default.
 *
 * @author Alex Objelean
 * @created 16 Jun 2012
 * @since 1.4.7
 */
public class ConfigurableProviderSupport
    implements ConfigurableProvider {
  /**
   * {@inheritDoc}
   */
  public Map<String, ResourcePreProcessor> providePreProcessors() {
    return new HashMap<String, ResourcePreProcessor>();
  }

  /**
   * {@inheritDoc}
   */
  public Map<String, ResourcePostProcessor> providePostProcessors() {
    return new HashMap<String, ResourcePostProcessor>();
  }

  /**
   * {@inheritDoc}
   */
  public Map<String, NamingStrategy> provideNamingStrategies() {
    return new HashMap<String, NamingStrategy>();
  }

  /**
   * {@inheritDoc}
   */
  public Map<String, HashStrategy> provideHashStrategies() {
    return new HashMap<String, HashStrategy>();
  }

  /**
   * {@inheritDoc}
   */
  public Map<String, UriLocator> provideLocators() {
    return new HashMap<String, UriLocator>();
  }

  /**
   * {@inheritDoc}
   */
  public Map<String, CacheStrategy<CacheKey, CacheValue>> provideCacheStrategies() {
    return new HashMap<String, CacheStrategy<CacheKey, CacheValue>>();
  }

  /**
   * {@inheritDoc}
   */
  public Map<String, RequestHandler> provideRequestHandlers() {
    return new HashMap<String, RequestHandler>();
  }

  /**
   * {@inheritDoc}
   */
  public Map<String, WroModelFactory> provideModelFactories() {
    return new HashMap<String, WroModelFactory>();
  }
}


package ro.isdc.wro.http.handler;

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

import java.io.IOException;
import java.util.UUID;

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

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

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.cache.CacheKey;
import ro.isdc.wro.model.group.Inject;
import ro.isdc.wro.model.resource.ResourceType;
import ro.isdc.wro.model.resource.support.change.ResourceWatcher;

/**
 * Triggers resource watcher check. This handler is not meant to be invoked publicly. It has a protection mechanism,
 * which allows only server-side handling.
 *
 * @author Alex Objelean
 * @created 9 Jan 2014
 * @since 1.7.3
 */
public class ResourceWatcherRequestHandler
    extends RequestHandlerSupport {
  private static final Logger LOG = LoggerFactory.getLogger(ResourceWatcherRequestHandler.class);
  /**
   * @VisibleForTesting
   */
  static final String PARAM_GROUP_NAME = "group";
  /**
   * @VisibleForTesting
   */
  static final String PARAM_RESOURCE_TYPE = "resourceType";
  /**
   * @VisibleForTesting
   */
  static final String PARAM_AUTH_KEY = "auth";
  static final String PATH_HANDLER = "resourceWatcher";
  /**
   * The alias of this {@link RequestHandler} used for configuration.
   */
  public static final String ALIAS = "resourceWatcher";
  @Inject
  private ResourceWatcher resourceWatcher;
  /**
   * A random string used to authorize request. This key is updated after each successful handle operation to avoid
   * hijacking.
   */
  private static String authorizationKey;

  public ResourceWatcherRequestHandler() {
    updateAuthorizationKey();
  }

  @Override
  public void handle(final HttpServletRequest request, final HttpServletResponse response)
      throws IOException {
    resourceWatcher.check(retrieveCacheKey(request));
    updateAuthorizationKey();
  }

  private CacheKey retrieveCacheKey(final HttpServletRequest request) {
    CacheKey cacheKey = null;
    final String resourceTypeAsString = request.getParameter(PARAM_RESOURCE_TYPE);
    final String groupName = request.getParameter(PARAM_GROUP_NAME);
    try {
      final ResourceType resourceType = ResourceType.get(resourceTypeAsString);
      isTrue(groupName != null);
      if (groupName != null) {
        LOG.debug("groupName={}, resourceType={}", groupName, resourceType);
        cacheKey = new CacheKey(groupName, resourceType);
      }
    } catch (final IllegalArgumentException e) {
      LOG.debug("groupName={}, resourceType={}", groupName, resourceTypeAsString);
      throw WroRuntimeException.wrap(e, "Cannot retrieve cacheKey from the request");
    }
    return cacheKey;
  }

  @Override
  public boolean accept(final HttpServletRequest request) {
    // Authorize only server-side included request (performed by {@link DispatcherStreamLocator}). Any public access to
    // this request handler is forbidden.
    final boolean isHandlerRequest = isHandlerRequest(request);
    return isHandlerRequest && isAuthorized(request);
  }

  private boolean isAuthorized(final HttpServletRequest request) {
    final String actualKey = request.getParameter(PARAM_AUTH_KEY);
    final boolean isAuthorized = authorizationKey.equals(actualKey);
    if (!isAuthorized) {
      LOG.debug("Unauthorized request. actualKey={}, expected={}", actualKey, authorizationKey);
    }
    return isAuthorized;
  }

  /**
   * Checks if the provided url is a resource proxy request.
   *
   * @param url
   *          to check.
   * @return true if the provided url is a proxy resource.
   */
  private boolean isHandlerRequest(final HttpServletRequest request) {
    String apiHandlerValue = request.getParameter(PATH_API);
    return PATH_HANDLER.equals(apiHandlerValue) && retrieveCacheKey(request) != null;
  }

  /**
   * Updates the authorizationKey with an unique value.
   */
  private void updateAuthorizationKey() {
    authorizationKey = generateRandomKey();
  }

  /**
   * @VisibleForTesting
   * @return a random key used for authorization.
   */
  String generateRandomKey() {
    return UUID.randomUUID().toString();
  }

  /**
   * Computes the servlet context relative url to call this handler using a server-side invocation. Hides the details
   * about creating a valid url and providing the authorization key required to invoke this handler.
   */
  public static String createHandlerRequestPath(final CacheKey cacheKey, final HttpServletRequest request) {
    final String handlerQueryPath = getRequestHandlerPath(cacheKey.getGroupName(), cacheKey.getType());
    return request.getServletPath() + handlerQueryPath;
  }

  private static String getRequestHandlerPath() {
    return String.format("?%s=%s", PATH_API, PATH_HANDLER);
  }

  private static String getRequestHandlerPath(final String groupName, final ResourceType resourceType) {
    return String.format("%s&%s=%s&%s=%s&%s=%s", getRequestHandlerPath(), PARAM_GROUP_NAME, groupName,
        PARAM_RESOURCE_TYPE, resourceType.name(), PARAM_AUTH_KEY, authorizationKey);
  }
}


/**
 * Copyright Alex Objelean
 */
package ro.isdc.wro.maven.plugin;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Properties;
import java.util.concurrent.Callable;

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

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.AutoCloseInputStream;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.mockito.Mockito;
import org.sonatype.plexus.build.incremental.BuildContext;

import ro.isdc.wro.config.Context;
import ro.isdc.wro.config.jmx.WroConfiguration;
import ro.isdc.wro.http.support.DelegatingServletOutputStream;
import ro.isdc.wro.maven.plugin.support.AggregatedFolderPathResolver;
import ro.isdc.wro.model.resource.ResourceType;
import ro.isdc.wro.model.resource.locator.ServletContextUriLocator;
import ro.isdc.wro.util.StopWatch;
import ro.isdc.wro.util.io.UnclosableBufferedInputStream;

/**
 * A build-time solution for organizing and minimizing static resources. By default uses the same configuration as the
 * run-time solution. Additionally, allows you to change the processors used by changing the wroManagerFactory
 * implementation used by the plugin.
 *
 * @goal run
 * @phase compile
 * @requiresDependencyResolution runtime
 * @author Alex Objelean
 */
public class Wro4jMojo
    extends AbstractWro4jMojo {
  /**
   * The path to the destination directory where the files are stored at the end of the process.
   *
   * @parameter default-value="${project.build.directory}" property="destinationFolder"
   * @optional
   */
  private File destinationFolder;
  /**
   * @parameter property="cssDestinationFolder"
   * @optional
   */
  private File cssDestinationFolder;
  /**
   * @parameter property="jsDestinationFolder"
   * @optional
   */
  private File jsDestinationFolder;
  /**
   * This parameter is not meant to be used. The only purpose is to hold project build directory
   *
   * @parameter default-value="${project.build.directory}"
   * @optional
   */
  private File buildDirectory;
  /**
   * This parameter is not meant to be used. The only purpose is to hold the final build name of the artifact
   *
   * @parameter default-value="${project.build.directory}/${project.build.finalName}"
   * @optional
   */
  private File buildFinalName;
  /**
   * @parameter property="groupNameMappingFile"
   * @optional
   */
  private File groupNameMappingFile;
  /**
   * Useful when the application is deployed under a contextPath which is different than ROOT (example: "myapp"). This
   * will be used by CssUrlRewritingProcessor to compute properly the url's. By default, the ROOT is assumed, meaning
   * that the rewritten url's will start with "/".
   *
   * @parameter property="contextPath"
   * @optional
   */
  private String contextPath;
  /**
   * Holds a mapping between original group name file & renamed one.
   */
  private final Properties groupNames = new Properties();

  @Override
  protected void validate()
      throws MojoExecutionException {
    super.validate();
    // additional validation requirements
    if (destinationFolder == null) {
      throw new MojoExecutionException("destinationFolder was not set!");
    }
  }

  @Override
  protected void onBeforeExecute() {
    groupNames.clear();
    if (groupNameMappingFile != null && isIncrementalBuild()) {
      try {
        // reuse stored properties for incremental build
        groupNames.load(new AutoCloseInputStream(new FileInputStream(groupNameMappingFile)));
      } catch (final IOException e) {
        getLog().debug("Cannot load " + groupNameMappingFile.getPath());
      }
    }
  }

  @Override
  protected void doExecute()
      throws Exception {
    if (contextPath != null) {
      getLog().info("contextPath: " + contextPath);
    }
    getLog().info("destinationFolder: " + destinationFolder);
    if (jsDestinationFolder != null) {
      getLog().info("jsDestinationFolder: " + jsDestinationFolder);
    }
    if (cssDestinationFolder != null) {
      getLog().info("cssDestinationFolder: " + cssDestinationFolder);
    }
    if (groupNameMappingFile != null) {
      getLog().info("groupNameMappingFile: " + groupNameMappingFile);
    }
    final Collection<String> groupsAsList = getTargetGroupsAsList();
    final StopWatch watch = new StopWatch();
    watch.start("processGroups: " + groupsAsList);

    final Collection<Callable<Void>> callables = new ArrayList<Callable<Void>>();

    for (final String group : groupsAsList) {
      for (final ResourceType resourceType : ResourceType.values()) {
        final File destinationFolder = computeDestinationFolder(resourceType);
        final String groupWithExtension = group + "." + resourceType.name().toLowerCase();

        if (isParallelProcessing()) {
          callables.add(Context.decorate(new Callable<Void>() {
            public Void call()
                throws Exception {
              processGroup(groupWithExtension, destinationFolder);
              return null;
            }
          }));
        } else {
          processGroup(groupWithExtension, destinationFolder);
        }
      }
    }
    if (isParallelProcessing()) {
      getTaskExecutor().submit(callables);
    }
    watch.stop();
    getLog().debug(watch.prettyPrint());
    writeGroupNameMap();
  }

  @Override
  protected boolean isIncrementalCheckRequired() {
    return super.isIncrementalCheckRequired() && destinationFolder.exists();
  }

  private void writeGroupNameMap()
      throws Exception {
    if (groupNameMappingFile != null) {
      FileOutputStream outputStream = null;
      try {
        final File mappingFileParent = new File(groupNameMappingFile.getParent());
        // create missing folders if needed
        mappingFileParent.mkdirs();
        outputStream = new FileOutputStream(groupNameMappingFile);
        groupNames.store(outputStream, "Mapping of defined group name to renamed group name");
      } catch (final FileNotFoundException ex) {
        throw new MojoExecutionException("Unable to save group name mapping file", ex);
      } finally {
        IOUtils.closeQuietly(outputStream);
      }
    }
  }

  /**
   * Encodes a version using some logic.
   *
   * @param group
   *          the name of the resource to encode.
   * @param input
   *          the stream of the result content.
   * @return the name of the resource with the version encoded.
   */
  private String rename(final String group, final InputStream input)
      throws Exception {
    try {
      final String newName = getManagerFactory().create().getNamingStrategy().rename(group, input);
      groupNames.setProperty(group, newName);
      return newName;
    } catch (final IOException e) {
      throw new MojoExecutionException("Error occured during renaming", e);
    }
  }

  /**
   * Computes the destination folder based on resource type.
   *
   * @param resourceType
   *          {@link ResourceType} to process.
   * @return destinationFoder where the result of resourceType will be copied.
   * @throws MojoExecutionException
   *           if computed folder is null.
   */
  private File computeDestinationFolder(final ResourceType resourceType)
      throws MojoExecutionException {
    File folder = destinationFolder;
    if (resourceType == ResourceType.JS) {
      if (jsDestinationFolder != null) {
        folder = jsDestinationFolder;
      }
    }
    if (resourceType == ResourceType.CSS) {
      if (cssDestinationFolder != null) {
        folder = cssDestinationFolder;
      }
    }
    getLog().info("folder: " + folder);
    if (folder == null) {
      throw new MojoExecutionException("Couldn't compute destination folder for resourceType: " + resourceType
          + ". That means that you didn't define one of the following parameters: "
          + "destinationFolder, cssDestinationFolder, jsDestinationFolder");
    }
    if (!folder.exists()) {
      folder.mkdirs();
    }
    return folder;
  }

  /**
   * Process a single group.
   */
  private void processGroup(final String group, final File parentFoder)
      throws Exception {
    ByteArrayOutputStream resultOutputStream = null;
    InputStream resultInputStream = null;
    try {
      getLog().info("processing group: " + group);

      // mock request
      final HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
      Mockito.when(request.getContextPath()).thenReturn(normalizeContextPath(contextPath));
      Mockito.when(request.getRequestURI()).thenReturn(group);
      // mock response
      final HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
      resultOutputStream = new ByteArrayOutputStream();
      Mockito.when(response.getOutputStream()).thenReturn(new DelegatingServletOutputStream(resultOutputStream));

      // init context
      final WroConfiguration config = Context.get().getConfig();
      // the maven plugin should ignore empty groups, since it will try to process all types of resources.
      config.setIgnoreEmptyGroup(true);
      Context.set(Context.webContext(request, response, Mockito.mock(FilterConfig.class)), config);

      Context.get().setAggregatedFolderPath(getAggregatedPathResolver().resolve());
      // perform processing
      getManagerFactory().create().process();
      // encode version & write result to file
      resultInputStream = new UnclosableBufferedInputStream(resultOutputStream.toByteArray());
      final File destinationFile = new File(parentFoder, rename(group, resultInputStream));
      final File parentFolder = destinationFile.getParentFile();
      if (!parentFolder.exists()) {
        // make directories if required
        parentFolder.mkdirs();
      }
      destinationFile.createNewFile();
      // allow the same stream to be read again
      resultInputStream.reset();
      getLog().debug("Created file: " + destinationFile.getName());

      final OutputStream fos = new FileOutputStream(destinationFile);
      // use reader to detect encoding
      IOUtils.copy(resultInputStream, fos);
      fos.close();
      // delete empty files
      if (destinationFile.length() == 0) {
        getLog().debug("No content found for group: " + group);
        destinationFile.delete();
      } else {
        getLog().info("file size: " + destinationFile.getName() + " -> " + destinationFile.length() + " bytes");
        getLog().info(destinationFile.getAbsolutePath() + " (" + destinationFile.length() + " bytes" + ")");
      }
    } finally {
      // instruct the build about the change in context of incremental build
      if (getBuildContext() != null) {
        getBuildContext().refresh(parentFoder);
      }
      if (resultOutputStream != null) {
        resultOutputStream.close();
      }
      if (resultInputStream != null) {
        resultInputStream.close();
      }
    }
  }

  /**
   * @return normalized representation of the context path. Example: "/myapp". Will add or strip "/" separator depending
   *         on provided input.
   */
  private String normalizeContextPath(final String contextPath) {
    final String separator = ServletContextUriLocator.PREFIX;
    final StringBuffer sb = new StringBuffer(separator);
    if (contextPath != null) {
      String normalizedContextPath = contextPath;
      normalizedContextPath = StringUtils.removeStart(normalizedContextPath, separator);
      normalizedContextPath = StringUtils.removeEnd(normalizedContextPath, separator);
      sb.append(normalizedContextPath);
    }
    return sb.toString();
  }

  private AggregatedFolderPathResolver getAggregatedPathResolver() {
    return new AggregatedFolderPathResolver().setBuildDirectory(buildDirectory).setBuildFinalName(buildFinalName).setContextFoldersAsCSV(
        getContextFoldersAsCSV()).setCssDestinationFolder(cssDestinationFolder).setDestinationFolder(destinationFolder).setLog(
        getLog());
  }

  /**
   * @param destinationFolder
   *          the destinationFolder to set
   * @VisibleForTesting
   */
  void setDestinationFolder(final File destinationFolder) {
    this.destinationFolder = destinationFolder;
  }

  /**
   * @param cssDestinationFolder
   *          the cssDestinationFolder to set
   * @VisibleForTesting
   */
  void setCssDestinationFolder(final File cssDestinationFolder) {
    this.cssDestinationFolder = cssDestinationFolder;
  }

  /**
   * @param jsDestinationFolder
   *          the jsDestinationFolder to set
   * @VisibleForTesting
   */
  void setJsDestinationFolder(final File jsDestinationFolder) {
    this.jsDestinationFolder = jsDestinationFolder;
  }

  /**
   * The folder where the project is built.
   *
   * @param buildDirectory
   *          the buildDirectory to set
   * @VisibleForTesting
   */
  void setBuildDirectory(final File buildDirectory) {
    this.buildDirectory = buildDirectory;
  }

  /**
   * @param buildFinalName
   *          the buildFinalName to set
   */
  public void setBuildFinalName(final File buildFinalName) {
    this.buildFinalName = buildFinalName;
  }

  /**
   * @param groupNameMappingFile
   *          the groupNameMappingFile to set
   * @VisibleForTesting
   */
  void setGroupNameMappingFile(final File groupNameMappingFile) {
    this.groupNameMappingFile = groupNameMappingFile;
  }

  /**
   * @VisibleForTesting
   */
  void setContextPath(final String contextPath) {
    this.contextPath = contextPath;
  }
}


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

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

import ro.isdc.wro.model.resource.Resource;

/**
 * Base Wro Runtime exception. All exceptions will extend this runtime exception.
 *
 * @author Alex Objelean
 * @created Created on Nov 3, 2008
 */
public class WroRuntimeException
    extends RuntimeException {
  /**
   * serialVersionUID
   */
  private static final long serialVersionUID = 1L;
  private static final Logger LOG = LoggerFactory.getLogger(WroRuntimeException.class);
  private Resource resource;

  public WroRuntimeException(final String message, final Throwable cause) {
    super(message, cause);
    LOG.debug(message);
  }

  /**
   * @param message
   */
  public WroRuntimeException(final String message) {
    this(message, null);
  }

  /**
   * Logs the error of this exception. By default errors are logged with DEBUG level. This method will use ERROR level.
   */
  public WroRuntimeException logError() {
    LOG.error(getMessage());
    return this;
  }

  /**
   * Wraps original exception into {@link WroRuntimeException} and throw it.
   *
   * @param e
   *          the exception to wrap.
   */
  public static WroRuntimeException wrap(final Exception e) {
    return wrap(e, e.getMessage());
  }

  /**
   * Wraps original exception into {@link WroRuntimeException} and throw it.
   *
   * @param e
   *          the exception to wrap.
   * @param message
   *          the message of the exception to wrap.
   */
  public static WroRuntimeException wrap(final Exception e, final String message) {
    if (e instanceof WroRuntimeException) {
      return (WroRuntimeException) e;
    }
    return new WroRuntimeException(message, e);
  }

  /**
   * @param resourceUri
   *          The name of the resource being processed when exception occurs.
   */
  public WroRuntimeException setResource(final Resource resource) {
    this.resource = resource;
    return this;
  }

  /**
   * @return The processed {@link Resource} which caused the exception.
   */
  public Resource getResource() {
    return resource;
  }
}


/**
 * Copyright@2010 Alex Objelean
 */
package ro.isdc.wro.extensions.processor.js;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.logging.Level;

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

import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.ClosureCodingConvention;
import com.google.javascript.jscomp.CompilationLevel;
import com.google.javascript.jscomp.Compiler;
import com.google.javascript.jscomp.CompilerOptions;
import com.google.javascript.jscomp.DiagnosticGroups;
import com.google.javascript.jscomp.Result;
import com.google.javascript.jscomp.SourceFile;

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.extensions.processor.support.ObjectPoolHelper;
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.Destroyable;
import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.util.ObjectFactory;

/**
 * Uses Google closure compiler for js minimization.
 *

 * In order to make this class {@link Context} agnostic, set the encoding explicitly using
 * {@link GoogleClosureCompressorProcessor#setEncoding(String)}.
 *
 * @see http://blog.bolinfest.com/2009/11/calling-closure-compiler-from-java.html
 * @author Alex Objelean
 */
@Minimize
@SupportedResourceType(ResourceType.JS)
public class GoogleClosureCompressorProcessor
    implements ResourcePostProcessor, ResourcePreProcessor, Destroyable {
  public static final String ALIAS_SIMPLE = "googleClosureSimple";
  public static final String ALIAS_ADVANCED = "googleClosureAdvanced";
  public static final String ALIAS_WHITESPACE_ONLY = "googleClosureWhitespace";
  /**
   * {@link CompilationLevel} to use for compression.
   */
  private CompilationLevel compilationLevel;
  /**
   * Reuse options(which are not thread safe).
   */
  private ObjectPoolHelper<CompilerOptions> optionsPool;
  @Inject
  private ReadOnlyContext context;
  private String encoding;

  /**
   * Uses google closure compiler with default compilation level: {@link CompilationLevel#SIMPLE_OPTIMIZATIONS}
   */
  public GoogleClosureCompressorProcessor() {
    this(CompilationLevel.SIMPLE_OPTIMIZATIONS);
  }

  /**
   * Uses google closure compiler with specified compilation level.
   *
   * @param compilationLevel
   *          not null {@link CompilationLevel} enum.
   */
  public GoogleClosureCompressorProcessor(final CompilationLevel compilationLevel) {
    Validate.notNull(compilationLevel);
    /**
     * Using pool to fix the threadSafety issue. See issue.
     */
    optionsPool = new ObjectPoolHelper<CompilerOptions>(new ObjectFactory<CompilerOptions>() {
      @Override
      public CompilerOptions create() {
        return newCompilerOptions();
      }
    });
    this.compilationLevel = compilationLevel;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void process(final Resource resource, final Reader reader, final Writer writer)
      throws IOException {
    final String content = IOUtils.toString(reader);
    final CompilerOptions compilerOptions = optionsPool.getObject();
    final Compiler compiler = newCompiler(compilerOptions);
    try {
      final String fileName = resource == null ? "wro4j-processed-file.js" : resource.getUri();
      final SourceFile[] input = new SourceFile[] {
        SourceFile.fromInputStream(fileName, new ByteArrayInputStream(content.getBytes(getEncoding())))
      };
      SourceFile[] externs = getExterns(resource);
      if (externs == null) {
        // fallback to empty array when null is provided.
        externs = new SourceFile[] {};
      }
      Result result = null;
      result = compiler.compile(Arrays.asList(externs), Arrays.asList(input), compilerOptions);
      if (result.success) {
        writer.write(compiler.toSource());
      } else {
        throw new WroRuntimeException("Compilation has errors: " + Arrays.asList(result.errors));
      }
    } catch (final Exception e) {
      onException(e);
    } finally {
      reader.close();
      writer.close();
      optionsPool.returnObject(compilerOptions);
    }
  }

  /**
   * Invoked when an exception occurs during processing. Default implementation wraps the exception into
   * {@link WroRuntimeException} and throws it further.
   *
   * @param e
   *          {@link Exception} thrown during processing.
   */
  protected void onException(final Exception e) {
    throw WroRuntimeException.wrap(e);
  }

  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;
  }

  private Compiler newCompiler(final CompilerOptions compilerOptions) {
    Compiler.setLoggingLevel(Level.SEVERE);
    final Compiler compiler = new Compiler();
    compilationLevel.setOptionsForCompilationLevel(compilerOptions);
    // make it play nice with GAE
    compiler.disableThreads();
    compiler.initOptions(compilerOptions);
    return compiler;
  }

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

  /**
   * @param resource
   *          Currently processed resource. The resource can be null, when the closure compiler is used as a post
   *          processor.
   * @return An Array of externs files for the resource to process.
   */
  protected SourceFile[] getExterns(final Resource resource) {
    return new SourceFile[] {};
  }

  /**
   * @param compilationLevel
   *          the compilationLevel to set
   */
  public GoogleClosureCompressorProcessor setCompilationLevel(final CompilationLevel compilationLevel) {
    this.compilationLevel = compilationLevel;
    return this;
  }

  /**
   * @return default {@link CompilerOptions} object to be used by compressor.
   */
  protected CompilerOptions newCompilerOptions() {
    final CompilerOptions options = new CompilerOptions();
    /**
     * According to John Lenz from the Closure Compiler project, if you are using the Compiler API directly, you should
     * specify a CodingConvention. {@link http://code.google.com/p/wro4j/issues/detail?id=155}
     */
    options.setCodingConvention(new ClosureCodingConvention());
    // use the wro4j encoding by default
    options.setOutputCharset(Charset.forName(getEncoding()));
    // set it to warning, otherwise compiler will fail
    options.setWarningLevel(DiagnosticGroups.CHECK_VARIABLES, CheckLevel.WARNING);
    return options;
  }

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

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


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

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

import ro.isdc.wro.model.group.processor.Minimize;
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.ImportAware;
import ro.isdc.wro.model.resource.processor.MinimizeAware;
import ro.isdc.wro.model.resource.processor.ResourceProcessorAware;
import ro.isdc.wro.model.resource.processor.SupportAware;
import ro.isdc.wro.model.resource.processor.SupportedResourceTypeAware;
import ro.isdc.wro.util.AbstractDecorator;

/**
 * Hides details common to all processors decorators, like ability to identify if a processor is minimize aware
 *
 * @author Alex Objelean
 * @created 11 Apr 2012
 * @since 1.4.6
 */
public abstract class AbstractProcessorDecoratorSupport<T>
    extends AbstractDecorator<T>
    implements ResourceProcessorAware {
  /**
   * @param the
   *          decorated processor. The type of the returned object is {@link Object} because we don't really care and we
   *          need it only to check if the processor is minimize aware and get its supported type. This "hack" will e
   *          removed in 1.5.0.
   */
  public AbstractProcessorDecoratorSupport(final T decorated) {
    super(decorated);
  }

  /**
   * This method is final, because it intends to preserve the getSupportedResourceType flag of the decorated processor.
   * You still can override this behavior by implementing
   * {@link AbstractProcessorDecoratorSupport#getSupportedResourceTypeInternal()} on your own risk.
   *
   * @return the {@link SupportedResourceType} annotation of the decorated processor if one exist.
   */
  public final SupportedResourceType getSupportedResourceType() {
    return getSupportedResourceTypeInternal();
  }

  /**
   * Allow subclass override the way getSupportedResourceType is used.
   */
  protected SupportedResourceType getSupportedResourceTypeInternal() {
    return getSupportedResourceTypeForProcessor(getDecoratedObject());
  }

  /**
   * Computes {@link SupportedResourceType} for provided processor.
   */
  final SupportedResourceType getSupportedResourceTypeForProcessor(final Object processor) {
    SupportedResourceType supportedType = processor.getClass().getAnnotation(SupportedResourceType.class);
    /**
     * This is a special case for processors which implement {@link SupportedResourceTypeProvider} interface. This is
     * useful for decorator processors which needs to "inherit" the {@link SupportedResourceType} of the decorated
     * processor.
     */
    if (processor instanceof SupportedResourceTypeAware) {
      supportedType = ((SupportedResourceTypeAware) processor).getSupportedResourceType();
    }
    return supportedType;
  }

  /**
   * This method is final, because it intends to preserve the minimize flag of the decorated processor. You still can
   * override this behavior by implementing {@link AbstractProcessorDecoratorSupport#isMinimizeInternal()} on your own
   * risk.
   */
  public final boolean isMinimize() {
    return isMinimizeInternal();
  }

  /**
   * Allow subclass override the way isMinimized is used.
   */
  protected boolean isMinimizeInternal() {
    return isMinimizeForProcessor(getDecoratedObject());
  }

  /**
   * {@inheritDoc}
   */
  public boolean isSupported() {
    return getDecoratedObject() instanceof SupportAware ? ((SupportAware) getDecoratedObject()).isSupported() : true;
  }

  /**
   * {@inheritDoc}
   */
  public boolean isImportAware() {
    return getDecoratedObject() instanceof ImportAware ? ((ImportAware) getDecoratedObject()).isImportAware() : false;
  }

  /**
   * {@inheritDoc}
   */
  public void destroy()
      throws Exception {
    if (getDecoratedObject() instanceof Destroyable) {
      ((Destroyable) getDecoratedObject()).destroy();
    }
  }

  /**
   * @return true if passed processor is minimize aware.
   */
  final boolean isMinimizeForProcessor(final Object processor) {
    if (processor instanceof MinimizeAware) {
      return ((MinimizeAware) processor).isMinimize();
    }
    return processor.getClass().isAnnotationPresent(Minimize.class);
  }

  /**
   * @return the array of supported resources the processor can process.
   */
  public final ResourceType[] getSupportedResourceTypes() {
    final SupportedResourceType supportedType = getSupportedResourceType();
    return supportedType == null ? ResourceType.values() : new ResourceType[] {
      supportedType.value()
    };
  }

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


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

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

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

import org.apache.commons.io.IOUtils;

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

import ro.isdc.wro.model.group.Inject;
import ro.isdc.wro.model.resource.locator.factory.UriLocatorFactory;
import ro.isdc.wro.model.resource.support.hash.HashStrategy;

/**
 * Responsbile for keeping details about resources contents and identify the change of any resource in time.
 *
 * @author Alex Objelean
 * @since 1.6.0
 * @created 14 Oct 2012
 */
public class ResourceChangeDetector {
  private static final Logger LOG = LoggerFactory.getLogger(ResourceChangeDetector.class);
  @Inject
  private UriLocatorFactory locatorFactory;
  @Inject
  private HashStrategy hashStrategy;
  /**
   * Map between a resource uri and a corresponding {@link ResourceChangeInfo} object. It is ensured that any get(key)
   * operation will return a not null object.
   */
  private final Map<String, ResourceChangeInfo> changeInfoMap = new ConcurrentHashMap<String, ResourceChangeInfo>() {
    @Override
    public ResourceChangeInfo get(final Object key) {
      ResourceChangeInfo result = super.get(key);
      if (result == null) {
        result = new ResourceChangeInfo();
        put((String) key, result);
      }
      return result;
    }
  };

  /**
   * Notifies the {@link ResourceChangeDetector} that the change cycle completes and a new one is prepared.
   */
  public void reset() {
    for (final ResourceChangeInfo resourceInfo : changeInfoMap.values()) {
      resourceInfo.reset();
    }
  }

  /**
   * Check if an uri from a particular group has changed.
   *
   * @param uri
   *          the uri to check for change.
   * @param groupName
   *          the name of the group where the uri belongs to.
   * @return true if the change is detected for the provided uri.
   */
  public boolean checkChangeForGroup(final String uri, final String groupName) throws IOException {
    notNull(uri);
    notNull(groupName);
    LOG.debug("group={}, uri={}", groupName, uri);
    final ResourceChangeInfo resourceInfo = changeInfoMap.get(uri);
    if (resourceInfo.isCheckRequiredForGroup(groupName)) {
      final InputStream inputStream = locatorFactory.locate(uri);
      try{
        final String currentHash = hashStrategy.getHash(inputStream);
        resourceInfo.updateHashForGroup(currentHash, groupName);
      }finally{
        IOUtils.closeQuietly(inputStream);
      }
    }
    return resourceInfo.isChanged(groupName);
  }
}


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

import ro.isdc.wro.extensions.processor.support.sass.RubySassEngine;
import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;

/**
 * A processor to support the bourbon (http://thoughtbot.com/bourbon/) mixins library for sass.
 * Using this processor automatically provides sass support, so there is no need to use both this one and
 * the {@link RubySassCssProcessor}.
 * <p/>
 * @author Simon van der Sluis
 * @created 16/05/12
 * @since 1.4.7
 */
public class BourbonCssProcessor extends RubySassCssProcessor implements ResourcePreProcessor, ResourcePostProcessor {
  /**
   * The processor alias.
   */
  public static final String ALIAS = "bourbonCss";
  private static final String BOURBON_GEM_REQUIRE = "bourbon";

  /**
   * A getter used for lazy loading, overrides RubySassEngine#getEngine() and ensure the
   * bourbon gem is imported (required).
   */
  @Override
  protected RubySassEngine newEngine() {
    final RubySassEngine engine = super.newEngine();
    engine.addRequire(BOURBON_GEM_REQUIRE);
    return engine;
  }
}


package ro.isdc.wro.extensions.processor.support.handlebarsjs;

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

import ro.isdc.wro.extensions.locator.WebjarUriLocator;
import ro.isdc.wro.extensions.processor.support.template.AbstractJsTemplateCompiler;

/**
 * HandlebarsJS is a JavaScript templating engine which provides the power necessary to let you build semantic templates
 * effectively with no frustration.
 *
 * @author heldeen
 */
public class HandlebarsJs extends AbstractJsTemplateCompiler {
  private static final String HANDLEBARS_JS_TEMPLATES_INIT = "(function() { var template = Handlebars.template, "
      + "templates = Handlebars.templates = Handlebars.templates || {};";

  private WebjarUriLocator webjarLocator;

  /**
   * {@inheritDoc}
   */
  @Override
  public String compile(final String content, final String name) {
    return HANDLEBARS_JS_TEMPLATES_INIT + "templates[" + name + "] = template("
        + super.compile(content, "") + " ); })();";
  }

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

  /**
   * {@inheritDoc}
   */
  @Override
  protected String getCompileCommand() {
    return "Handlebars.precompile";
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected InputStream getCompilerAsStream() throws IOException {
    return getWebjarLocator().locate(WebjarUriLocator.createUri("handlebars.js"));
  }
}


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

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

import org.mozilla.javascript.ScriptableObject;
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 sass.js version 0.5.0 project:
 * {@link https://github.com/visionmedia/sass.js}.
 *
 * @author Alex Objelean
 */
public class SassCss {
  private static final Logger LOG = LoggerFactory.getLogger(SassCss.class);
  /**
   * The name of the sass script to be used by default.
   */
  public static final String DEFAULT_SASS_JS = "sass-0.5.0.min.js";
  private ScriptableObject scope;

  /**
   * Initialize script builder for evaluation.
   */
  private RhinoScriptBuilder initScriptBuilder() {
    try {
      RhinoScriptBuilder builder = null;
      if (scope == null) {
        final String scriptInit = "var exports = {};";
        builder = RhinoScriptBuilder.newChain().evaluateChain(scriptInit, "initSass").evaluateChain(
          getScriptAsStream(), DEFAULT_SASS_JS);
        scope = builder.getScope();
      } else {
        builder = RhinoScriptBuilder.newChain(scope);
      }
      return builder;
    } catch (final IOException ex) {
      throw new WroRuntimeException("Failed reading javascript sass.js", ex);
    }
  }

  /**
   * @return the stream of the uglify script. Override this method to provide a different script version.
   */
  protected InputStream getScriptAsStream() {
    return SassCss.class.getResourceAsStream(DEFAULT_SASS_JS);
  }

  /**
   * @param data css content to process.
   * @return processed css content.
   */
  public String process(final String data) {
    final StopWatch stopWatch = new StopWatch();
    stopWatch.start("initContext");
    final RhinoScriptBuilder builder = initScriptBuilder();
    stopWatch.stop();

    stopWatch.start("sass rendering");
    try {
      // replace tabs with spaces, since the script doesn't handle well tabs (throws exception).
      // dataWithoutTabs = data;
      final String execute = "exports.render(" + WroUtil.toJSMultiLineString(data) + ");";
      final Object result = builder.evaluate(execute, "sassRender");
      return String.valueOf(result);
    } finally {
      stopWatch.stop();
      LOG.debug(stopWatch.prettyPrint());
    }
  }
}


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

import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.apache.commons.lang3.Validate.notNull;
import static org.apache.commons.lang3.Validate.validState;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;

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

import ro.isdc.wro.manager.factory.standalone.StandaloneContext;
import ro.isdc.wro.manager.factory.standalone.StandaloneContextAware;
import ro.isdc.wro.util.WroUtil;

/**
 * An extension of {@link ServletContextUriLocator} which is used for standalone (or build-time) solution. This class
 * existed initially as an anonymous class, but was extracted since it is complex enough to exist on its own.
 *
 * @author Alex Objelean
 * @created 28 Sep 2013
 * @since 1.7.2
 */
public final class StandaloneServletContextUriLocator
    extends ServletContextUriLocator implements StandaloneContextAware {
  private StandaloneContext standaloneContext;
  private static final Logger LOG = LoggerFactory.getLogger(StandaloneServletContextUriLocator.class);

  public StandaloneServletContextUriLocator() {
  }

  /**
   * This implementation will try to locate the provided resource inside contextFolder configured by standaloneContext.
   * If a resource cannot be located, the next contextFolder from the list will be tried. The first successful result
   * will be returned.
   */
  @Override
  public InputStream locate(final String uri)
      throws IOException {
    validState(standaloneContext != null, "Locator was not initialized properly. StandaloneContext missing.");

    Exception lastException = null;
    final String[] contextFolders = standaloneContext.getContextFolders();
    for(final String contextFolder : contextFolders) {
      try {
        return locateStreamWithContextFolder(uri, contextFolder);
      } catch(final IOException e) {
        lastException = e;
        LOG.debug("Could not locate: {} using contextFolder: {}", uri, contextFolder);
      }
    }
    final String exceptionMessage = String.format("No valid resource '%s' found inside any of contextFolders: %s", uri,
        Arrays.toString(standaloneContext.getContextFolders()));
    throw new IOException(exceptionMessage, lastException);
  }

  /**
   * TODO this is duplicated code (from super) -> find a way to reuse it.
   */
  private InputStream locateStreamWithContextFolder(final String uri, final String contextFolder)
      throws IOException, FileNotFoundException {
    if (getWildcardStreamLocator().hasWildcard(uri)) {
      final String fullPath = WroUtil.getFullPath(uri);
      final String realPath = contextFolder + fullPath;
      return getWildcardStreamLocator().locateStream(uri, new File(realPath));
    }

    final String uriWithoutPrefix = uri.replaceFirst(PREFIX, EMPTY);
    final File file = new File(contextFolder, uriWithoutPrefix);
    LOG.debug("Opening file: " + file.getPath());
    return new FileInputStream(file);
  }

  public void initialize(final StandaloneContext standaloneContext) {
    notNull(standaloneContext);
    this.standaloneContext = standaloneContext;
  }
}


/*
 * Copyright wro4j@2011.
 */
package ro.isdc.wro.extensions.processor.support.csslint;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.List;

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

import ro.isdc.wro.extensions.processor.support.linter.OptionsBuilder;
import ro.isdc.wro.extensions.script.RhinoScriptBuilder;
import ro.isdc.wro.util.StopWatch;
import ro.isdc.wro.util.WroUtil;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

/**
 * CssLint script engine utility. The underlying implementation uses CSSLint script utility<br/>
 * {@link https ://github.com/stubbornella/csslint}. The underlying csslint version is 0.9.10.
 *
 * @author Alex Objelean
 * @since 1.3.8
 * @created 19 Jun 2011
 */
public class CssLint {
  private static final Logger LOG = LoggerFactory.getLogger(CssLint.class);
  /**
   * The name of the csslint script to be used by default.
   */
  private static final String DEFAULT_CSSLINT_JS = "csslint.min.js";
  private final OptionsBuilder optionsBuilder = new OptionsBuilder();
  /**
   * CSV Options to apply.
   */
  private String options;
  private ScriptableObject scope;

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

  /**
   * @return the stream of the csslint script. Override this method to provide a different script version.
   */
  protected InputStream getScriptAsStream() {
    return CssLint.class.getResourceAsStream(DEFAULT_CSSLINT_JS);
  }

  /**
   * Validates a css resource using cssLint and throws {@link CssLintException} if the resource is invalid. If no exception is thrown,
   * the resource is valid.
   *
   * @param data
   *          css content to process.
   * @throws CssLintException
   *           when parsed css has some kind of problems.
   */
  public void validate(final String data)
      throws CssLintException {
    final StopWatch watch = new StopWatch();
    watch.start("init");
    final RhinoScriptBuilder builder = initScriptBuilder();
    watch.stop();
    watch.start("cssLint");
    LOG.debug("options: {}", this.options);
    final String script = buildCssLintScript(WroUtil.toJSMultiLineString(data));
    LOG.debug("script: {}", script);
    builder.evaluate(script, "CSSLint.verify").toString();
    final boolean valid = Boolean.parseBoolean(builder.evaluate("result.length == 0", "checkNoErrors").toString());
    if (!valid) {
      final String json = builder.addJSON().evaluate("JSON.stringify(result)", "CssLint messages").toString();
      LOG.debug("json {}", json);
      final Type type = new TypeToken<List<CssLintError>>() {}.getType();
      final List<CssLintError> errors = new Gson().fromJson(json, type);
      LOG.debug("Errors: {}", errors);
      throw new CssLintException().setErrors(errors);
    }
    LOG.debug("isValid: {}", valid);
    watch.stop();
    LOG.debug(watch.prettyPrint());
  }

  private String buildCssLintScript(final String data) {
    return String.format("var result = CSSLint.verify(%s,%s).messages", data, optionsBuilder.buildFromCsv(options));
  }

  /**
   * @param options CSV representation of the options.
   */
  public CssLint setOptions(final String options) {
    this.options = options;
    return this;
  }

//
//  /**
//   * @param options
//   *          the options to set
//   */
//  public CssLint setOptions(final String... options) {
//    if (options != null && options.length > 0) {
//      this.options = options.length > 1 ? options : optionsBuilder.splitOptions(options[0]);
//    } else {
//      this.options = ArrayUtils.EMPTY_STRING_ARRAY;
//    }
//    LOG.debug("options: {}", Arrays.asList(this.options));
//    return this;
//  }
}


/**
 * Copyright Alex Objelean
 */
package ro.isdc.wro.extensions.processor.support;

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

import org.apache.commons.pool.BasePoolableObjectFactory;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.util.ObjectFactory;

/**
 * A generic aware object pool wrapper. Probably not the best name, but it can be changed later. It helps you to avoid
 * the cast and hides the exception handling by throwing {@link RuntimeException} when borrowing or returning object to
 * the pool fails.
 *
 * @author Alex Objelean
 * @created 10 Nov 2011
 * @since 1.4.2
 */
public class ObjectPoolHelper<T> {
  private static final Logger LOG = LoggerFactory.getLogger(ObjectPoolHelper.class);
  private static final int MAX_IDLE = 2;
  private static final long MAX_WAIT = 5L * 1000L;
  private static final long EVICTABLE_IDLE_TIME = 30 * 1000L;
  // Allows using the objects from the pool in a thread-safe fashion.
  private GenericObjectPool<T> objectPool;

  public ObjectPoolHelper(final ObjectFactory<T> objectFactory) {
    notNull(objectFactory);
    objectPool = createObjectPool(objectFactory);
    notNull(objectPool);
  }

  /**
   * Ensure that a not null pool will be created.
   */
  private GenericObjectPool<T> createObjectPool(final ObjectFactory<T> objectFactory) {
    final GenericObjectPool<T> pool = newObjectPool(objectFactory);
    notNull(pool);
    return pool;
  }

  /**
   * Creates a {@link GenericObjectPool}. Override this method to set custom objectPool configurations.
   */
  protected GenericObjectPool<T> newObjectPool(final ObjectFactory<T> objectFactory) {
    final int maxActive = Math.max(2, Runtime.getRuntime().availableProcessors());
    final GenericObjectPool<T> pool = new GenericObjectPool<T>(new BasePoolableObjectFactory<T>() {
      @Override
      public T makeObject()
        throws Exception {
        return objectFactory.create();
      }
    });
    pool.setMaxActive(maxActive);
    pool.setMaxIdle(MAX_IDLE);
    pool.setMaxWait(MAX_WAIT);
    /**
     * Use WHEN_EXHAUSTED_GROW strategy, otherwise the pool object retrieval can fail. More details here:
     * http://code.google.com/p/wro4j/issues/detail?id=364
     */
    pool.setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_GROW);
    // make object eligible for eviction after a predefined amount of time.
    pool.setSoftMinEvictableIdleTimeMillis(EVICTABLE_IDLE_TIME);
    pool.setTimeBetweenEvictionRunsMillis(EVICTABLE_IDLE_TIME);
    return pool;
  }

  /**
   * @return object from the pool.
   */
  public T getObject() {
    try {
      return objectPool.borrowObject();
    } catch (final Exception e) {
      // should never happen
      throw new RuntimeException("Cannot get object from the pool", e);
    }
  }

  public void returnObject(final T engine) {
    notNull(engine);
    try {
      objectPool.returnObject(engine);
    } catch (final Exception e) {
      // should never happen
      throw new RuntimeException("Cannot get object from the pool", e);
    }
  }

  /**
   * Use a custom {@link GenericObjectPool}.
   *
   * @param objectPool
   *          to use.
   */
  public final void setObjectPool(final GenericObjectPool<T> objectPool) {
    notNull(objectPool);
    this.objectPool = objectPool;
  }

  /**
   * Close the object pool to avoid the memory leak.
   *
   * @throws Exception
   *           if the close operation failed.
   */
  public void destroy() throws Exception {
    LOG.debug("closing objectPool");
    objectPool.close();
  }
}


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

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

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.model.resource.Resource;
import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.util.ObjectFactory;
import ro.isdc.wro.util.WroUtil;

/**
 * A processor which parse a resource and search for placeholders of this type: <code>${}</code> and replace them with
 * the values found in a map provided the by client.
 *
 * @author Alex Objelean
 * @since 1.3.8
 * @created 15 Jun 2011
 */
public class PlaceholderProcessor
  implements ResourcePreProcessor, ResourcePostProcessor {
  private static final Logger LOG = LoggerFactory.getLogger(PlaceholderProcessor.class);
  private static final Pattern PATTERN_PLACEHOLDER = Pattern.compile(WroUtil.loadRegexpWithKey("placeholder"));
  private static final Properties EMPTY_PROPERTIES = new Properties();

  /**
   * Factory used to build Properties object containing values of the variables to substitute.
   */
  private ObjectFactory<Properties> propertiesFactory;

  /**
   * If false, when a variable is not defined, a runtime exception will be thrown. Default value is true - meaning that
   * missing variables will be replaced with empty value.
   */
  private boolean ignoreMissingVariables = true;

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

  /**
   * {@inheritDoc}
   */
  public void process(final Resource resource, final Reader reader, final Writer writer)
    throws IOException {
    final String content = IOUtils.toString(reader);
    final Matcher matcher = PATTERN_PLACEHOLDER.matcher(content);
    final StringBuffer sb = new StringBuffer();

    Properties properties = null;
    if (propertiesFactory != null) {
      properties = propertiesFactory.create();
    }
    //be sure that properties will never be null;
    if (properties == null) {
      properties = EMPTY_PROPERTIES;
    }
    while (matcher.find()) {
      final String variableName = matcher.group(1);
      LOG.debug("found placeholder: {}", variableName);
      matcher.appendReplacement(sb, replaceVariable(properties, variableName));
    }
    matcher.appendTail(sb);
    writer.write(sb.toString());
  }

  /**
   * @param variableName
   * @return
   */
  private String replaceVariable(final Properties properties, final String variableName) {
    final String variableValue = properties.getProperty(variableName);
    if (!ignoreMissingVariables && variableValue == null) {
      throw new WroRuntimeException("No value defind for variable called: [" + variableName + "]");
    }

    final String result = variableValue == null ? StringUtils.EMPTY : variableValue;
    LOG.debug("replacing: [{}] with [{}]", variableName, result);
    return result;
  }

  /**
   * @param ignoreMissingVariables the ignoreMissingVariables to set
   */
  public PlaceholderProcessor setIgnoreMissingVariables(final boolean ignoreMissingVariables) {
    this.ignoreMissingVariables = ignoreMissingVariables;
    return this;
  }

  /**
   * @param propertiesFactory the propertiesFactory to set
   */
  public PlaceholderProcessor setPropertiesFactory(final ObjectFactory<Properties> propertiesFactory) {
    this.propertiesFactory = propertiesFactory;
    return this;
  }
}


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

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

import ro.isdc.wro.model.group.Inject;
import ro.isdc.wro.model.group.processor.Injector;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.SupportedResourceType;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.model.resource.processor.ResourceProcessorAware;
import ro.isdc.wro.util.AbstractDecorator;
import ro.isdc.wro.util.LazyInitializer;

/**
 * Decorates a {@link LazyInitializer} which creates a processor. Allows defer the initialization of the decorated
 * processor, making it unnecessary dependency unless it is really used.
 *
 * @author Alex Objelean
 * @since 1.4.6
 */
public final class LazyProcessorDecorator
    extends AbstractDecorator<LazyInitializer<ResourcePreProcessor>>
    implements ResourceProcessorAware {
  @Inject
  private Injector injector;
  private ProcessorDecorator processor;

  public LazyProcessorDecorator(final LazyInitializer<ResourcePreProcessor> processor) {
    super(processor);
  }

  private ProcessorDecorator getProcessorDecorator() {
    if (processor == null) {
      processor = new ProcessorDecorator(getDecoratedObject().get());
      injector.inject(processor);
    }
    return processor;
  }

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

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

  /**
   * {@inheritDoc}
   */
  public SupportedResourceType getSupportedResourceType() {
    return getProcessorDecorator().getSupportedResourceType();
  }

  /**
   * {@inheritDoc}
   */
  public boolean isMinimize() {
    return getProcessorDecorator().isMinimize();
  }

  /**
   * {@inheritDoc}
   */
  public boolean isSupported() {
    return getProcessorDecorator().isSupported();
  }

  /**
   * {@inheritDoc}
   */
  public boolean isImportAware() {
    return getProcessorDecorator().isImportAware();
  }

  /**
   * {@inheritDoc}
   */
  public void destroy()
      throws Exception {
    getProcessorDecorator().destroy();
  }

  @Override
  public String toString() {
    //Handle situation when toString is invoked before this instance is injected.
    return injector != null ? getProcessorDecorator().toString() : super.toString();
  }
}


package ro.isdc.wro.extensions.http.handler;

import java.lang.reflect.Type;

import ro.isdc.wro.http.handler.ResourceProxyRequestHandler;
import ro.isdc.wro.model.resource.Resource;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

/**
 * Serializes {@link Resource} to json. It transforms the resources to a jsonElement which also provide a proxyUri
 * which the user can use do download the specific resource. If the resource is external
 * (eg. http://www.site.com/style.css) no proxyUri is provided.
 *
 * Example JSON produced by this serializer:
 * {
 *    "type": "CSS",
 *    "uri": "/static/css/style.css",
 *    "proxyUri": "/wro/wroResources?id\u003d/static/css/style.css"
 * }
 *
 * @author Ivar Conradi Østhus
 * @created 15 Jun 2012
 * @since 1.4.7
 */
public class ResourceSerializer implements JsonSerializer<Resource> {
  private final String basePath;

  public ResourceSerializer(final String requestURI) {
    this.basePath = requestURI;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public JsonElement serialize(final Resource resource, final Type type, final JsonSerializationContext context) {
    final JsonObject jsonObject = new JsonObject();
    final String uri = resource.getUri();
    jsonObject.add("type", new JsonPrimitive(resource.getType().toString()));
    jsonObject.add("minimize", new JsonPrimitive(resource.isMinimize()));
    jsonObject.add("uri", new JsonPrimitive(uri));
    jsonObject.add("proxyUri", new JsonPrimitive(getExternalUri(uri)));
    return jsonObject;
  }

  private String getExternalUri(final String uri) {
    return isExternal(uri) ? uri : ResourceProxyRequestHandler.createProxyPath(basePath, uri);
  }

  private boolean isExternal(final String uri) {
    return uri.toLowerCase().startsWith("http://") || uri.toLowerCase().startsWith("https://");
  }
}