@Override
  public int execute(ModuleRequest request, ModuleResponse response, ServletContext context) {
    this.context = context;
    String url = getURL(request);

    LOGGER.trace("Started processing request : {}", url);

    List<String> resourcesToMerge = Utils.findResourcesToMerge(request.getContextPath(), url);

    // If not modified, return 304 and stop
    ResourceStatus status = isNotModified(request, response, resourcesToMerge);
    if (status.isNotModified()) {
      LOGGER.debug("Resources Not Modified. Sending 304.");
      sendNotModified(response);
      return STOP_CHAIN;
    }

    String extensionOrPath = Utils.detectExtension(url); // in case of non js/css files it null
    if (extensionOrPath == null) {
      extensionOrPath =
          resourcesToMerge.get(
              0); // non grouped i.e. non css/js file, we refer it's path in that case
    }

    // Add appropriate headers
    addAppropriateResponseHeaders(
        extensionOrPath, resourcesToMerge, status.getActualETag(), response);
    try {
      OutputStream outputStream = response.getOutputStream();
      int resourcesNotFound =
          processResources(
              request.getContextPath(), outputStream, resourcesToMerge, autoCorrectUrlsInCss);

      if (resourcesNotFound > 0
          && resourcesNotFound == resourcesToMerge.size()) { // all resources not found
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        LOGGER.warn("All resources are not found. Sending 404.");
        return STOP_CHAIN;
      }

      if (outputStream != null) {
        // try {
        response.setStatus(HttpServletResponse.SC_OK);
        outputStream.close();
        // response.commit();
        // } catch (IOException e) {
        // e.printStackTrace();
        //  LOGGER.error(Utils.buildLoggerMessage("Response commit failed.", e.getMessage()));
        // return IRule.Status.CONTINUE;
        // }
      }
    } catch (IOException ex) {
      // ex.printStackTrace();
      LOGGER.error("Error in processing request.", ex);
      return OK;
    }

    LOGGER.debug("Finished processing Request : {}", url);
    return STOP_CHAIN;
  }
  public void setupRequest() {
    super.setupRequest();

    boolean removePreviousFilters =
        Utils.readBoolean(
            properties.getProperty(this.currentTestNumber + ".test.removePreviousFilters"), true);
    if (removePreviousFilters) {
      filters.clear();
      servletTestModule.setDoChain(false);
    } else {
      for (Filter filter : filters) {
        servletTestModule.addFilter(filter);
        servletTestModule.setDoChain(true);
      }
    }
    String filter = properties.getProperty(this.currentTestNumber + ".test.filter");
    if (filter != null && !filter.trim().equals("")) {
      String[] filtersString = filter.split(",");
      for (String filterClass : filtersString) {
        Class<?> clazz = null;
        try {
          clazz = Class.forName(filterClass);
          Filter f = servletTestModule.createFilter(clazz);
          if (!filters.contains(f)) {
            filters.add(f);
            servletTestModule.setDoChain(true);
          }
        } catch (ClassNotFoundException e) {
          LOGGER.debug("Error: ", e);
        }
      }
    }
  }
  @Override
  public DirectivePair parseDirectives(String ruleString) {

    DirectivePair pair = null;

    int index = 0;

    boolean autoCorrectUrlsInCss = true;

    String[] splits = ruleString.split("\\s+");

    assert splits.length >= 1;

    if (!splits[index++].equals(JSCSSMergeModule.class.getSimpleName())) return pair;

    if (splits.length > 1) {

      if ("autoCorrectUrlsInCss".equals(splits[index++])) {
        if (splits.length > 2) {
          autoCorrectUrlsInCss = Utils.readBoolean(splits[index], true);
        }
      }
    }
    pair = new DirectivePair(new JSCSSMergeDirective(autoCorrectUrlsInCss), null);
    return pair;
  }
 /**
  * @param request - HttpServletRequest
  * @param response - HttpServletResponse
  * @param resourcesToMerge - List of resources relative paths
  * @return true if not modified based on if-None-Match and If-Modified-Since
  */
 private ResourceStatus isNotModified(
     HttpServletRequest request, HttpServletResponse response, List<String> resourcesToMerge) {
   // If-Modified-Since
   String ifModifiedSince = request.getHeader(HTTP_IF_MODIFIED_SINCE);
   if (ifModifiedSince != null) {
     Date date = Utils.readDateFromHeader(ifModifiedSince);
     if (date != null) {
       if (!Utils.isAnyResourceModifiedSince(resourcesToMerge, date.getTime(), context)) {
         this.sendNotModified(response);
         return new ResourceStatus(null, true);
       }
     }
   }
   // If-None-match
   String requestETag = request.getHeader(HTTP_IF_NONE_MATCH_HEADER);
   String actualETag = Utils.buildETagForResources(resourcesToMerge, context);
   if (!Utils.isAnyResourceETagModified(resourcesToMerge, requestETag, actualETag, context)) {
     return new ResourceStatus(actualETag, true);
   }
   return new ResourceStatus(actualETag, false);
 }
  @Override
  public DirectivePair parseDirectives(String ruleString) {

    DirectivePair pair = null;
    int index = 0;

    int lineBreak = -1;

    boolean noMunge = false;

    boolean preserveSemi = false;

    boolean disableOptimizations = false;

    String charset = DEFAULT_CHARSET;

    String[] splits = ruleString.split("\\s+");

    if (!splits[index++].equals(YUICompressModule.class.getSimpleName())) return pair;

    while (index < splits.length) {
      if ((splits[index++]).equalsIgnoreCase(INIT_PARAM_LINE_BREAK)) {
        lineBreak = Utils.readInt(splits[index++], lineBreak);
      } else if ((splits[index++]).equalsIgnoreCase(INIT_PARAM_NO_MUNGE)) {
        noMunge = Utils.readBoolean(splits[index++], noMunge);
      } else if ((splits[index++]).equalsIgnoreCase(INIT_PARAM_PRESERVE_SEMI)) {
        preserveSemi = Utils.readBoolean(splits[index++], preserveSemi);
      } else if ((splits[index++]).equalsIgnoreCase(INIT_PARAM_DISABLE_OPTIMIZATIONS)) {
        disableOptimizations = Utils.readBoolean(splits[index++], disableOptimizations);
      } else if ((splits[index++]).equalsIgnoreCase(INIT_PARAM_CHARSET)) {
        charset = Utils.readString(splits[index++], charset);
      }
    }

    pair =
        new YUICompressRulesPair(
            new PreMinifyDirective(lineBreak, noMunge, preserveSemi, disableOptimizations, charset),
            null);
    return pair;
  }
  public boolean hasCorrectDateHeaders() {

    Date now = new Date();

    Date lastModified =
        Utils.readDateFromHeader(
            webMockObjectFactory.getMockResponse().getHeader(HEADER_LAST_MODIFIED));

    Date expires =
        Utils.readDateFromHeader(webMockObjectFactory.getMockResponse().getHeader(HEADER_EXPIRES));

    if (lastModified == null || expires == null) return false;

    long differenceInMilliseconds = expires.getTime() - now.getTime();

    // !TODO test lastModified value

    return (expiresMinutes - differenceInMilliseconds
        <= 5
            * 1000); // ensure difference between last modified and expires is almost same (tolerate
                     // 5 sec)
  }
 /**
  * @param extensionOrFile - .css or .js etc. (lower case) or the absolute path of the file in case
  *     of image files
  * @param resourcesToMerge - from request
  * @param hashForETag - from request
  * @param resp - response object
  */
 private void addAppropriateResponseHeaders(
     String extensionOrFile,
     List<String> resourcesToMerge,
     String hashForETag,
     HttpServletResponse resp) {
   String mime = Utils.selectMimeForExtension(extensionOrFile);
   if (mime != null) {
     LOGGER.trace("Setting MIME to ", mime);
     resp.setContentType(mime);
   }
   if (hashForETag != null) {
     resp.addHeader(HTTP_ETAG_HEADER, hashForETag);
     LOGGER.trace("Added ETag headers");
   }
   resp.addHeader(HEADER_X_OPTIMIZED_BY, X_OPTIMIZED_BY_VALUE);
 }
  /**
   * @param context - ServletContext
   * @param contextPath - context path or custom configured context path
   * @param cssFilePath - css file path
   * @param line - one single line in a css file
   * @return processed string with appropriate replacement of image URLs if any
   */
  private String processCSSLine(
      ServletContext context, String contextPath, String cssFilePath, StringBuffer line) {
    Matcher matcher = CSS_IMG_URL_PATTERN.matcher(line);
    String cssRealPath = context.getRealPath(cssFilePath);
    while (matcher.find()) {
      String refImgPath = matcher.group(1);
      if (!Utils.isProtocolURL(refImgPath)) { // ignore absolute protocol paths
        String resolvedImgPath = refImgPath;
        if (!refImgPath.startsWith("/")) {
          resolvedImgPath = Utils.buildProperPath(Utils.getParentPath(cssFilePath), refImgPath);
        }
        String imgRealPath = context.getRealPath(resolvedImgPath);
        String fingerPrint = Utils.buildETagForResource(resolvedImgPath, context);
        int offset = line.indexOf(refImgPath);
        line.replace(
            offset, // from
            offset + refImgPath.length(), // to
            contextPath + Utils.addFingerPrint(fingerPrint, resolvedImgPath));

        Utils.updateReferenceMap(cssRealPath, imgRealPath);
      }
    }
    return line.toString();
  }
 /**
  * @param request HttpServletRequest
  * @return The URL without fingerprint if it has any
  */
 private String getURL(HttpServletRequest request) {
   return Utils.removeFingerPrint(request.getRequestURI());
 }