content: /*
   * Copyright (c) 2010. All rights reserved.
   */
  package ro.isdc.wro.config;
  
  import static org.apache.commons.lang3.Validate.notNull;
  
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.UUID;
  import java.util.concurrent.Callable;
  
  import javax.servlet.FilterConfig;
  import javax.servlet.ServletContext;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  
  import org.apache.commons.lang3.builder.ToStringBuilder;
  import org.apache.commons.lang3.builder.ToStringStyle;
  
  import ro.isdc.wro.WroRuntimeException;
  import ro.isdc.wro.config.jmx.WroConfiguration;
  import ro.isdc.wro.config.support.ContextPropagatingCallable;
  import ro.isdc.wro.http.WroFilter;
  
  
  /**
   * Holds the properties related to a request cycle.
   *
   * @author Alex Objelean
   */
  public class Context
      implements ReadOnlyContext {
    /**
     * Maps correlationId with a Context.
     */
    private static final Map<String, Context> CONTEXT_MAP = Collections.synchronizedMap(new HashMap<String, Context>());
    /**
     * Holds a correlationId, created in {@link WroFilter}. A correlationId will be associated with a {@link Context}
     * object.
     */
    private static ThreadLocal<String> CORRELATION_ID = new ThreadLocal<String>();
    private WroConfiguration config;
    /**
     * Request.
     */
    private transient HttpServletRequest request;
    /**
     * Response.
     */
    private transient HttpServletResponse response;
    /**
     * ServletContext.
     */
    private transient ServletContext servletContext;
    /**
     * FilterConfig.
     */
    private transient FilterConfig filterConfig;
    /**
     * The path to the folder, relative to the root, used to compute rewritten image url.
     */
    private String aggregatedFolderPath;
  
  
    /**
     * @return {@link WroConfiguration} singleton instance.
     */
    public WroConfiguration getConfig() {
      return config;
    }
  
  
    /**
     * DO NOT CALL THIS METHOD UNLESS YOU ARE SURE WHAT YOU ARE DOING.
     * <p/>
     * sets the {@link WroConfiguration} singleton instance.
     */
    public void setConfig(final WroConfiguration config) {
      notNull(config);
      this.config = config;
    }
  
  
    /**
     * A context useful for running in web context (inside a servlet container).
     */
    public static Context webContext(final HttpServletRequest request, final HttpServletResponse response,
      final FilterConfig filterConfig) {
      return new Context(request, response, filterConfig);
    }
  
  
    /**
     * A context useful for running in non web context (standalone applications).
     */
    public static Context standaloneContext() {
      return new Context();
    }
  
  
    /**
     * @return {@link Context} associated with CURRENT request cycle.
     */
    public static Context get() {
      validateContext();
      final String correlationId = CORRELATION_ID.get();
      return CONTEXT_MAP.get(correlationId);
    }
  
    /**
     * @return true if the call is done during wro4j request cycle. In other words, if the context is set.
     */
    public static boolean isContextSet() {
      return CORRELATION_ID.get() != null && CONTEXT_MAP.get(CORRELATION_ID.get()) != null;
    }
  
    /**
     * Checks if the {@link Context} is accessible from current request cycle.
     */
    private static void validateContext() {
      if (!isContextSet()) {
        throw new WroRuntimeException("No context associated with CURRENT request cycle!");
      }
    }
  
    /**
     * Set a context with default configuration to current thread.
     */
    public static void set(final Context context) {
      set(context, new WroConfiguration());
    }
  
    /**
     * Associate a context with the CURRENT request cycle.
     *
     * @param context {@link Context} to set.
     */
    public static void set(final Context context, final WroConfiguration config) {
      notNull(context);
      notNull(config);
      context.setConfig(config);
  
      final String correlationId = generateCorrelationId();
      CORRELATION_ID.set(correlationId);
      CONTEXT_MAP.put(correlationId, context);
    }
  
    /**
     * @return a string representation of an unique id used to store Context in a map.
     */
    private static String generateCorrelationId() {
      return UUID.randomUUID().toString();
    }
  
  
    /**
     * Remove context from the local thread.
     */
    public static void unset() {
      final String correlationId = CORRELATION_ID.get();
      if (correlationId != null) {
        CONTEXT_MAP.remove(correlationId);
      }
      CORRELATION_ID.remove();
    }
  
  
    /**
     * Perform context clean-up.
     */
    public static void destroy() {
      unset();
      //remove all context objects stored in map
      CONTEXT_MAP.clear();
    }
  
    /**
     * Set the correlationId to the current thread.
     */
    public static void setCorrelationId(final String correlationId) {
      notNull(correlationId);
      CORRELATION_ID.set(correlationId);
    }
  
    /**
     * Remove the correlationId from the current thread. This operation will not remove the {@link Context} associated
     * with the correlationId. In order to remove context, call {@link Context#unset()}.
     * <p/>
     * Unsetting correlationId is useful when you create child threads which needs to access the correlationId from the
     * parent thread. This simulates the {@link InheritableThreadLocal} functionality.
     */
    public static void unsetCorrelationId() {
      CORRELATION_ID.remove();
    }
  
    /**
     * @return the correlationId associated with this thread.
     */
    public static String getCorrelationId() {
      validateContext();
      return CORRELATION_ID.get();
    }
  
    /**
     * Decorates a callable with {@link ContextPropagatingCallable} making it possible to access the {@link Context} from
     * within the decorated callable.
     *
     * @param callable
     *          the {@link Callable} to decorate.
     * @return the decorated callable.
     */
    public static <T> Callable<T> decorate(final Callable<T> callable) {
      return new ContextPropagatingCallable<T>(callable);
    }
  
    /**
     * @return number of {@link Context} objects which were created but not yet destroyed. This is useful to detect leaks
     *         (mostly for unit testing).
     */
    public static int countActive() {
      return CONTEXT_MAP.size();
    }
  
    /**
     * Private constructor. Used to build {@link StandAloneContext}.
     */
    private Context() {}
  
  
    /**
     * Constructor.
     */
    private Context(final HttpServletRequest request, final HttpServletResponse response, final FilterConfig filterConfig) {
      notNull(request);
      notNull(response);
      this.request = request;
      this.response = response;
      this.servletContext = filterConfig != null ? filterConfig.getServletContext() : null;
      this.filterConfig = filterConfig;
    }
  
  
    /**
     * @return the request
     */
    public HttpServletRequest getRequest() {
      return this.request;
    }
  
  
    /**
     * @return the response
     */
    public HttpServletResponse getResponse() {
      return this.response;
    }
  
  
    /**
     * @return the servletContext
     */
    public ServletContext getServletContext() {
      return this.servletContext;
    }
  
  
    /**
     * @return the filterConfig
     */
    public FilterConfig getFilterConfig() {
      return this.filterConfig;
    }
  
    /**
     * {@inheritDoc}
     */
    public String getAggregatedFolderPath() {
      return this.aggregatedFolderPath;
    }
  
    /**
     * @param aggregatedFolderPath the aggregatedFolderPath to set
     */
    public void setAggregatedFolderPath(final String aggregatedFolderPath) {
      this.aggregatedFolderPath = aggregatedFolderPath;
    }
  
    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
      return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
    }
  }
  