content: package ro.isdc.wro.http.support;
  
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.io.Reader;
  import java.io.StringReader;
  import java.io.StringWriter;
  import java.io.Writer;
  import java.util.List;
  
  import javax.servlet.Filter;
  import javax.servlet.FilterChain;
  import javax.servlet.FilterConfig;
  import javax.servlet.ServletException;
  import javax.servlet.ServletRequest;
  import javax.servlet.ServletResponse;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  
  import org.apache.commons.io.IOUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import ro.isdc.wro.WroRuntimeException;
  import ro.isdc.wro.config.Context;
  import ro.isdc.wro.manager.factory.BaseWroManagerFactory;
  import ro.isdc.wro.model.group.processor.Injector;
  import ro.isdc.wro.model.group.processor.InjectorBuilder;
  import ro.isdc.wro.model.resource.Resource;
  import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
  import ro.isdc.wro.util.StopWatch;
  
  
  /**
   * Allows configuration of a list of processors to be applied on the
   *
   * @author Alex Objelean
   * @since 1.4.5
   * @created 17 Mar 2012
   */
  public abstract class AbstractProcessorsFilter
      implements Filter {
    private static final Logger LOG = LoggerFactory.getLogger(AbstractProcessorsFilter.class);
    private FilterConfig filterConfig;
  
    /**
     * {@inheritDoc}
     */
    public final void init(final FilterConfig config)
        throws ServletException {
      this.filterConfig = config;
      doInit(config);
    }
  
    /**
     * Allows custom initialization.
     */
    protected void doInit(final FilterConfig config) {
    }
  
    /**
     * {@inheritDoc}
     */
    public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain)
        throws IOException, ServletException {
      final HttpServletRequest request = (HttpServletRequest) req;
      final HttpServletResponse response = (HttpServletResponse) res;
      try {
        // add request, response & servletContext to thread local
        Context.set(Context.webContext(request, response, filterConfig));
        final ByteArrayOutputStream os = new ByteArrayOutputStream();
        final HttpServletResponse wrappedResponse = new RedirectedStreamServletResponseWrapper(os, response);
        chain.doFilter(request, wrappedResponse);
        final Reader reader = new StringReader(new String(os.toByteArray(), Context.get().getConfig().getEncoding()));
  
        final StringWriter writer = new StringWriter();
        final String requestUri = request.getRequestURI().replaceFirst(request.getContextPath(), "");
        doProcess(requestUri, reader, writer);
        // it is important to update the contentLength to new value, otherwise the transfer can be closed without all
        // bytes being read. Some browsers (chrome) complains with the following message: ERR_CONNECTION_CLOSED
        final int contentLength = writer.getBuffer().length();
        response.setContentLength(contentLength);
        // Content length can be 0 when the 30x (not modified) status code is returned.
        if (contentLength > 0) {
          IOUtils.write(writer.toString(), response.getOutputStream());
        }
      } catch (final RuntimeException e) {
        onRuntimeException(e, response, chain);
      } finally {
        Context.unset();
      }
    }
  
    /**
     * Applies configured processor on the intercepted stream.
     */
    private void doProcess(final String requestUri, final Reader reader, final Writer writer)
        throws IOException {
      Reader input = reader;
      Writer output = null;
      LOG.debug("processing resource: {}", requestUri);
      try {
        final StopWatch stopWatch = new StopWatch();
        final Injector injector = InjectorBuilder.create(new BaseWroManagerFactory()).build();
        final List<ResourcePreProcessor> processors = getProcessorsList();
        if (processors == null || processors.isEmpty()) {
          IOUtils.copy(reader, writer);
        } else {
          for (final ResourcePreProcessor processor : processors) {
            stopWatch.start("Using " + processor.getClass().getSimpleName());
            // inject all required properties
            injector.inject(processor);
  
            output = new StringWriter();
            LOG.debug("Using {} processor", processor);
            processor.process(createResource(requestUri), input, output);
  
            input = new StringReader(output.toString());
            stopWatch.stop();
          }
          LOG.debug(stopWatch.prettyPrint());
          writer.write(output.toString());
        }
      } finally {
        reader.close();
        writer.close();
      }
    }
  
    /**
     * @param requestUri
     *          the uri of the requested resource.
     * @return the {@link Resource} to pass as argument to the processor.
     */
    protected Resource createResource(final String requestUri) {
      return Resource.create(requestUri);
    }
  
    /**
     * Invoked when a {@link RuntimeException} is thrown. Allows custom exception handling. The default implementation
     * redirects to 404 for a specific {@link WroRuntimeException} exception when in DEPLOYMENT mode.
     *
     * @param e
     *          {@link RuntimeException} thrown during request processing.
     */
    protected void onRuntimeException(final RuntimeException e, final HttpServletResponse response,
        final FilterChain chain) {
      LOG.debug("RuntimeException occured", e);
      try {
        LOG.debug("Cannot process. Proceeding with chain execution.");
        chain.doFilter(Context.get().getRequest(), response);
      } catch (final Exception ex) {
        // should never happen
        LOG.error("Error while chaining the request.");
      }
    }
  
    /**
     * @return a list of processors to apply for this filter.
     */
    protected abstract List<ResourcePreProcessor> getProcessorsList();
  
    /**
     * {@inheritDoc}
     */
    public void destroy() {
    }
  }
  